(一)Unix IO概述
所有的IO设备,如网络、磁盘和终端,都被模型化为文件,(一个Unix文件就是一个m个字节的序列),而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Unix内核引出一个简单、低级的应用接口,称为Unix IO:
- 打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个IO设备。内核返回一个小的非负整数,叫做描述符。内核记录有关这个打开文件的所有信息,应用程序只需要记住这个描述符。(Unix外壳创建的每个进程开始时都有三个打开的文件:标准输入STDIN_FILENO=0;标准输出STDOUT_FILENO=1;标准错误STDERR_FILENO=2)。
- 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始化为0。应用程序可以通过seek操作,显式地设置文件的当前位置k。
- 读写文件。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序可以检测此条件,而文件结尾处并没有“EOF符号”。
- 关闭文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的存储器资源。
(二·一)打开和关闭文件
int open(char *filename, int flags, mode_t mode);
int close(int fd);
- 对于open来说,返回的描述符总是在进程中当前没有打开的最小描述符。
- flags指明进程如何访问文件:O_RDONLY:只读;O_WRONLY:只写;O_RDWR:读写
- flags也可以提供一个其他掩码,为写提供一些提示:,O_CREAT:如果文件不存在,创建空文件;O_TRUNC:如果文件存在,清空;o_APPEND:添加
- mode指定新文件的访问权限;每个进程都有一个umask,可以通过umask函数设置;当open一个新文件时,其访问权限为:mode & ~umask
- 关闭一个已经关闭的描述符会出错。
(二·二)读写文件
- read函数从描述符fd的当前位置拷贝最多n个字节到存储器位置buf,返回-1表示一个错误;返回0表示EOF;否则,返回实际传送值。
- write函数从存储器位置buf拷贝至多n个字节到fd的当前位置。
- 可以调用lseek函数,显式地修改当前文件位置
我们注意到read和write当中,传送的字节都是至多为n,那么什么时候会小于n呢?也就是不足值(short count)出现的情况:
- 读时遇到EOF。若文件20个字节,n=50;那么第一个read返回不足值20;第二个read返回0发出EOF信号;
- 从终端读文本行。如果fd与终端相关联(如键盘和显示器),那么每个read函数将一次传送一个文本行,返回的不足值等于文本行大小;
- 读和写网络套接字。内部缓冲约束和较长的网络延迟会引起read和write返回不足值。如果想创建健壮的web服务器应用,必须反复调用read和write处理不足值,直到所有需要的字节都传送完毕。
(三)RIO包健壮地读写
为自动处理不足值情况,我们构造一个健壮的IO函数。
RIO函数分两类:
无缓冲的输入输出函数:对网络中读写二进制数据尤其有用;
带缓冲的输入函数:可以高效地从文件中读取文本行和二进制数据。其中带缓冲的RIO输入函数是线程安全的。
假设我们要编写一个程序来计算文本文件中文件行的数量该如何实现?
一种方法是read函数一次读取一个字节,检查是否有换行符。缺点是:效率不高,因为每读一个字节就要陷入一次内核。
更好地方法是调用一个包装函数(rio_readlineb),从一个内部读缓冲区拷贝一个文本行,当缓冲区为空时,再自动调用read重新填满缓冲区。
对于既然文本行也包含二进制数据的文件,我们也提供一个rio_readn带缓冲区版本:rio_readnb。
【总结:带缓冲区的rio函数是为了效率;不带缓冲区的roio_readn和rio_writen是为了健壮性】
(四)读取文件的元数据
(五)内核是如何打开文件的?
(六)