原子操作是许多系统调用得以正确执行的必要条件
文件控制操作:fcntl()
点击(此处)折叠或打开
- #include <fcntl.h>
- int fcntl(int fd, int cmd, ...);
- /*return on success depends on cmd, or -1 on error*/
点击(此处)折叠或打开
- int flags, accessMode;
- flags = fcntl(fd, F_GETFL);
- if(flags == -1)
- errExit("fcntl");
- if(flags & O_SYNC)
- printf("writes are synchronized\n");
判断文件的访问模式有一点复杂,这是因为O_RDONLY(0), O_WRONLY(1), O_RDWR(2)这三个常量并不与打开文件状态标志中的单个比特位对应。因此,要判定访问模式,需要使用掩码O_ACCMODE与flag相与,将结果与三个常量进行对比
点击(此处)折叠或打开
- accessMode = flag&O_ACCMODE;
- if (accessMode == O_WRONLY || accessMode == O_RDWR)
- printf("file is writable\n");
文件描述符和打开文件之间的关系
由于目前Chinaunix上传图片老是失败,只能转为文字描述,待上传图片功能修正之后,这边会上传。针对文件,有三个结构是我们必须要记录在脑子里的,这三个结构可以理解为通过两个mapping阶段相互串联在一起的
第一个结构就是每个进程都有一个文件描述符表,它是一个数组,下标表示其文件描述符,内容包含文件描述符标志(当前只有close-on-exec)和一个文件指针;这个指针指向的就是第二个结构,也可以把指针指向这个动作称为第一个mapping
第二个结构就是操作系统为每个打开的文件维护了一个系统级的打开文件表,里面记录了关于某个打开文件的文件偏移量、状态标志等信息,最后是一个文件指针,指向第三个结构,也可以吧这个指针的指向这个动作称为第二个mapping
第三个结构就是一个系统级的i-node表,记录了文件的元信息
复制文件描述符
复制文件描述符是针对第一个结构到第二个结构之间的map做了一次调整,主要有一下三个函数点击(此处)折叠或打开
- #include <unistd.h>
- int dup(int oldfd);
- /*returns (new) file descriptor on success, or -1 on error*/
- #include <unistd.h>
- int dup2(int oldfd, int newfd);
- /*returns (new) file descriptor on success, or -1 on error*/
- #define _GNU_SOURCE
- #include <unistd.h>
- int dup3(int oldfd, int newfd, int flags);
- /*returns (new) file descriptor on success, or -1 on error*/
其中dup2和dup3均指定了新文件描述符fd,dup3比dup2多了一个flags参数(就是之前说过的close-on-exec标志)
以上操作均可以由fcntl()来完成,使用大于等于startfd的最小值来返回newfd
点击(此处)折叠或打开
- newfd = fcntl(oldfd, F_DUPFD[|F_DUPFD_CLOEXEC], startfd);
在文件特定偏移量处的I/O:pread()和pwrite(), 原子操作
这两个系统调用会完成与read()和write()相类似的工作,只是前两正会在offset参数所指定的位置进行文件I/O操作,而非始于文件当前偏移量处,且它们不会改变文件的当前偏移量点击(此处)折叠或打开
- #include <unistd.h>
- ssize_t pread(int fd, void *buf, size_t count, off_t offset);
- /*returns number of bytes read, 0 on EOF, or -1 on error*/
- ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
- /*returns number of bytes written, or -1 on error*/
scatter-gather I/O,readv()和writev(),原子操作
点击(此处)折叠或打开
- #include <sys/uio.h>
- struct iovec{
- void *iov_base;
- size_t iov_len;
- };
- ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
- /*returns number of bytes read, 0 on EOF, or -1 on error*/
- ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
- /*returns number of bytes written, or -1 on error*/
截断文件
点击(此处)折叠或打开
- #include <unistd.h>
- int truncate(const char *pathname, off_t length);
- int ftruncate(int fd, off_t length);
- /*both return 0 on success, or -1 on error*/
将文件大小设置为length,若文件长度大于参数length,丢弃超出部分;若小于length,在文件尾部添加一系列空字节或是一个文件空洞
非阻塞I/O
在打开文件时指定O_NONBLOCK标志,目的有二:1. 若open()未能立即打开文件,返回错误,而非陷入阻塞
2. 调用open()成功之后,后续的i/o操作也是非阻塞的
大文件I/O
要想获取LFS功能,推荐的做法是:在编译程序时,将宏_FILE_OFFSET_BITS的值定义为64,如点击(此处)折叠或打开
- $ cc -D_FILE_OFFSET_BITS=64 prog.c
点击(此处)折叠或打开
- #define _FILE_OFFSET_BITS 64
这样,在打印off_t类型的时候,需要将其强制转换为long long类型
点击(此处)折叠或打开
- printf("offset=%lld\n", (long long) offset)
/dev/fd目录
对于每一个进程,内核都提供有一个特殊的虚拟目录/dev/fd。该目录中包含/dev/fd/n形式的文件名,其中n是与进程中的打开文件描述符相对应的编号,打开/dev/fd目录中的一个文件等同于复制相应的文件描述符,所以,如下两行代码是等价的点击(此处)折叠或打开
- fd = open("/dev/fd/1", O_WRONLY);
- fd = dup(1);
创建临时文件
点击(此处)折叠或打开
- #include <stdlib.h>
- int mkstemp(char *template);
- /*returns file descriptor if OK, -1 on error*/
基于调用者提供的末班,mkstemp()函数生成一个唯一文件名并打开该文件,返回一个可用于I/O调用的文件描述符,该模板参数采用路径名形式,其中最后6个字符必须为xxxxxx,这6个字符将被替换,以保证文件名的唯一性,且秀干后的参数将通过template参数传回去。
文件拥有者对mkstemp()函数建立的文件拥有读写权限(其他用户没有任何操作权限),且打开文件时使用了O_EXCL标志,以保证调用者以独占的方式访问文件
点击(此处)折叠或打开
- #include <stdio.h>
- char *tmpnam(char *ptr);
- /*returns: pointer to unique pathname*/
- FILE *tmpfile(void);
- /*returns file pointer on success, or NULL on error*/