原子操作:将一次系统调用所需要的各个动作作为不可中断的操作,一次性加以执行。
原子操作是许多系统调用得以正确执行的必要条件。
5.1 原子操作与竞争条件
竞争状态:操作共享资源的两个进程(或线程),其结果取决于一个无法预期的顺序,这些进程(或线程)获得CPU使用权的先后相对顺序。
个人感觉就是线程安全之类的东西。
试图以独占方式打开文件的错误代码:
当两个进程在一个多CPU系统上同时运行时,可能出现以下情况:
看了半天没看懂他具体想表达什么,大概就是两个进程同时执行,都以为自己以独占的方式创建了文件。于是违背了原子操作原则。
append时,加入O_APPEND标志可以将文件偏移量的移动和数据写操作纳入同一原子操作。
5.2 文件控制操作 fcntl()
fcntl()系统调用对一个打开的文件描述符执行一系列控制操作。
fcntl函数的用途之一是针对一个打开的文件,获取或修改其访问模式和状态标志。
先通过设置cmd参数为F_GETFL获取设置,再通过F_SETFL进行设置。
访问模式的判定比较复杂,需要使用掩码O_ACCMODE与flag相与,再将结果与三个常量比对:
accessMode = flags & O_ACCMODE; \\flags通过F_GETFL得到 if (accessMode == O_WRONLY || accessMode == O_RDWR) printf("file is writeable\n")
访问模式应该是不能设置的吧==
通过F_SETFL设置状态标志
允许修改的标志有:O_APPEND, O_NONBLOCK, O_NOATIME, O_ASYNC, O_DIREST.
使用fcntl修改文件状态标志,尤其适用于如下场景:
目前只在socket编程时用过fcntl=-=
5.4 文件描述符和打开文件之间的关系
文件描述符和文件之间的关系可以是多对一的,多个文件描述符指向同一打开文件,这些文件描述符可在相同或不同的进程中打开。
内核维护了三种数据结构:
- 进程级的文件描述符
- 系统级的打开文件表
- 文件系统的i-node表
要点
-
两个不同的文件描述符,若指向同一打开的文件句柄,将共享同一文件偏移量。
-
文件描述符标志,为进程和文件描述符所私有,对标志的修改不会影响到其他文件描述符。
5.5 复制文件描述符
复制一个打开的文件描述符,返回一个新描述符,二者都指向同一打开的文件句柄
当复制需要调用者指定新文件描述符时,使用dup2:
fcntl()也可以复制文件描述符,且更加灵活:
cpp newfd = fcntl(oldfd, F_DUPFD, startfd);
dup3只增加了一个功能,即可以设置标志O_CLOEXEC.
5.6 特定偏移量处的I/O
pread和pwrite系统调用可以避免进程之间出现进程状态。
5.7分散输入和集中输出
一个文件描述符对应多个缓冲区
struct iovec{
void *iov_base; //缓冲区的起始地址
size_t iov_len; //缓冲区中数据的大小
};
分散输入:
原子性是readv()的重要特征。
readv将数据散置于iov指定的缓冲区中。(依次填满,从iov[0]开始)
集中输出
writev同样具有原子性
将iov中数据按顺序拼接起来,连续写入fd对应的文件中。
带偏移量的分散输入和集中输出
5.8 截断文件
将文件长度调整为length(通过截断或者在尾部添加空字节)
5.9 非阻塞I/O
设置O_NONBLOCK标志
内核缓冲区保证了普通文件I/O不会陷入阻塞,故打开普通文件时一般忽略O_NONBLOCK标志。
后面应该会对这个有比较详细的讲解
5.10 大文件I/O 略
5.11 /dev/fd 目录
对于每个进程,内核都提供有一个特殊的虚拟目录/dev/fd,目录中包含了进程中打开的文件描述符。
fd = open("/dev/fd/1", O_WRONLY);
对应标准输出。
5.12 创建临时文件
GUN C提供了一系列库函数用于创建临时文件,仅在程序运行期间使用,
template最后6个字符必须为XXXXXX,这6个字符将被替换,修改后的字符串会被返回。
文件拥有者对打开的文件拥有读写权限。
tmpfile返回一个文件流,