IO标准库函数与系统调用 缓冲区
行缓冲测试无缓冲测试 IO标准库函数与系统调用
标准库函数运行在用户空间下,而系统调用运行在内核空间下。
IO标准库函数中使用了系统调用。例如,fopen(3) 调用 open(2) 打开指定的文件,返回一个文件描述符(就是一个int类型的编号),分配一个 FILE 结构体,其中包含该文件的描述符(FD)、IO缓冲区和当前读写位置等信息,返回这个 FILE 结构体的地址。Linux中输入命令 man 2 open 查看第二章节系统调用的 open。
使用命令 man man 查看 man 命令的帮助,man帮助手册的第3部分是库函数的调用,如fopen(3),第2部分是系统调用,即由系统内核提供的函数,如 open(2)。
fgetc(3) 通过传入的 FILE * 参数找到该文件的描述符、IO缓冲区和当前读写位置,判断能否从IO缓冲区中读到下一个字符,如果能读到就直接返回该字符,否则调用 read(2),把文件描述符传进去,让内核读取该文件的数据到IO缓冲区,然后返回缓冲区的下一字符。
fputc(3) 判断该文件的IO缓冲区是否有空间再存放一个字符,如果有空间则直接保存在IO缓冲区中并返回,如果IO缓冲区已满就调用 write(2),让内核把IO缓冲区的内容写回文件。
fclose(3) 如果IO缓冲区中还有数据没写回文件,就调用 write(2) 写回文件,然后调用 close(2) 关闭文件,释放 FILE结构体 和 IO缓冲区。
fgetc、fputc 等函数本质上只是用户对传入的 FILE * 参数进行操作,并没有直接对文件进行操作,用户只是调用系统调用,让操作系统内核帮我们操作文件。操作系统才是直接对文件进行操作的角色,用户只是通过系统调用间接操作文件。用户对 FILE * 这个指针参数进行操作便能完成对文件整体的操作,故将 FILE * 参数称为句柄(类似于门把手,可以通过门把手打开整扇门)。
同时,IO标准库帮我们维护 buffer 这个IO缓冲区,因此IO标准库也叫做缓冲IO函数。buffer 缓冲区的作用是帮助提高文件读写的效率,可以将IO缓冲区类比为收发快递的菜鸟驿站,当用户寄出去的快递达到一定数量的时候,才会统一发货,否则会提高运输次数,浪费资源(当程序要写进文件的字符达到一定数量的时候,才会统一写进文件,最大化利用资源,因为用户态和内核态的转换需要时间)。
当商户给用户发多件货物时,肯定是统一发送至菜鸟驿站比较高效节约运输次数(当程序要从文件中读取字符时,先切换至内核态,拉一批字符出来到IO缓冲区,再切回用户态逐一读取字符,这样只需要切换两次状态)。
缓冲区分为三类:全缓冲、行缓冲、无缓冲。全缓冲是指待缓冲区填满的时候,再进行系统调用将缓冲区内容输入到内存或输出到磁盘;行缓冲是指当缓冲区出现换行符或填满时,立即进行系统调用;无缓冲是指当缓冲区用东西进来的时候,马上进行系统调用。
stdout 是行缓冲,遇到换行符或填满才将缓冲区内容输出到标准输出(屏幕)中;stderr 是无缓冲,立即将“错误信息”输出到标准输出中。
#include
test1结果如下,屏幕没有任何输出。
test2结果如下,stdout 缓冲区中有换行符输入,立即进行系统调用将缓冲区内容(ABCn)输出到标准输出(屏幕)中。
test3结果如下,向缓冲区中输入1024个A后,仍然没有进行系统调用将缓冲区内容输出到屏幕中,说明缓冲区大小至少为1024个字节(即1KB),因为缓冲区满了之后,需要再输入一个字符才能将缓冲区内容输出到屏幕。
test4结果如下,向缓冲区输入1025个A后,缓冲区内容通过系统调用输出到了屏幕中,是因为当输入第1024个A之后,缓冲区刚好满了,在输入第1025个字符时,发现缓冲区满了,立即通过系统调用将缓冲区内容(1024个A)输出到屏幕中,最后再将第1025个A输入到缓冲区中,即此时缓冲区中仍然有一个A。
#include
test1结果如下,stderr 缓冲区中一旦有字符输入,立即进行系统调用将该字符输出到屏幕中。