系统级I/O有哪些?
本章主要介绍UnixI/O、RIO、标准I/O
输入/输出(I/O) 是在主存和外部设备(如:磁盘驱动器、终端和网络)之间复制数据的过程。
输入操作是从I/O设备复制数据到内存,而输出操作是从主存复制数据到I/O设备。
在Linux系统中,是通过使用由内核提供的系统级Unix I/O函数来实现较高级别的I/O函数。
各系统级I/O之间的关系:
1.UnixI/O
一个Linux文件就是一个多个字节的序列,所有的I/O设备都被模型化为文件(一切皆文件),所有的输入和输出都被当作对相应的文件进行读和写来执行。这种允许Linux内核引出一个简单、低级的应用接口,将设备映射为文件的方式,叫做UnixI/O。这种方式使得所有的输入和输出都能以一种统一且一致的方式来执行。
文件
- 普通文件包含任意数据,应用程序常常要区分文本文件和二进制文件,文本文件是只含有ASCII或Unicode字符的普通文件,其他的为二进制文件。
- 目录是包含一组链接的文件,其每个链接都将一文件名映射到另一个文件,这个文件可能是另一个目录。".“是到该目录自身的链接,”. ."是到目录层次结构中父目录的链接。
- 套接字
打开和关闭文件
-
进程通过open函数来打开一个已存在的文件或者创建一个新文件。
int open(char *filename,int flags,mode_t mode); 返回:若成功则为新文件描述符;出错则为-1
函数将filename转换为文件描述符,且返回描述符数字。 -
flags参数指明了进程打算如何访问这个文件,分为只读、只写、可读可写。O_RDONLY、O_WRONLY、O_RDWR
flags参数也可以是一个或者更多位掩码的或
O_CREAT:若文件不存在,则创建它的一个截断的(空)文件。
O_TRUNC:若文件已存在,则截断它。
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。 -
mode参数指定了新文件的访问权限位。如图
-
进程通过调用close函数关闭一个打开的文件。
int close(int fd); 返回:若成功则为0,否则为-1
读写文件
- read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,返回:-1 错误 ,0 EOF,否则,返回实际传送字节数量。
- write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
- ssize_t和size_t的区别
在x86_64系统中,size_t被定义为unsigned long,而ssize_t被定义为long,read和write函数返回一个有符号的大小,因为出错时它必须返回-1. - 在某些情况下,read和write传送的字节比应用程序要求的要少,这些不足值不表示有错误,原因如下:
2. RIO
用RIO包健壮的读写
-
无缓冲的输入输出函数
调用rio_readn和rio_writen函数,应用程序可以在内存和文件之间直接传送数据
rio_readn函数从描述符fd的当前文件位置最多传送n个字节到内存位置usrbuf。rio_writen函数从位置usrbuf传送n个字节到描述符fd。Rio_read函数在遇到EOF时只能返回一个不足值。rio_writen函数决不会返回不足值。 -
带缓冲的输入函数
每打开一个描述符都会调用一次rio_readinitb函数,它将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来。rio_readlineb函数从文件rp读出一个文本行(包括结尾的换行符),将它复制到内存位置usrbuf,并且用NULL(零)字符来结束这个文本行。rio readlineb函数最多读maxlen-1个字节,余下的一个字符留给结尾的NULL字符。超过maxlen-1字节的文本行被截断,并用一个NULL字符结束。
rio_readnb函数从文件rp最多读n个字节到内存位置usrbuf。对同一描述符,对rio_readlineb和rio_readnb的调用可以任意交叉进行。然而,对这些带缓冲的函数的调用却不应和无缓冲的rio_readn函数交叉使用。
共享文件
内核用三个数据结构来表示打开的文件:
-
描述符表:每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
-
文件表:打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成包括当前的文件位置、引用计数以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应文件表表项中的引用计数。内核不会删除设个文件表表项,指导它的引用计数为零。
-
v-node表:同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。
fork后,子进程有一个父进程描述符的副本,父子进程共享相同的打开文件集合,因此共享相同的文件位置。
I/O重定向
Linux shell 提供I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。
I/O重定向的一种方式是使用dup2函数