第九周学习总结
第十章 系统级I/O
输入/输出(I/O)是在主存和外部设备(如磁盘驱动器、终端和网络)之间拷贝数据的过程。
- ANSI C提供了标准I/O库——printf和scanf这样带缓冲区的I/O函数。
- C++中有重载操作符>>(输出)和<<(输入)提供了类似的功能。
- Unix系统中,是通过使用由内核提供的系统级Unix I/O函数来实现的。
学习系统级I/O,也就是Unix I/O的原因是:
- 了解Unix I/O将帮助你理解其他的系统概念
- 有时你除了使用Unix I/O以外别无选择
10.1 Unix I/O
所有的I/O设备,如磁盘,网络,终端,都被模型化为文件。
- 打开文件。一个应用程序通过要求内核打开相应的文件。文件打开后,应用程序记录描述符,内核记录所有和文件相关的信息。
- 每个进程开始的时候都有3个打开的文件:标准输入0,标准输出1,标准错误2。
- 改变当前的文件位置。对于每个打开的文件,内核记录其所有相关的信息,其中一个是文件位置k,初始为0。从文件开头起始的字节偏移量。
- 读写文件。一个读操作就是从文件拷贝n>0个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。给定的一个大小为m字节的文件,当k>=m时,执行读操作会触发一个称为EOF的条件,应用程序能检测到这个条件。写操作就是从存储器拷贝n>0个字节到一个文件,从当前文件k开始,然后更新k。
- 关闭文件——应用程序通知内核关闭文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。
10.2 打开和关闭文件
进程通过open函数来打开一个已存在的文件或者创建一个新文件的。
int open(char *filename, int flags, int mode);
flags参数指明了进程打算如何访问这个文件:
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWR:可读可写
- O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
- O_TRUNC:如果文件已经存在,就截断它。
- O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。
当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置为mode & ~umask。
进程通过调用close关闭一个打开的文件。
int close(int fd);
10.3 读和写文件
应用程序通过分别调用read和write函数来执行输入和输出。
ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void *buf, size_t n);
read函数调用返回有三种情况:
- 返回值-1表示一个错误。
- 返回值0表示EOF。
- 返回值表示的是实际传送的字节数。
如果打开文件是与终端相关联的,那么每个read函数将一次传送一个文本行,返回的不足值对于文本行的大小。
在某些情况下,read和write传送的字节比应用程序要求的要少。这些不足值不表示有错误。出现这种情况的原因如下:
- 读时遇到EOF。
- 从终端读文本行。
- 读和写网络套接字。
10.4 用RIO包健壮地读写
Robust I/O,RIO提供了两类不同的函数:
- 无缓冲的输入输出函数。这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。它们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
- 带缓冲的输入函数。文件的内容缓存在应用级缓冲区内。
RIO的无缓冲的输入输出函数:
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
如果rio_readn和rio_writen函数被一个从应用信号处理程序的返回中断,那么每个函数都会手动地重启read或write。
RIO的带缓冲的输入函数:
rio_readnb
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
RIO读程序的核心是rio_read的函数。rio_read函数时Unix read函数带缓冲的版本。
10.5 读取文件元数据
文件的元数据,就是关于文件的信息。
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
讨论Web服务器时,会需要stat数据结构中的st_mode和st_size成员。st_mode则编码了文件访问许可位和文件类型。
Unix提供的宏指令根据st_mode成员来确定文件的类型。
- S_ISREG() 普通文件
- S_ISDIR() 目录文件
- S_ISSOCK() 套接字
10.6 共享文件
内核用三个相关的数据结构来表示打开的文件:
- 描述符表。是每个进程都有的。每个条目是由文件描述符索引的,条目值指向文件表中的一项。
- 文件表。是所有进程共享的。条目包含一个文件的当前位置,引用计数,以及一个指向v-node表中对应表项的指针。
- v-node表。是所有进程共享的。包含了stat结构中的大多数信息,包括st_mode和st_size成员。
一次open,将在文件表中建立一个新的条目,将在描述符表中建立一个新的描述符指向这个文件表中的条目。
如果已open之后,进程fork了,那么父子进程都拥有相同的描述符内容,指向的也会是文件表中相同的条目,也就是共享了该文件的当前位置。
两次open同一个文件,虽然是同一个文件,但是有两个描述符条目,指向的也是文件表中的两个条目,虽然文件表中的两个条目指向v-node表中的一个条目,但是,文件表两个条目是关键。
10.7 I/O重定向
I/O重定向的一个方式是使用dup2函数。
int dup2(int oldfd, int newfd);
dup2函数拷贝描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在拷贝oldfd之前关闭newfd。
10.8 标准I/O
ANSI C定义了一组高级输入输出函数,称为标准I/O库。
标准I/O库将一个打开的文件模型化为一个流。对于程序员而言,一个流就是一个指向FILE类型的结构的指针。
类型为FILE的流是对文件描述符和流缓冲区的抽象。流缓冲区的目的和RIO读缓冲区的一样:就是使开销较高的Unix I/O系统调用的数量尽可能的小。
参考内容:
课本,百度百科。