【linux系统编程】C标准I/O函数和系统无缓冲函数(Unbuffered I/O函数)关系

先接收一下,本文章主要内容:

1.C标准I/O函数和系统无缓冲函数(Unbuffered I/O函数)关系;

2.I\O函数:buffer && unbuffer;

开始前,我们要清楚linux下C编程与WIN下C编程是不一样的,我们这片文章是linux下C编程,WIN下我并没有进行代码等相关测试。

其次,我们来区分一下C标准I/O函数和C标准I/O函数这两个函数的概念:

C标准I/O函数

定义在C标准库中,在所有支持C标准库的系统中都能使用C标准库函数。

Unbuffered 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系统调用获得,在使用readwriteclose系统调用时都需要传文件描述符。

fputc(3)

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

fclose(3)

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


openreadwriteclose等系统函数称为无缓冲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 a file,I/O函数不仅用于读写常规文件,也用于读写设备,比如终端或网络设备。在读写设备时通常是不希望有缓冲的,例如向代表网络设备的文件写数据就是希望数据通过网络设备发送出去,而不希望只写到缓冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,所以网络编程通常直接调用Unbuffered I/O函数。

这里我们基本上就弄懂了C标准I/O函数和系统无缓冲函数(Unbuffered I/O函数)关系。
———————————————————分割线—————————————————————————

BUFFER && UNBUFFER

这些缓冲是怎么回事呢?
I\O缓冲区:

  用户程序调用C标准I/O库函数读写文件或设备,而这些库函数要通过系统调用把读写请求传给内核(以后我们会看到与I/O相关的系统调用),最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc/fputc为例,当用户程序第一次调用fgetc读一个字节时,fgetc函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc函数会再次进入内核读1K字节到I/O缓冲区中。
另一方面,用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。

如下图示:
【linux系统编程】C标准I/O函数和系统无缓冲函数(Unbuffered I/O函数)关系_第1张图片
比较难理解,是吧?
我们现在做一个有趣的是实验:
#include <stdio.h>                                                                      int main() {     printf("hello world");     while(1);     return 0; }
我们会发现,hello world并没有输出,这是因为标准输入输出(stdio)是行缓冲的。
行缓冲:
如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。
如果没有while(1),等程序中的main函数退出的时候都会执行一个exit,,而且之前会做fflush操作。
但是由于上面的代码我们是用ctrl+c结束的没有经过main函数,所以并没有执行fflush的机会。

代码中while(1) 是一个死循环,为了让大家在这里看清楚:由于printf没有加入换行符,所以printf()函数并没有把hello world 存入内核,他只是把它放入了缓冲中。等待我们给一个信号----换行才会把它输出。
#include "stdio.h"      int main() {     printf("hello world");     fflush(stdout);     while(1);     return 0; }

虽然没有换行符,但是fflush强行把数据写回内核,因此在屏幕上能出现字符。在printf后面添加fflush(stdout)是常见用法,将输出缓冲区的内容打印到标准输出设备上,能提高printf的效率。

最后在来说说带缓冲和不带缓冲的io函数,其实这个缓冲区的意思应该是用户缓冲区,也就是说read.write等函数也不是直接就写到了磁盘文件当中,而是写道内核缓冲区当中。

      那么既然有内核缓冲区,为什么还要带缓冲的io呢?这样做是为了减少系统调用的次数,read等系统调用会操作磁盘,而且如果每次写的东西很少,这样做很不经济。而带缓冲有三种模型:

    一。全缓冲,当缓冲区满了以后在调用read等函数。

    二。行缓冲,标准io都是行缓冲的,比如我们的printf如果不加\n会出现不立即显示的情况,这个时候需要调用fflush刷新缓冲区,理解写入内核,至于我们没怎么感觉到差别是因为,return 或者exit的时候,会先做一些清理工作,包括刷新缓冲区,关闭所有文件描述符,然后调用_exit().


引用:1.Linux C编程一站式学习 宋劲杉
2.http://hi.baidu.com/anheizzq/item/bc8b89a4d7ac5494151073a4




你可能感兴趣的:(【linux系统编程】C标准I/O函数和系统无缓冲函数(Unbuffered I/O函数)关系)