unbuffered i/o 与 standard i/o 区别

这是一篇讲C标准I/O库函数与Unbuffered I/O函数的文章,因为最近在看APUE (2rd)为了对这一章节理解的更透彻,所以转了一一篇文章,自己稍作整理,对这方面有兴趣深究的朋友,可以跟岾!
转载于: http://learn.akae.cn/media/ch28s02.html

      看看C标准I/O库函数是如何用系统调用实现的。


fopen(3)

调用open(2)打开指定的文件,返回一个文件描述符(就是一个int类型的编号),分配一个FILE结构体,其中包含该文件的描述符、I/O缓冲区和当前读写位置等信息,返回这个FILE结构体的地址。


fgetc(3)

通过传入的FILE *参数找到该文件的描述符、I/O缓冲区和当前读写位置,判断能否从I/O缓冲区中读到下一个字符,如果能读到就直接返回该字符,否则调用read(2),把文件描述符传进去,让内核读取该文件的数据到I/O缓冲区,然后返回下一个字符。注意,对于C标准I/O库来说,打开的文件由FILE *指针标识,而对于内核来说,打开的文件由文件描述符标识,文件描述符从open系统调用获得,在使用read、write、close系统调用时都需要传文件描述符。


fputc(3)

判断该文件的I/O缓冲区是否有空间再存放一个字符,如果有空间则直接保存在I/O缓冲区中并返回,如果I/O缓冲区已满就调用write(2),让内核把I/O缓冲区的内容写回文件。


fclose(3)

如果I/O缓冲区中还有数据没写回文件,就调用write(2)写回文件,然后调用close(2)关闭文件,释放FILE结构体和I/O缓冲区。


以写文件为例,C标准I/O库函数(printf(3)、putchar(3)、fputs(3))与系统调用write(2)的关系如下图所示。

图 28.1. 库函数与系统调用的层次关系





open、read、write、close等系统函数称为无缓冲I/O(Unbuffered I/O)函数,因为它们位于C标准库的I/O缓冲区的底层[36]。用户程序在读写文件时既可以调用C标准I/O库函数,也可以直接调用底层的Unbuffered I/O函数,那么用哪一组函数好呢?

  • 用Unbuffered I/O函数每次读写都要进内核,调一个系统调用比调一个用户空间的函数要慢很多,所以在用户空间开辟I/O缓冲区还是必要的,用C标准I/O库函数就比较方便,省去了自己管理I/O缓冲区的麻烦。

  • 用C标准I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用fflush(3)。

  • 我们知道UNIX的传统是Everything is a file,I/O函数不仅用于读写常规文件,也用于读写设备,比如终端或网络设备。在读写设备时通常是不希望有缓冲的,例如向代表网络设备的文件写数据就是希望数据通过网络设备发送出去,而不希望只写到缓冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,所以网络编程通常直接调用Unbuffered I/O函数。


C标准库函数是C标准的一部分,而Unbuffered I/O函数是UNIX标准的一部分,在所有支持C语言的平台上应该都可以用C标准库函数(除了有些平台的C编译器没有完全符合C标准之外),而只有在UNIX平台上才能使用Unbuffered I/O函数,所以C标准I/O库函数在头文件stdio.h中声明,而read、write等函数在头文件unistd.h中声明。


       在支持C语言的非UNIX操作系统上,标准I/O库的底层可能由另外一组系统函数支持,例如Windows系统的底层是Win32 API,其中读写文件的系统函数是ReadFile、WriteFile。


       现在该说说文件描述符了。每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块(PCB,Process Control Block)。task_struct中有一个指针指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针,如下图所示。

图 28.2. 文件描述符表





至于已打开的文件在内核中用什么结构体表示,我们将在下一章详细介绍,目前我们在画图时用一个圈表示。用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor),用int型变量保存。当调用open打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read或write,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。

我们知道,程序启动时会自动打开三个文件:标准输入、标准输出和标准错误输出。在C标准库中分别用FILE *指针stdin、stdout和stderr表示。这三个文件的描述符分别是0、1、2,保存在相应的FILE结构体中。头文件unistd.h中有如下的宏定义来表示这三个文件描述符:

#define STDIN_FILENO 0#define STDOUT_FILENO 1#define STDERR_FILENO 2

事实上Unbuffered I/O这个名词是有些误导的,虽然write系统调用位于C标准库I/O缓冲区的底层,但在write的底层也可以分配一个内核I/O缓冲区,所以write也不一定是直接写到文件的,也可能写到内核I/O缓冲区中,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,而C标准库的I/O缓冲区则不具有这一特性(想一想为什么)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值