UNIX 环境高级编程|UNIX 系统概述
GitHub: https://github.com/storagezhang
Emai: debugzhang@163.com
本文为《UNIX 环境高级编程》第 1 章学习笔记
1.2 UNIX 体系结构
系统调用
内核的接口被称为系统调用。公用函数库构建在系统调用接口之上,应用程序既可使用公用函数库,也可使用系统调用。
shell
shell 是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
man
man (选项) (参数)
章节 | 名称 |
---|---|
1 | Standard commands(标准命令) |
2 | System calls(系统调用) |
3 | Library functions(库函数) |
4 | Special devices(设备说明) |
5 | File formats(文件格式) |
6 | Games and toys(游戏和娱乐) |
7 | Miscellaneous(杂项) |
8 | System Administrative Commands(管理员命令) |
9 | Kernel routines [Non standard] |
1.4 文件和目录
工作目录
每个进程都有一个工作目录,有时称其为当前工作目录。所有的相对路径名都是从工作目录开始解释。进程可以用 chdir
函数更改其工作目录。
起始目录
登录时,工作目录设置为起始目录,该起始目录从口令文件中相应用户的登录项中取得。
1.5 输入和输出
文件描述符
文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符。在读和写文件时,可以使用这个文件描述符。
标准输入、标准输出和标准错误
每当运行一个新程序时,所有的 shell 都为其打开 3 个文件描述符,即标准输入、标准输出和标准错误。如果不做特殊处理,这 3 个描述符都链接向终端。
不带缓冲的 I/O
函数 open
、read
、write
、lseek
以及 close
提供了不带缓冲的 I/O。这些函数都是用文件描述符。
标准 I/O
标准 I/O 函数为那些不带缓冲的 I/O 函数提供了一个带缓冲的接口。使用标准 I/O 函数无需担心如何选择最佳的缓冲区大小。
1.6 程序和进程
程序
程序是一个存储在磁盘上某个目录中的可执行文件。内核使用 exec
函数(7 个 exec
函数之一),将程序读入内存,并执行程序。
进程和进程 ID
程序的执行实例被称为进程。
UNIX 系统确保每个进程都有一个唯一的数字标识符,称为进程 ID。进程 ID 总是一个非负整数。
进程控制
有 3 个用于进程控制的主要函数:fork
、exec
和 waitpid
。
exec
函数有 7 种变体,但经常把它们统称为 exec
函数。
fork
和跟随其后的 exec
两者的组合就是某些操作系统所称的产生一个新进程。在 UNIX 系统中,这两部分分离成两个独立的函数。
线程和线程 ID
一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
与进程相同,线程也用 ID 标识。但是,线程 ID 只在它所属的进程内起作用。一个进程中的线程 ID 在另一个进程中没有意义。当在一进程中对某个特定线程进行处理时,我们可以使用该线程的 ID 引用它。
1.7 出错处理
errno
当 UNIX 系统函数出错时,通常会返回一个负值,而且整型变量 errno
通常被设置为具有特定信息的值。
- 文件 <errno.h> 中定义了
errno
以及可以赋予它的各种常量。 - 这些常量都以字符
E
开头。 - 在 Linux 中,出错常量在
errno(3)
手册页中列出。 - 在支持线程的环境中,多个线程共享进程地址空间,每个线程都有属于它自己的局部
errno
以避免一个线程干扰另一个线程。 - 如果没有出错,
errno
值不会被历程清除。因此,仅当函数的返回值指明出错时,才检验其值。 - 任何函数都不会将
errno
值设置为 0,而且在 <errno.h> 中定义的所有常量都不为 0。
C 标准定义了两个函数,它们用于打印出错信息:
#include <string.h>
char *streror(int errnum);
strerror
函数将 errnum
(通常就是 errno
值)映射为一个出错消息字符串,并且返回此字符串的指针。
#include <stdio.h>
void perror(const char *msg);
perror
函数基于 errno
的当前值,在标准错误上产生一条出错消息,然后返回。
出错恢复
可将在 <errno.h> 中定义的各种出错分成两类:致命性的和非致命性的。
- 对于致命性的错误,无法执行恢复动作。
- 最多只能在用户屏幕上打印一条出错消息或者将一条出错消息写入日志,然后退出。
- 对于非致命性的出错,有时可以较妥善地进行处理。
- 大多数非致命性出错是暂时的(如资源短缺)。
与资源相关的非致命性出错包括:
EAGAIN
ENFILE
ENOBUFS
ENOLCK
ENOSPC
EWOULDBLOCK
ENOMEM
(有时)EBUSY
(中断一个慢速系统调用时)
对于资源相关的非致命性出错的典型恢复操作是延迟一段时间,然后重试。
1.8 用户标识
用户 ID
口令文件登录项中的用户 ID(user ID)是一个数值,它向系统标识各个不同的用户。
系统管理员在确定一个用户的登录名的同时,确定其用户 ID。
用户不能更改其用户 ID。
用户 ID 为 0 的用户为根用户(root)或超级用户(superuser),称这种用户的特权为超级用户特权。
如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。
组 ID
口令文件登录项也包括用户的组 ID(group ID),它是一个数值。
组 ID 也是由系统管理员在指定用户登录名时分配的。
这种机制允许同组的各个成员之间共享资源。
附属组 ID
除了在口令文件中对一个登录名指定一个组 ID 外,大多数 UNIX 系统版本还允许一个用户属于另外一些组。
读文件 /etc/group
,寻找列有该用户作为其成员的前16 个记录项就可以得到该用户的附属组 ID(supplementary group ID)。
1.9 信号
信号用于通知进程发生了某种情况。
进程有 3 种处理信号的方式:
- 忽略信号
- 有些信号表示硬件异常,例如,除以 0 或访问进程地址空间以外的存储单元等,因为这些异常产生的后果不确定,所以不推荐使用这种处理方式。
- 按系统默认方式处理
- 对于除数为 0,系统默认方式是终止该进程。
- 提供一个函数,信号发生时调用该函数,这被称为捕捉该信号
- 通过提供自编的函数,我们就能知道什么时候产生了信号,并按期望的方式处理它。
终端键盘上有两种产生信号的方法:
- 中断键(interrupt key):
Delete
或Ctrl + C
- 退出键(quit key):
Ctrl + \
- 它们被用于终端当前运行的进程。
另一种产生信号的方法是调用 kill
函数,在一个进程中调用此函数就可向另一个进程发送一个信号。这样做的限制是:当向一个进程发送信号时,我们必须是那个进程的所有者或者是超级用户。
1.10 时间值
历史上,UNIX 系统使用两种不同的时间值:
- 日历时间:
- 自协调世界时(Coordinated Universal Time,UTC)1970 年 1 月 1 日 00:00:00 这个特定时间以来所经历的秒数累计值。
- 系统基本数据类型
time_t
用于保存这种时间值。
- 进程时间:
- 也被称为 CPU 时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴答计算。
- 系统基本数据类型
clock_t
用于保存这种时间值。
当度量一个进程的执行时间时,UNIX 系统为一个进程维护了 3 个进程时间值:
- 时钟时间
- 又称为墙上时钟时间(wall clock time),它是进程运行的时间总量,其值与系统中同时运行的进程数有关。
- 用户 CPU 时间
- 是执行用户指令所用的时间量。
- 系统 CPU 时间
- 是为该进程执行内核程序所经历的时间。
- 取得任一进程的三个时间值命令:
time(1)
1.11 系统调用和库函数
所有的操作系统都提供多种服务的入口点,由此程序向内核请求服务,这些入口点被称为系统调用。
UNIX 所使用的技术是为每个系统调用在标准 C 库中设置一个具有同样名字的函数。用户进程用标准 C 调用序列来调用这些函数,然后,函数又用系统所要求的技术调用相应的内核服务。
通用库函数可能会调用一个或多个内核的系统调用,但是它们并不是内核的入口点。
系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。
习题
在 1.7 节中, perror 的参数是用 ISO C 的属性 const 定义的,而 strerror 的整形参数没有用此属性定义,为什么?
strerror
函数定义:
#include <string.h>
char *streror(int errnum);
perror
函数定义:
#include <stdio.h>
void perror(const char *msg);
strerror
传递的参数为 int
类型,传递的是值,因此不会修改原来参数的值。
perror
传递的参数为 const char *
类型,传递的是对应的地址,如果不使用 const
限定的话,那么如果在函数内修改了值,函数外的变量值也会变化。
若日历时间存放在带符号的 32 位整型数中,那么到哪一年它将溢出?可以用什么办法扩展溢出浮点数?采用的策略是否与现有的应用相兼容?
32 位整型数的最大值为 2147483647 ,大概为 68.09626 年,最大可表示到 2038-01-19 11:14:07。
将 time_t
数据类型定为 64 位整型。
如果它现在是 32 位整型,那么为使应用程序正常工作,应当对其重新编译。
但是,某些文件系统及备份介质以 32 位整型存放时间。对于这些同样需要加以更新,但又需要能读旧的格式。
若进程时间存放在带符号的 32 位整型数中,而且每秒为 100 时钟滴答,那么经过多少天后该时间值将会溢出?
大约 248 天。
- 点赞
- 收藏
- 关注作者
评论(0)