计算机系统——系统级I/O

一、Unix I/O
  一个Linux文件就是一个字节序列,所有的I/O设备都被模型化为文件,所有的输入和输出都被当作对响应文件的读和写。这种将设备优雅的映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O

1.1 文件
  每个Linux文件都有一个类型来表明其在系统中的角色:
  -普通文件,包含任何数据,应用程序常常区分文本文件,仅包含ASCII或Unicode字符;和二进制文件,是除了文本文件之外的所有文件。对于内核而言,两者没有区别;
  -目录,包含一组链接的文件,每个链接都将一个文件名映射到一个文件;
  -套接字,用来与另一个进程进行跨网络通信;
  -其他多种文件类型。
Linux内核将所有文件组织成目录层次结构,由根目录确定,系统中的每个文件都是根目录的直接或间接的后代。

1.2 文件访问

  进程通过调用open()函数来打开一个已存在的文件或者创建一个新文件,形如

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(char *filename, int flags, mode_t);

其在成功时将filename转换为一个文件描述符,并且返回描述符值。描述符是一个小的非负整数,在后续对此文件的所有操作中标识了这个文件,返回的描述符总是进程中没有打开的最小描述符。其在失败时返回-1。
  进程通过调用close()函数来关闭一个打开的文件,形如

#include <unistd.h>

int close(int fd);

其在成功时返回0,在出错时返回-1。关闭一个已经关闭的描述符就会出错。

  应用程序通过分别调用read()和write()函数来执行输入和输出,形如

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void *buf, size_t n);

  read()从描述符fd的文件复制至多n个字节到内存位置buf,在返回-1时表示错误,在0时表示文件结束符【End Of File,EOF】,否则,返回值表示实际传送的字节数量。
  write()从内存位置buf复制至多n个字节到描述符fd的文件,在成功时返回实际传送的字节数量,否则返回-1。
  在某些情况下,read()和write()传送的字节比要求的要少,称为不足值,其不表示有错误,可能的情况为:
  -EOF,从当前文件位置到结束少于要求的字节;
  -终端读文本行,read以文本行为单位传送,返回的不足值为文本行的大小;
  -网络套接字,由于缓冲约束、网络延迟等可能造成不足值。
要创建健壮的网络应用,必须反复的调用read()和write()处理不足值,直到所有需要的字节都传送完毕。

  通过调用lseek()函数,应用程序可以显式的修改当前文件的位置。

1.3 RIO文件访问
  RIO【Robust I/O】是一个封装体,会自动处理不足值,提供了方便、健壮和高效的I/O。其提供了两种不同的函数:
  -无缓冲的输入输出函数,直接在内存和文件之间传送数据;
  -带缓冲的输入函数,允许高效的从文件中读取文本行和二进制数据,这些内容缓存在应用及缓冲区中。
该封装体由《深入了解计算机系统》提供,位于http://csapp.cs.cmu.edu/3e/code.html的src/csapp.c与include/csapp.h。

  通过调用rio_readn()和rio_writen()函数,应用程序可以在内存和文件之间直接传送数据,其接口形如

#include "csapp.h"

ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);

其中,rio_readn()在遇到EOF时返回不足值,而rio_writen()决不返回不足值。对于同一个描述符,可以任意交错的调用二者。

  如果需要一个程序计算文本文件中文本行的数量,可以通过read一次一个字节的从文件传送到用户内存,检查换行符,但效率不高。一种更好的方法时调用rio_readlineb(),其从内存读缓冲区复制文本行;或者调用rio_readnb,从缓冲区复制文本行和二进制数据,其接口形如

#include "csapp.h"

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);

其会在成功时返回读的字节数,或在EOF时返回0,以及在出错时返回-1。rio_readinitb()用于将描述符与地址rp处的读缓冲区联系起来。
  rio_readlineb()会从读缓冲区rp读出下一个包括换行符的文本行,使用NULL结束文本并复制到内存位置usrbuf。其最多读取maxlen-1个字节,剩下的字节留给结尾的NULL。函数会在读取到最大字节、EOF或换行符时停止。
  rio_readnb()会从读缓冲区rp读最多n个字节到内存位置usrbuf。rio_readlineb()可以和rio_readnb()交叉使用,但是要注意的是,有缓存读和无缓存读不应交叉使用。

1.4 标准I/O
  C定义了标准I/O库,即libc.so,提供了Unix标准I/O的较高级别的替代。
  标准I/O库将打开的文件模型化为,每个C程序开始时都会有三个打开的流,被定义在stdio.h中。


二、文件信息

2.1 文件元数据
  文件的元数据关于文件的信息,每个文件的元数据由内核保存。
  应用程序能够通过调用stat()和fstat()函数,检索到,也成为。其接口如下

#include <unistd.h>
#include <sys/stat.h>

int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);

其中,stat是关于元数据的数据结构。其在成功时返回0,出错时返回-1。stat()以一个文件名作为输入,并填写stat数据结构的成员;fstat()则以文件描述符作为输入。

2.2 共享文件
  内核使用三个相关的数据结构来表示打开的文件:
  -描述符表,每个进程都有独立的描述符表,表项由进程打开的文件描述符来索引,每个描述符表项指向文件表的一个表项;
  -文件表,打开文件的集合是一张文件表来表示的,所有进程共享文件表。文件表的表项包括文件位置,引用计数,即当前指向该表项的描述符表项数,以及指向v-node表的指针。当文件表项的引用计数为0时,该文件不会被任何进程打开,从而内核会删除这个文件表项;
  -v-node,包含stat数据结构的部分信息,由所有进程共享。
那么,当一个文件被一个进程打开时,进程的描述符表中该文件的描述符将会索引到一个表项,其指向了文件表的一个表项,该表项包含了打开文件的位置,并指向了v-node表的一个表项,该表项描述了文件的元数据。
  多个描述符可以通过不同的文件表项引用同一个文件。例如一个进程调用了两次open(),其会生成两个文件描述符,指向两个文件表表项,但最终指向同一个v-node表项。
  父子进程在fork()调用后会共享文件,父子各自进程的描述符表的表项会指向相同的文件表表项,最终指向同一个v-node表项。

2.3 重定向
  Linux shell提供了I/O重定向操作符,允许用户将文件和标准输入输出联系起来,例如

linux> ls > foo.txt

使得shell加载和执行ls程序,将标准输出重定向到文件foo.txt。
  重定向可以使用dup2()实现,形如

#include <unistd.h>

int dup2(int oldfd, int newfd);

其会复制描述符表项oldfd到表项newfd,并覆盖newfd以前的内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值