UNIX环境高级编程——1.UNIX基础知识
UNIX基础知识
UNIX体系结构
- 严格意义上来说,可以将操作系统定义为一种软件,控制计算机硬件资源,提供程序运行环境。通常把这种软件成为内核。
- 内核的接口被成为系统调用(system call)。公共函数库构建在系统调用接口之上,应用程序既可使用公共函数库,也可使用系统调用。shell是一个特殊的应用程序,为其他应用程序提供接口。
- 从广义上来说,操作系统包含了内核和一些其他软件,这些软件使得计算机能够发货作用,并且具有自己的特性。这些软件包含了系统实用程序(system utility)、应用程序、shell以及公用函数库等。
文件与目录
- UNIX文件系统是目录和文件的一种层次结构,所有东西的起点是称为根(root)的目录,这个目录的名称是一个字符“/”。
- 创建新目录时会自动创建了两个文件名:.(称为点)和…(称为点点)。点指向当前目录,点点指向父目录。在最高层次的根目录中,点点与点相同。
输入和输出
- 文件描述符(file descriptor)通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。
- 每当运行一个新程序时,所有的 shell 都为其打开 3 个文件描述符,即标准输入(standard input)、标准输出(standard output)以及标准错误(standard error)。如果不做特殊处理,例如就像简单的命令ls,则这3个描述符都链接向终端。
- 函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用文件描述符。
- 两个常量STDIN_FILENO和STDOUT_FILENO定义在<unistd.h>头文件中,它们指定了标准输入和标准输出的文件描述符。在POSIX标准中,它们的值分别是0和1,但是考虑到可读性,我们将使用这些名字来表示这些常量。
- 标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小,使用标准I/O函数还简化了对输入行的处理(常常发生在UNIX的应用程序中)。
程序和进程
- 程序(program)是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。
- 程序的执行实例被称为进程(process)。某些操作系统用任务(task)表示正在被执行的程序。
- UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一个非负整数。
- 有3个用于进程控制的主要函数:
- fork:创建一个新进程,新进程是调用进程的一个副本。
- exec(七种变体):执行命令或程序文件
- waitpid:返回子进程的终止状态(status 变量)
- 通常,一个进程只有一个控制线程(thread)(某一时刻执行的一组机器指令)。
- 一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
- 与进程相同,线程也用ID标识。但是,线程ID只在它所属的进程内起作用。一个进程中的线程ID在另一个进程中没有意义。
出错处理
- 对于 errno 应当注意两条规则。第一条规则是:如果没有出错,其值不会被例程清除。因此,仅当函数的返回值指明出错时,才检验其值。第二条规则是:任何函数都不会将 errno 值设置为0,而且在<errno.h>中定义的所有常量都不为0。
用户标识
- 用户 ID 为 0 的用户为根用户(root)或超级用户(superuser)
- 现今的UNIX系统使用32位整型数表示用户ID和组ID
信号
- 信号(signal)用于通知进程发生了某种情况。例如,若某一进程执行除法操作,其除数为0,则将名为SIGFPE(浮点异常)的信号发送给该进程。
- 进程有以下3种处理信号的方式。
- 忽略信号
- 按系统默认方式处理
- 提供一个函数,信号发生时调用该函数,这被称为捕捉该信号
- 在一个进程中调用此函数就可向另一个进程发送一个信号。当然这样做也有些限制:当向一个进程发送信号时,我们必须是那个进程的所有者或者是超级用户。
时间
- 日历时间:自协调世界时(Coordinated Universal Time, UTC)以来所经过的秒数累计值。系统基本数据类型time_t用于保存这种时间值。
- 进程时间:CPU时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴答计算。系统基本数据类型clock_t保存这种时间值。
- 时钟时间:进程运行的时间总量,其值与系统中同时运行的进程数有关。
- 用户CPU时间:执行用户指令所用的时间量。
- 系统CPU时间:为该进程执行内核程序所经历的时间。
系统调用和库函数
- UNIX所使用的技术是为每个系统调用在标准C库中设置一个具有同样名字的函数。用户进程用标准C调用序列来调用这些函数,然后,函数又用系统所要求的技术调用相应的内核服务。
- 系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。
习题
-
1.1:在系统上验证,除根目录外,目录.和…是不同的。
- 答:在根目录执行ls .和ls …输出不变
-
1.2:分析图1-6程序的输出,说明进程ID为852和853的进程发生了什么情况?
- 答:在大多数的Linux/Unix系统中,生成一个进程ID的方法是从0开始依次连续分配,所以在两次程序运行之间,系统创建了两个新的进程。
-
在1.7节中,perror的参数是用ISO C的属性const定义的,而strerror的整型参数没有用此属性定义,为什么?
#include <string.h> char *strerror(int errnum); #include <stdio.h> void perror(const char *msg);
- 答:函数声明中,strerror传参是int类型,会发生值的拷贝,不会修改原来的值,而perror传参是const char*类型,传的是地址,如果不适用const修饰符,在函数内修改了值会导致外部变量的值也发生变化。
-
若日历时间存放在带符号的32位整型数中,那么到哪一年它将溢出?可以用什么方法扩展溢出浮点数?采用的策略是否与现有的应用相兼容?
- 答:带符号的32位整型数能表示的最大值是2147483647,约为68.09625973年,也就是说会在格林威治时间2038年01月19日溢出。采用double扩展溢出浮点数,不过问题是浮点数与现有的定点数应用无法兼容。
-
若进程时间存放在带符号的32位整型数中,而且每秒为100时钟滴答,那么经过多少天后该时间值将会溢出?
- 答:也就是计算时钟滴答什么时候溢出,2147483647 / 3600 / 24 / 100 = 248.55
参考资料
- http://c.biancheng.net/view/314.html
随书练习源码地址
- https://github.com/Johncdy/BookSource/tree/main/Advanced_Programming_in_the_UNIX_Environment