I/O缓冲

Linux下C编程的I/O函数可分为两大类:

一类是包含在头文件 stdio.h 中的标准C语言库I/O函数(即stdio函数),如int fgetc(FILE *stream); int getc (FILE *stream); int getchar(void); 

对于stdio函数,它们的操作都是围绕流(stream)进行的,当用stdio函数FILE * fopen(const char * path,const char * mode);打开一个文件时,我们就把一个流与这个文件相关联,fopen函数返回一个指向FILE对象的指针,该对象通常是一个结构体,它包含了I/O库为管理该流所需要的所有信息,包括:用于实际I/O的文件描述符,指向该流缓冲区的指针,缓冲区的长度,当前缓冲区中的字符数,以及出错标志等。

这类函数都是带有缓冲区的,他们的直接读写对象是用户空间的缓冲区,而对磁盘文件的实际操作是通过调用read()和write()系统调用实现的;关于缓冲区的解释,以下摘自Stevens的《UNIX环境高级编程》第五章:

标准I/O提供了三种类型的缓存:

(1) 全缓存。在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。对于驻在磁盘上的文件通常是由标准I/O库实施全缓存的。

(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。

(3) 不带缓存。标准I/O库不对字符进行缓存。如果用标准I/O函数写若干字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。

另一类包含在头文件 unistd.h 中,是系统I/O调用,如ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);

对于系统I/O调用,它们都是通过文件描述符对文件进行操作的,当调用int open(const char *path, int oflag, ... /* mode_t mode */ );成功打开一个文件时,open函数就返回一个文件描述符来表示对这个文件的引用,以后所有对这个文件的读写操作都是通过返回的文件描述符进行的。

与stdio函数不同的是,这些函数是不带缓冲的,不带缓冲指的是每个read()和write()都调用内核中的一个system call,而在用户空间中是没有缓冲的;read()和write()系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存(kernel buffer cache)之间复制数据。例如,如下调用将3个字节的数据从用户空间内存传递到内核空间的缓冲区中:write(fd, "abc", 3); write()随即返回。在后续某个时刻,内核会将其缓冲区中的数据写入到磁盘。同理,对于输入而言,内核从磁盘中读取数据并存储到内核缓冲区。read()调用将从该缓冲区中读取数据,直至把缓冲区中的数据读完,这时,内核会将文件的下一段内容读入缓冲区高速缓存。因此,可以说系统调用与磁盘操作并不同步。


下面通过调用标准I/O的getchar()函数,和文件I/O的read函数来对标准输入进行读操作,具体说明两类函数的不同:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
    int i;
    char ch, buf[50];
    bzero(buf, 50);
    while(1){

        ch = getchar();
        printf("ch = %c\n", ch);
        
        printf("before read\n");
        read(STDIN_FILENO, buf, 10);
        printf("buf = %s\n", buf);
        for(i = 0; i <= 50; i++){              //将buf中的字符以数值形式输出
            if(buf[i] != 0){
                printf("buf[%d] = %d\t",i, buf[i]);
            }
        }
    }
}


程序运行后,getchar()函数等待用户输入直到按回车才结束,回车前的所有输入字符都会逐个显示在屏幕上。getchar()通过系统调用read()函数从内核缓冲区读取用户输入的字符串(包含回车符)到用户空间缓冲区,然后getchar()从用户空间缓冲区读取字符串的第一个字符作为函数的返回值。以后每次调用getchar(),只要用户空间缓冲区有数据,getchar()就会直接从该缓冲区读取一个字符(包含回车符)作为函数的返回值,直到读完缓冲区。再次调用getchar(),又会像第一次调用getchar()函数那样等待用户输入。

文字描述太难懂,直接上代码,一次getchar()执行过程如下:

if(用户缓冲区没有数据){

        if(内核缓冲区有数据){

                调用read()函数从内核缓冲区读取数据到用户缓冲区;

         }else{

                等待用户输入;

         }

}

if(用户缓冲区有数据){

        getchar()读取用户缓冲区第一个字符作为函数返回值;

}

上面是程序中getchar()部分的运行过程,下面说说read()函数,read(STDIN_FILENO, buf, 10); 这个语句是从标准输入在内核中的缓冲区读取数据,若没有数据,则等待用户输入直到按回车才结束;若有数据则读取10个字符到指定的buf;若数据少于10字符,有多少读多少;若数据多于10个字符,则只读取10个字符,剩下的等待下次读取;这里所说的下次读取,包括任何调用read()函数的情况。如在上面的程序中,调用getchar()时用户缓冲区没有数据,getchar()就会通过调用read()从内核缓冲区读取数据,而不会关心数据的来源。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值