UNIX环境高级编程 - 文件I/O - 原子操作、复制、修改文件描述符
原子操作
- 追加到一个文件
- 追加一个文件时,不能通过
lseek
到末尾然后write
。要用O_APPEND
选项打开文件,然后直接write
。- 通过
lseek
到末尾然后write
时,如果多个进程同时执行这两个操作,则会引起竞争条件 - 通过
O_APPEND
选项打开文件,然后直接write
时,内核每一次在写操作之前,都会将进程的当前偏移量设置到文件的末尾,于是就不需要执行lseek
定位操作
- 通过
pread/pwrite
可以执行原子性的定位读/定位写O_CREAT|O_EXCL
选项打开文件时,可以原子性的检查文件是否存在和创建文件这两个操作。
- 函数
pread
和pwrite
#include <unistd.h>
ssize_t pread(int fd,void*buf,size_t nbytes,off_t offset);
ssize_t pwrite(int fd,const void*buf,size_t nbytes,off_t offset);
- 参数:
fd
:打开的文件描述符buf
:读出数据存放的缓冲区/ 写到文件的数据的缓冲区nbytes
:预期读出/写入文件的字节数offset
:从文件指定偏移量开始执行read/write
- 返回:
- 成功:读到的字节数/已写的字节数
- 失败: -1
调用pread
相当于先调用lseek
再调用read
.但是调用pread
时,无法中断其定位和读操作,并且不更新当前文件偏移量;调用pwrite
相当于先调用lseek
再调用write
.但是调用pwrite
时,无法中断其定位和写操作,并且不更新当前文件偏移量
其他操作
dup
使用dup/dup2
复制一个现有的文件描述符:
#include <unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
- 参数:
fd
:被复制的文件描述符(已被打开)fd2
:指定的新的文件描述符(待生成)
- 返回值:
- 成功: 返回新的文件描述符
- 失败: 返回 -1
对于dup
函数,返回的新的文件描述符一定是当前可用的文件描述符中最小的数字。
对于dup2
函数:
- 如果
fd2
已经是被打开的文件描述符且不等于fd
,则先将其关闭,然后再打开(原子操作) - 如果
fd2
等于fd
,则直接返回fd2
(也等于fd
),而不作任何操作
任何情况下,这个返回的新的文明描述符与参数fd
共享同一个文件表项(因此文件状态标志以及文件偏移量都会共享)。任何情况下,这个返回的新的文明描述符的close-on-exec
标志总是被清除。
dup后的内核数据结构如下:
sync
UNIX操作系统在内核中设有缓冲区,大多数磁盘 I/O 都通过缓冲区进行。当用户程序想文件写入数据时,内核通常都首先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式称为延迟写。
- 当内核需要重用缓冲区来存方其他数据时,它会把所有延迟写的数据库写入磁盘
- 也可以调用下列函数来显式的将延迟写的数据库写入磁盘
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
-
参数:
fd
:指定的打开的文件描述符
-
返回值:
- 成功:返回 0
- 失败: 返回 -1
区别:
sync
:将所有修改过的块缓冲区排入写队列,然后返回,它并不等待时机写磁盘结束fsync
:只对由fd
指定的单个文件起作用,等待写磁盘操作结束才返回fdatasync
:只对由fd
指定的单个文件起作用,等待写磁盘操作结束才返回,但是它只影响文件的数据部分(fsync
会同步更新文件的属性)
update
守护进程会周期性的调用sync
函数。命令sync
也会调用sync
函数