lseek
之前在地址空间一文中提及 mmap 可以映射文件到内存地址空间,并通过指针实现随机访问。对于使用 read/write 的情形,系统调用 lseek 同样可以达成这个目的。
|
|
whence 用来控制 offset 的工作方式:
SEEK_SET:偏移量从文件头开始计算。例如 lseek(fd,10,SEEK_SET) 将偏移量设置为第 10 个字节。SEEK_CUR:在当前偏移位置继续往后偏移。比如已经通过 read 读取了 10 字节,lseek(fd,10,SEEK_CUR) 将偏移20字节。SEEK_END:从文件结束开始偏移(支持负值)。lseek(fd,-10,SEEK_END) 将从结尾向前偏移 10 字节。
fsync
使用 write 写入文件时,修改的内容不一定会立即写入,具体时间由操作系统控制。但对于像事务型数据库这类存在日志、事务概念的程序,它们要求强制写入磁盘来保障持久性。
因此,UNXI 中提供 fsync(fd) 系统调用来将所有未写入的数据写入磁盘。
文件描述符
文件描述符类似指针,但不直接指向具体文件,而是指向一个中间内容。
单独考虑 fork 和文件描述符,我们知道 fork 后两个进程都将指向相同的文件。但我们知道通过 read/write 对文件进行操作时会维护一个偏移量,一个问题是 fork 之后两个进程的文件描述符偏移量也会同步吗?
文件描述符的本质
文件描述符类似指针,但不是指向文件本身,而是指向操作系统维护的一个内核文件对象,这个内核文件对象维护了实际文件的 inode 以及偏移量等信息。当 fork 后文件描述符也被写时复制,但具体指向的内核对象并不会被写时复制,也就是说这是一个“浅拷贝”,所以一个 fork 后进程会共享同一个内核文件对象。
偏移量共享:父子进程操作同一文件时偏移量同步更新。
graph TD
classDef user fill:#B9D9EB,stroke:#333;
classDef kernel fill:#F4D03F,stroke:#333;
classDef physical fill:#ABEBC6,stroke:#333;
subgraph 用户层
U1[进程1 文件描述符表 stdin, stdout, stderr, fileA]
U2[进程2 文件描述符表 stdin, stdout, stderr, fileB]
end
subgraph 内核层
K1[[文件表]]
K2[[v节点表]]
end
subgraph 物理层
P1[(磁盘文件A)]
P2[(磁盘文件B)]
P3[(终端设备)]
P4[(网络套接字)]
end
U1 -->|fd3 指向| K1
U2 -->|fd3 指向| K1
K1 -->|关联| K2
K2 -->|映射| P1
K2 -->|映射| P2
K2 -->|映射| P3
K2 -->|映射| P4
style U1 fill:#B9D9EB
style U2 fill:#B9D9EB
style K1 fill:#F4D03F
style K2 fill:#F4D03F
style P1 fill:#ABEBC6
style P2 fill:#ABEBC6
style P3 fill:#ABEBC6
style P4 fill:#ABEBC6
测试代码:
|
|
输出:
|
|
文件描述符带来的风险
文件描述符是内核为进程维护的一个内核文件对象的映射,那么如果 exec 后新的程序会继承文件描述符吗?
测试代码:
|
|
|
|
执行结果:
|
|
显然,exec 后的新程序会继承之前打开的文件描述符,比如这里的 b 就成功读取了 a 打开的文件。这毫无疑问是一个风险,毕竟用一个高权限进程拉起其他程序是很常见的操作,而如果不及时关闭文件描述符,新程序将继承一些隐私文件的访问权限。
为此,引入了 close-on-exec 标志(FD_CLOEXEC)确保文件描述符在 exec 时会自动关闭。
管道
管道是 linux 中进程间通讯的基本机制。管道的本质是单向字节流,并且采用先进先出的方式工作。
管道分为两种:
- 匿名管道:调用 pipe 直接创建,用于具有父子关系的进程之间进行通信。
- 命名管道:mkfifo 创建一个持续存在于文件系统中的管道。由于像文件一样,因此支持任意进程访问并进行通信。
| 特性 | 匿名管道 (Anonymous Pipe) | 命名管道 (Named Pipe/FIFO) |
|---|---|---|
| 创建方式 | pipe()系统调用 |
mkfifo()系统调用或mkfifo命令 |
| 生命周期 | 随进程终止自动销毁 | 持久存在于文件系统直到显式删除 |
| 进程关系要求 | 必须是有亲缘关系的进程(如父子进程) | 支持任意进程间通信 |
| 访问方式 | 通过文件描述符访问 | 通过文件路径访问 |
pipe
|
|
参数说明
pipefd[2]:用于返回管道的两个文件描述符:pipefd[0]:管道的读端。pipefd[1]:管道的写端。
flags(仅pipe2()):用于设置管道行为的标志位,可以是以下值的按位或:O_CLOEXEC:设置文件描述符的 close-on-exec 标志。O_DIRECT:启用“数据包模式”,每次写操作被视为一个独立的数据包。O_NONBLOCK:设置非阻塞模式。O_NOTIFICATION_PIPE(Linux 5.8+):启用通知机制,内核将事件消息写入管道。
在处理 pipefd 返回的两个文件描述符时,要手动关闭不需要的端口。
|
|