前面曾经说了APUE第三章前面的几个系统调用,它们都是unbuffered I/O,下面来看看第三章其它的内容。
首先是3.9节说的I/O efficiency(I/O效率)问题,前面提到在unbuffered I/O中BUFFSIZE的选择对文件I/O的效率有很大影响,书中图3-6用不同的BUFFSIZE值产生的时间来说明对效率的影响,磁盘块长度的值由st_blksize表示,关于buffered I/O和unbuffered I/O效率的具体比较,在以后的章节中会有说明,我们就不细看啦,这里只提几个函数sync, fsync, fdatasync,功能差不多。因为UNIX内核中设有缓冲区高速缓存或页高速缓存,所以写的时候是先写进缓存在最后统一写入磁盘,这叫delayed write延迟写,操作系统里经常提到这个概念,这里边我们重点关注这几个函数的功能。
当然它们的功能类似,sync被一个称谓update的daemon周期性(一般30s)的调用,定期flush内核中的block buffers,将所有修改过的block buffers排入写队列,不等待写入磁盘直接返回。就是这样。
在这次笔记中主要说一下文件共享的东西。UNIX系统支持不同进程间共享,直接看图:
内核使用三种数据结构表示打开的文件:process table entry, file table entry, v-node table entry(进程表,文件表和v-node表),层层向下。图中表示很清晰,每个进程中维护一个file descriptor表项,表项中有fd flag(F_DUPFD、F_GETFD、F_SETFD、F_DUPFD_CLOEXEC等),指向下一层的指针就不说啦;文件表中记录file status flag(O_RDONLY等)和当前偏移,当然还有一个指向下一层的指针;v-node表中则是文件类型、文件操作指针等具体的东西,当然大多数还有索引i-node,这个就不细说啦。
既然知道了层次结构,那就来看看每层(或者说是每个表)上的操作函数吧:
1.首先是process table entry中,duplicate,也就是复制。函数定义是这样的:
#include <unistd.h> int dup(int fd); int dup2(int fd, int fd2); //Both return: new file descriptor if OK, −1 on error
我们经常喜欢复制一个文件,如果进程开启时只有fd 0 1 2三个描述符是打开的,那么我们执行语句
newfd = dup(1);//把fd=1的文件复制了;
那么这条语句执行时,返回可用文件描述符中最小的那一个返回(因为此时3是最小可用的,所以返回3),这样,fd=1和fd=3的指针都指向同一file table,如下图所示:
看!很简单吧,其它的函数用到再查吧,我们接下来看另外一个函数fcntl,它的功能当然是file control相关啦:manipulate file descriptors!下面是声明:
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );具体的cmd有哪些,网上到处都是,书写也写得很详细,我也不打出来了,只举书上图3-11示例程序中的一句代码:
<pre name="code" class="cpp">if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) err_sys("fcntl error for fd %d", atoi(argv[1])); switch (val & O_ACCMODE) { case O_RDONLY: printf("read only"); break; case O_WRONLY: printf("write only"); break; case O_RDWR: printf("read write"); break; default: err_dump("unknown access mode"); }这里面fcntl的用法是改变fd=argv[1]的文件的fd flag为O_RDONLY,历史原因的影响下O_RDONLY=0, O_WRONLY=1, O_RDWR=2,这三个已经固定下来了,当然还有O_EXEC(open for execute only)和O_SEARCH(open directory for search only),这五个值是互异的。在得到fcntl的返回值后,如程序所示,val必须与O_ACCMODE掩码与运算后才能得到相应的值,这点要稍微注意一下。好了,这就是fcntl的简单介绍。
还有一个函数ioctl用在终端设备上比较多,在这里我们先不讨论,等到用到的时候再说。
2.关于file table entry上的函数。
这里首先说一下atomic operations(原子操作),举个例子,两个独立进程对同一文件写很容易发生错误,因为current file offset和多个写操作之间有时候会冲突,这就需要原子操作保证涉及一个独立操作的多个函数对某一进程是原子性的,这样才能保证它操作文件的时候不被别的文件干扰。具体示例见3.11节,关于lseek(只改变current file offset)和read的分析,解决方案主要涉及下面两个函数,这里不再细说:
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset); //Returns: number of bytes read, 0 if end of file, −1 on error ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset); //Returns: number of bytes written if OK, −1 on error
——————————————————————————————————————————————————
好啦,第三章结束,这一章主要是文件I/O相关的知识,也就那几个函数嘛,so easy