又一次看完APUE第三章,有了新的收获,总结一下第三章。
第三章讲的是POSIX的文件I/O,不带缓冲的I/O,相对于与第五章ISO C的标准I/O。
不带缓冲的I/O 与带缓冲的I/O
书上对于不带I/O的解释是每个I/O操作调用内核中一个系统调用。当然这个答案完全不能让读者弄明白到底是什么意思。
取自一网友的理解:
所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲,当用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才会把数据写入磁盘。因此所谓的不带缓冲的I/O是指进程不提供缓冲功能。每调用一次write或read函数,直接系统调用。
而带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。
因此,带缓冲的I/O在往磁盘写入相同的数据量时,会比不带缓冲的I/O调用系统调用的次数要少。
文件描述符
对于内核而言,所有的打开文件都通过文件描述符引用 (FileDescriptor),是一个非负整数,对于打开的文件内核会返回一个文件描述符,我们可以通过文件描述对打开的文件进行读写操作。
通常而言文件描述符 0,1,3 分别代表了 标准输入、标准输出、标准错误输出,关于对应的符号常亮被定义在<unistd.h>中。
每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表
(a) 文件描述符标志。
(b) 指向一个文件表项的指针。
内核为所有打开文件维持一张文件表。每个文件表项包含:
(a) 文件状态标志(读、写、增写、同步、非阻塞等 )。
(b) 当前文件位移量
(c) 指向该文件v节点表项的指针
假设在一个系统中存在很多进程(process),每个进程里面有一个文件 描述符表,大致结构如下:
struct Process{ //这是一个数组,文件描述符就是下标。 vector<FileDescriptorEntry> entries; }; struct FileDescriptorEntry{ bool close_on_exec; //调用exec是否关闭 bool other_flags; //其他标记 OpenedFileTable* ft_ptr; //指向全局的打开文件表表项 };
然后系统维护一个打开表文件表表项,在每个进程的文件描述符里面有对应的表项指针。大致结构如下:
struct OpenedFileTable{ int status; //状态标志,比如O_RDWR,O_APPEND,OSYNC等。 off_t offset; //当前偏移 vnode_t* vnode; //所指向的vnode };
在进程复制一个文件描述符并没有增加一个新的表项,而是指向相同的表项。然后vnode 就是文件系统对应的内容了,包括位置大小属性等等信息。
文件共享
关于文件共享的例子:看课后习题3.3。
1.如果两个独立的进程各自打开同一一个文件,我们会发先我们生成的两个文件描述符,打开盖文件的每个进程都得到了一个文件表项,但是对应的V节点表项却是同一个。这样的安排是得每个进程都有自己的对改文件有自己的偏移量。
2、当我们使用dup函数复制文件描述符和这种情况的区别就是fd指向文件表是同一个,因此我们可以使用dup来复制一个现存的文件描述符。
关于close-on-exec :
每个文件描述符都有一个close-on-exec标志。默认情况下,这个标志最后一位被设置为 0。
这个标志符的具体作用在于当开辟其他进程调用exec()族函数时,在调用exec函数之前为exec族函数释放对应的文件描述符。
I/O操作:
int open(const char *pathname ,int oflage ..../*mode_t mode*/)
常用mode :O_RDONLY O_WRONLY O_RDWR O_APPEND O_CREAT
int close(int filedes);
off_t lseek(int filedes ,off_t offset ,int whence);
SEEK_SET SEEK_CUR SEEK_END
size_t read (int filedes, void * buf ,size_t nbytes);
size_t write(int filedes, void * buf ,size_t nbytes);
int ioctl(int filedes,int request,... );