《UNIX环境高级编程》读书笔记
《UNIX环境高级编程 第3版》补漏笔记,只记录自己在意的知识。
第一章 UNIX基础知识
1.9 信号
头文件signal.h
提供一个singal
函数允许注册回调,信号产生时可以捕捉信号进行一些处理。
比如SIGINT
由shell中按Ctrl+C产生(中断键),如果程序没捕捉这个信号,则默认的行为是退出程序,如果捕捉了就可以根据需要自行处理这个信号了。
1.10 时间值
日历时间:用time_t
保存。
进程时间:用clock_t
保存,包括时钟时间(wall clock time)、用户CPU时间、系统CPU时间。
shell中获取进程时间:
1 | $ cd /usr/include |
1.11 系统调用和库函数
系统调用就是系统内核空间的入口点,UNIX中以C函数定义系统调用。
实现方法是,在标准C库中设置一个相同名字的函数,函数用系统所要求的技术调用相应的内核服务。
系统调用一般只是最小接口,标准C库中丰富的函数通过调用这些系统调用来实现。
<unistd.h>中定义大量系统调用封装(POSIX的)。
第二章 UNIX标准及实现
2.2 UNIX标准化
ISO C
由C程序设计语言国际标准工作组维护,它定义了C语言的语法语义、标准库。
POSIX
一开始由IEEE维护,Portable Operating System Interface,目的是提升应用程序在各种UNIX系统环境之间的可移植性,规定了POSIX兼容系统必须提供的各种服务,如pthread、异步IO等等。
SUS
,Single UNIX Specification,单一UNIX规范,POSIX.1的超集。
2.5 限制
<limits.h>中有各种限制,包括:
ISO C限制
:典型:CHAR_BIT
CHAR_MAX
UINT_MAX
,在给定的系统中不变。
POSIX限制
: _POSIX_XXXX_MAX
是POSIX要求的最小值。具体数值,有的在编译时可用(宏),有的需要运行时确定,通过sysconf
pathconf
fpathconf
之一获取。
2.8 基本系统数据类型
POSIX要求在<sys/types.h>
中定义,比如下面这些:
1 | clock_t 进程时间 |
第三章 文件I/O
POSIX定义在<unistd.h>
中,相关函数:
1 | open 打开文件 |
启用O_APPEND
标志,可以用lseek再read,但是write时会自动把offset设置到文件末尾再写。
3.10 文件共享
os允许一个文件同时被几个应用程序打开,用到了共享机制:各自持有文件状态、偏移量,但使用相同的v-node/i-node包含文件所有者、长度、指向实际硬盘中数据块位置等信息。
3.11 原子操作
- 打开文件时使用O_APPEND标志
- pread/pwrite,先lseek再操作的原子操作函数
- 使用open实现代替先判断文件是否存在后创建文件的原子操作
3.12 dup和dup2
dup
/dup2
用于复制现有的文件描述符(fd),后者能指定目标fd号,如果指定的fd号已经打开了文件,则先将它关闭。
3.14 fcntl
POSIX定义在<fcntl.h>
中,改变文件属性。
1 | int fcntl (int fd, int cmd, ...); |
参数cmd对应5种功能:
- 复制已有文件描述符(fd):
F_DUPFD
,F_DUPFD_CLOEXEC
- 获取/设置文件描述符标志:
F_GETFD
,F_SETFD
- 获取/设置文件状态标志:
F_GETFL
,F_SETFL
,其中包含访问方式(ACCMODE,比如只读、只写等等)、当前状态(O_SYNC
等待写完成、O_RSYNC
同步读和写) - 获取/设置异步I/O所有权:
F_GETOWN
,F_SETOWN
,设置和获取接受SIGIO
和SIGURG
信号的进程或进程组ID(用于实现异步IO) - 获取/设置记录锁:
F_GETLK
,F_SETLK
,F_SETLKW
dup2其实就是重定向。
todo:
- 不明白GETFD/SETFD用途,这里有篇文章https://www.cnblogs.com/xuyh/p/3273082.html
3.15 ioctl
SUS标准的扩展部分,I/O操作的杂物箱,用来进行很多杂项设备的操作,<sys/ioctl.h>
。
每个设备驱动程序都可以定义它自己专用的一组ioctl命令,比如终端设备。
3.16 /dev/fd
不是POSIX的组成部分,目的是方便shell使用。
打开/dev/fd/n
等效为复制描述符n:
1 | fd = open("/dev/fd/0", mode); |
注意Linux例外,因为实现成了文件的符号链接,所以可以更改打开属性。
第四章 文件和目录
相关函数在<sys/stat.h>
中:
1 | stat 根据路径获取属性 |
相关函数在<unistd.h>
中:
1 | access 以"实际UID/GID"测试文件访问权限 |
一组用于读目录的函数定义在<dirent.h>
中:
1 | opendir 打开目录 |
ISO C定义相关函数在<stdio.h>
中:
1 | remove 删除文件或目录 |
todo:
- 读p105实现ftw的源码,以及学习<ftw.h>,递归降序遍历目录
4.2 函数stat/fstat/fstatat/lstat
stat结构包含文件信息:
- 文件type、mode(permissions)
- i节点号
- 设备号
- 特殊文件设备号
- links数量
- 拥有者uid
- 拥有者gid
- 普通文件的size in bytes
- 最后访问、修改、文件状态改变的时间
- 最佳I/O block大小(blksize_t)
- 占用磁盘块数量
功能类比ls -ls
命令。
4.3 文件类型
- 普通文件
- 目录文件
- 块特殊文件(block special file):提供对设备(如disk)带缓冲的访问,每次访问以固定长度为单位进行
- 字符特殊文件(character special file): 提供对设备不带缓冲的访问,每次访问长度可变。
- FIFO:用于进程间通信,也叫命名管道(named pipe)
- 套接字(socket):进程间网络通信
- 符号链接(symbolic link): 指向另一个文件
系统中的设备,只可能是块特殊文件或字符特殊文件。
4.4 “设置用户ID”和”设置组ID”
1 | 实际UID/实际GID 我们实际是谁 |
进程维护着”设置用户ID”和”设置组ID”,stat结构的st_uid
、st_gid
的为文件拥有者的uid/gid,这两个值与进程对文件的操作权限有关。
4.10 粘着位
当一个目录设置了粘着位(S_ISVTX),需要满足其中一个条件才能重命名该目录下的文件:
- 拥有此文件
- 拥有此目录
- 是超级用户
4.14 文件系统
章节介绍UFS文件系统,一个柱面组中包含i节点数组、目录块和文件块;i节点是一些文件信息,指向文件块;目录块中包含文件名及i节点的编号,允许多个目录块指向同一i节点。
所谓硬链接就是建立一个指向i节点的目录块。
i节点会对硬链接计数,当计数为0的i节点会被清除。
4.15 link/linkat/unlink/unlinkat/remove
link可以创建硬连接,硬链接可能导致路径形成循环。
如果i节点的link计数为0,但文件被打开,它不会被立即清除,而是等文件关闭后,这一特性用于建立即使程序崩溃也能被删除的临时文件。
4.17 符号链接
硬链接要求文件处于同一文件系统中,而符号链接(软链接)没这要求。
以路径使用操作文件的函数时,得注意符号链接的情况,有的函数会处理符号链接本身,而不是指向的文件。
使用shell命令ln
可以创建符号链接。
4.19 文件的时间
st_atim
文件数据最后的访问时间,如read操作,ls -u
st_mtim
文件数据最后的修改时间,如write操作st_ctim
i节点状态最后更改时间,如chmod操作, ls -c
4.24 设备特殊文件
文件的st_dev
是文件系统的设备号。
字符特殊文件和块特殊文件才有st_rdev
,为实际设备的设备号。
使用<sys/type.h>
提供的major
和minor
宏获得主次设备号。
主设备号标识设备驱动程序、外设板;次设备号标识特定的子设备。比如一个磁盘驱动器上的几个文件系统,主设备号相同,但次设备号不同。
比如,在我的linux环境下,/dev/tty0
的dev=0/6,rdev=4/0,而/dev/tty1
的dev=0/6,rdev=4/1。
第五章 标准I/O库
标准I/O库是ISO C定义的,SUS对它进行了一些扩充。
定义在<stdio.h>
中的相关函数:
1 | setbuf 对指定fp设置或关闭缓冲 |
<stdlib.h>
中定义:
1 | mkdtemp 创建临时目录,成功返回目录名并修改输入的template字符串,不会自动删除目录 |
标准I/O效率不佳,因为一次操作涉及两次数据复制,替代:快速IO: fio
,sfio
,储存映射文件mmap函数
。
todo:
- 发生flush的条件没看明白
- 习题5.7答案没看
5.2 流和FILE对象
用标准I/O库打开一个文件时,实际上就是把一个流和文件关联了起来。
使用<wchar.h>
中提供的fwide
函数可以改变流的定向(stream’s orientation),它决定了流的宽度,用于国际字符集。
5.3 标准输入、标准输出、标准错误
<stdio.h>
中定义了stdin
stdout
stderr
文件指针。
5.4 缓冲
标准I/O提供缓冲的目的是让程序减少调用read/write原语的次数,3种类型:
- 全缓冲:填满缓冲区后才执行I/O操作
- 行缓冲:遇到换行符执行I/O操作
- 不缓冲
5.11 格式化I/O
格式化输出format string:
1 | %[flags].[fldwidth][precision][lenmodifier]convtype |
格式化输出format string:
1 | %[*][fldwidth][m][lenmodifier]convtype |
第十章 信号
信号是软件中断,相关头文件<signal.h>
,在Linux中信号编号被定义在<bits/signum.h>
中:
1 |
相关函数在<signal.h>
中定义:
1 | signal 监听某信号,为其指定handler(语义与实现有关,跨平台时应该自行用sigaction实现其功能) |
相关函数在<setjmp.h>
中定义:
1 | sigsetjmp 考虑到sig机制的setjmp |
相关函数在<stdlib.h>
中定义:
1 | abort 给自己发送一个SIGABRT信号 |
todo:
- 10.4介绍了之前的信号如何不可靠但是没说现在是怎么解决的
- 10.6中有longjmp和siglongjmp可重入性的讨论,没仔细看
- 10.7没看SIGCHLD语义问题因为Linux中没这问题
- 没看10.10中使用alarm实现sleep函数
- 没看sigaction的用法
- 没看abort实现
10.5 中断的系统调用
早期UNIX将系统调用分为低速和高速,高速不会阻塞,而阻塞期间如果来了信号,不同的UNIX实现可能有不同的表现:有的会重启动系统调用的工作,有的返回EINTR错误。这也是跨平台应用不推荐直接用signal函数的原因。(Linux默认会重新启动被中断的系统调用)
10.6 可重入函数
使用信号机制时应当注意其handler调用的函数的可重入性,可能会影响被打断的程序的执行,尤其注意:
- 已知使用静态数据结构
- 标准I/O很多实现不可重入
- malloc或free
- 涉及到errno应先保存后恢复
10.13 函数sigpending
使用sigprocmask可以屏蔽指定信号,而sigpending函数可以查看被阻塞的状态。
如果解除屏蔽,阻塞的信号会投递到进程中。