标准I/O库:
先来看看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)的关 系如下图所示。
库函数与系统调用的层次关系
Unbuffered I/O(不带缓冲的I/O)函数
open、read、write、close等系统函数称为无缓冲I/O(Unbuffered I/O)函数,因为它们位于C标准库的I/O缓冲区的底层。用户程序在读写文件时既可以调用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 afile,I/O函数不仅用于读写常规文件,也用于读写设备,比如终端或网络设备。在读写设备时通常是不希望有缓冲的,例如向代表网络设备的文件写数据就是希望数据通过网络设备发送出去,而不希望只写到缓冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,所以网络编程通常直接调用Unbuffered I/O函数。
C标准库函数是C标准的一部分,而Unbuffered I/O函数是UNIX标准的一部分,在所有支持C语言的平台上应该都可以用C标准库函数(除了有些平台的C编译器没有完全符合C标准之外),而只有在UNIX平台上才能使用UnbufferedI/O函数,所以C标准I/O库函数在头文件stdio.h中声明,而read、write等函数在头文件unistd.h中声明。在支持C语言的非UNIX操作系统上,标准I/O库的底层可能由另外一组系统函数支持,例如Windows系统的底层是Win32 API,其中读写文件的系统 函数是ReadFile 、WriteFile。