UNIX基础知识
- UNIX体系结构
- 登陆
- 文件和目录
- 文件系统【1、目录是一个包含目录项的文件;2、根目录:所有东西的起点;】
- 文件属性:
stat
和fstat
返回包含所有文件属性的一个信息结构;
- 文件属性:
- 文件名
-
斜线
/
和空字符不能出现在文件名中,斜线用来分隔构成路径名的各文件名,空字符用来终止一个路径名 - 两个特殊文件
.
(点)和..
(点点),前者指向当前目录,后者指向父目录
-
斜线
- 路径名【绝对路径以
/
开头,相对路径以.
开头】
此程序读取一个文件夹并输出文件夹下的文件及目录信息,其核心代码可以简略如下:#include<dirent.h> #include<apue.h> #include <error.h> int dir(char *path) { DIR *dp; struct dirent *dirp; if (path == NULL) err_quit("usage: directory_name not null"); if ((dp = opendir(path)) == NULL) err_sys("can't open %s", path); while ((dirp = readdir(dp)) != NULL) printf("%s\n", dirp->d_name); closedir(dp); return 0; } int main(int argc, char *argv[]) { char *path = ".."; return dir(path); }
可以看一下DIR *dp;//目录 struct dirent *dirp;//文件或者目录文件结构 dp = opendir(path);//打开目录 while ((dirp = readdir(dp)) != NULL)//不断读取目录 printf("%s\n", dirp->d_name);//输出文件名字 closedir(dp);//结束读取之后关闭目录
strut dirent
#if __DARWIN_64_BIT_INO_T struct dirent __DARWIN_STRUCT_DIRENTRY; #endif /* __DARWIN_64_BIT_INO_T */ #define __DARWIN_STRUCT_DIRENTRY { \ __uint64_t d_ino; /* file number of entry */ \ __uint64_t d_seekoff; /* seek offset (optional, used by servers) */ \ __uint16_t d_reclen; /* length of this record */ \ __uint16_t d_namlen; /* length of string in d_name */ \ __uint8_t d_type; /* file type, see below */ \ char d_name[__DARWIN_MAXPATHLEN]; /* entry name (up to MAXPATHLEN bytes) */ \ }
- 工作目录
- 每个进程都有一个工作目录:当前工作目录,相对路径从工作目录开始解释。
- 可以使用
chdir函数
改变当前工作目录。
- 起始目录
- 用户登陆时的工作目录设置为
起始目录
,起始目录在登陆口令文件
中定义。
- 用户登陆时的工作目录设置为
- 文件系统【1、目录是一个包含目录项的文件;2、根目录:所有东西的起点;】
- 输入和输出
-
文件描述符
- 一个小的非负整数,内核用于标识一个特定进程正在访问的文件
- 当内核打开一个文件时或者创建一个文件时,它都返回一个文件描述符
-
标准输入、标准输出和标准错误
- 当运行一个新程序时,所有的
shell
都为其打开3个文件描述符,标准输入
、标准输出
和标准错误
- 3个文件描述符都可重定向到文件(详细参考shell编程)
- 当运行一个新程序时,所有的
-
不带缓冲的I/O
- 函数
open
、read
、write
、lseek
以及close
提供了不带缓冲的I/O。这些函数都适用文件描述符。
#include <apue.h> #include <error.h> #define BUFFSIZE 4096 int main() { int n; char buf[BUFFSIZE]; while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) if (write(STDOUT_FILENO, buf, n) != n) err_sys("write error"); if (n < 0) err_sys("read error"); exit(0); }
#define STDIN_FILENO 0 /* standard input file descriptor */ #define STDOUT_FILENO 1 /* standard output file descriptor */ #define STDERR_FILENO 2 /* standard error file descriptor */
read
函数返回读取的字节数,此值用作要写的字节数。当到达输入文件的尾端时,read
返回0
,程序停止执行。如果发生了一个读错误,read
返回-1
。 - 函数
-
标准I/O
- 标准I/O为那些不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。
- 常见的标准I/O函数:
printf
、fgets
(读取一行)、getc
、putc
- 标准I/O常量:
stdin
(标准输入)、stdout
(标准输出)、EOF
#include <apue.h> #include <error.h> int main() { int c; while ((c = getc(stdin))!=EOF) if (putc(c,stdout)==EOF) err_sys("output error"); if (ferror(stdin)) err_sys("input error"); exit(0); }
-
- 程序和进程
- 程序【内核使用
exec函数
(7个之一)将存储在磁盘上的可执行文件(程序)读入内存,并执行】 - 进程与进程ID
-
进程:程序的执行实例
-
进程ID:每个进程唯一的数字标识符,非负整数
-
打印进程ID,
getpid()
函数得到当前进程ID,其返回一个pid_t
类型数据,返回值保证在long
数据范围内(在LLDB编译器中,看到pid_t实际上是一个int32)。#include <apue.h> int main(void){ printf("hello world from process id %ld\n",(long)getpid()); exit(0); }
-
- 进程控制
- 进程控制的主要函数:
fork
、exec
和waitpid
,exec
函数有7种变体。
#include<apue.h> #include<error.h> #include<sys/wait.h> int main(){ char buf[MAXLINE]; pid_t pid; int status; printf("%% "); //fgets读取一行,当键入文件终止符(一般crtl+D),返回null指针 //fgets返回的每一行都以换行符终止,后随一个null字节,因此可用strlen计算字符串长度 while(fgets(buf,MAXLINE,stdin)!=NULL){ if(buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0;//execlp函数要求的参数是以null结尾而不是换行符 //fork创建一个新进程,新进程是调用进程的一个副本 //调用进程为父进程,新进程为子进程 //fork对父进程返回子进程ID,对子进程则返回0 //fork调用一次,返回两次(分别在父进程和子进程中) if((pid=fork())<0) err_sys("fork error"); else if(pid==0){ //在子进程中,调用execlp以执行从标准输入读入的命令 execlp(buf,buf,(char*)0); err_ret("couldn't execute: %s",buf); exit(127); } //父进程等待子进程终止 //waitpid指定要等待的子进程,并返回子进程的终止状态 if((pid=waitpid(pid,&status,0))<0) err_sys("waitpid error"); printf("%% "); } exit(0); }
- 进程控制的主要函数:
- 线程与线程ID
- 一个进程内部可以有多个控制线程
- 一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
- 因为他们能访问同一存储区,所以各线程访问共享数据时需要采取同步措施以避免不一致性。
- 线程ID:线程ID只在其所属的进程内起作用。
- 程序【内核使用
- 出错处理
- 文件
<errno.h>
中定义了errno
以及可以赋予它的各种常量,这些错误常量一般以E
开头 -
errno
使用的两条规则:- 1、如果没有出错,其值不会被例程清除,所以仅在出错时检验其值;
- 2、任何函数都不会将
errno
设置为0.
- 打印出错信息
-
strerror
函数:将errnum(通常就是errno)映射为一个出错消息字符串,并返回 -
perror
函数:基于errno值,在标准错误上产生一条出错信息,然后返回
#include <apue.h> #include<errno.h> int main(int argc,char* argv[0]){ fprintf(stderr,"EACCES: %s\n",strerror(EACCES)); errno=ENOENT; perror(argv[0]); exit(0); }
EACCES: Permission denied /Users/test/aupe3/build/1_basic/basic: No such file or directory
-
- 出错恢复
- 致命错误:无法执行恢复动作,最多能打印出错信息,写入日志;
- 非致命性错误:对于资源相关的非致命错误典型的恢复操作是延时一段时间,然后重试。对于延时策略,一些应用使用指数补偿算法。
- 文件
- 用户标识
- 用户ID:每个用户有一个唯一的用户ID,向系统标识不同的用户,用户不能更改其用户ID,root用户的用户ID为0.
- 组ID:多个登陆项具有相同的组ID,组ID被用于将若干用户集合到项目或者部门中去,允许同组的成员之间共享资源。组文件将组名映射为数值的组ID,组文件通常是/etc/group。
- 使用用户ID和组ID,只需4字节保存两个信息(每个以双字节整型值存放),而使用ASCII字符需要消耗更多的内存,且检验权限时,字符串比较相对比较耗时。
- 但是对于用户而言,使用名字相比使用ID数值更加方便,所以口令文件中包含了登陆名和用户ID之间的映射关系,而组文件则包含了组名与组ID之间的映射关系。
#include<apue.h> int main(){ printf("uid = %d, gid = %d\n",getuid(),getgid()); exit(0); }
- 信号
- 用于通知进程发生了某种情况,进程有3种处理信号的方式:
- 忽略信号
- 按系统默认方式处理
- 提供一个函数捕捉该信号进行处理
- 为之前的shell进程控制例程添加信号处理函数
#include<apue.h> #include<error.h> #include<sys/wait.h> static void sig_int(int); int main() { char buf[MAXLINE]; pid_t pid; int status; //signal当产生了指定的信号(SIGINT)时,执行sig_int函数 if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal error"); printf("%% "); //fgets读取一行,当键入文件终止符(一般crtl+D),返回null指针 //fgets返回的每一行都以换行符终止,后随一个null字节,因此可用strlen计算字符串长度 while (fgets(buf, MAXLINE, stdin) != NULL) { if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = 0;//execlp函数要求的参数是以null结尾而不是换行符 //fork创建一个新进程,新进程是调用进程的一个副本 //调用进程为父进程,新进程为子进程 //fork对父进程返回子进程ID,对子进程则返回0 //fork调用一次,返回两次(分别在父进程和子进程中) if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) { //在子进程中,调用execlp以执行从标准输入读入的命令 execlp(buf, buf, (char *) 0); err_ret("couldn't execute: %s", buf); exit(127); } //父进程等待子进程终止 //waitpid指定要等待的子进程,并返回子进程的终止状态 if ((pid = waitpid(pid, &status, 0)) < 0) err_sys("waitpid error"); printf("%% "); } exit(0); } void sig_int(int signo){ printf("interrupt\n%% "); }
- 用于通知进程发生了某种情况,进程有3种处理信号的方式:
- 时间值
- 日历时间:1970.1.1 00:00:00(UTC) 这个特定时间以来所经过的秒数累计值,
time_t
数据结构保存这种时间类型; - 进程时间:CPU时间,用以度量进程使用的中央处理器资源,
clock_t
数据类型保存这种时间类型。 - 度量一个进程的执行时间时,UNIX系统为一个进程维护了3个进程时间值:
- 时钟时间;
- 墙上时钟时间,进程运行的时间总量,其值与系统中同时运行的进程数有关;
- 用户CPU时间;
- 执行用户指令所用的时间量;
- 系统CPU时间。
- 为该进程执行内核程序所经历的时间。
- 时钟时间;
- 日历时间:1970.1.1 00:00:00(UTC) 这个特定时间以来所经过的秒数累计值,
- 系统调用与库调用
- 系统调用:各种版本的UNIX实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点称为系统调用;
- 库调用
- 区别:
- 一般可以替换库函数,但是系统调用通常是不能被替换的;
- 很多库函数会调用系统调用;
- 系统调用通常提供一个最小接口,而库函数通常提供比较复杂的功能;
- 习题
- 1.4 若日历时间存放在带符号的32位整型数中,那么到哪一年溢出?怎么解决?
- 带符号32位整型数最大值为,可知将在年溢出,可以换用int64位
- 1.5 若进程时间存放在带符号的32位整型数中,而且每秒100时钟滴答,那么经过多少天后该时间溢出?
- 1.4 若日历时间存放在带符号的32位整型数中,那么到哪一年溢出?怎么解决?
- Reference
- 《UNIX环境高级编程》第3版
- 其他
网友评论