【Linux】谈谈用户层缓冲区的问题

便准C的IO缓冲区

在C语言中,它的IO接口都是带缓冲区的;
比如什么printf,fprintf,puts,putc,scanf,sscanf等;C语言的标准IO都是有缓冲区的;
那它的缓冲区在哪呢?
FILE这个结构体中:
有兴趣可以在Linux操作系统中:打开文件/usr/include/stdio.h里面可找到FILE的结构体:

/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

我们可以看到FILE结构体里面有很多缓冲区,这就是C语言缓冲区的位置;


当我们执行所谓的C语言IO接口操作时候,都必须经过C的缓冲区;
比如执行有关写操作的IO接口,不是直接把信息写到磁盘和屏幕或者其他外设,而是先写入到缓冲区中;然后再由缓冲区的刷新策略决定,是否将缓冲区的数据刷新到内核缓冲区中(内核缓冲区就是为了提高与外设交换的一个缓冲区),再由内核缓冲区刷新到外设中。(内核缓冲区刷新到外设这个是由操作系统搞的,我们不必关心内核缓冲区)


刷新策略有三种:
在这里插入图片描述


如何从C语言的缓冲区刷新数据到内核缓冲区呢?

这里式通过文件描述符刷新到内核缓冲区的;因为C接口IO的调用,一定要经过系统调用,系统调用IO一定会使用文件描述符;


标准C的IO接口缓冲区导致的一些现象解释

所以一些代码现象我们就可以解释了:

#include<stdio.h>
int main(){
  printf("hello printf\n");

  return 0;
}

这段代码会输出:
hello printf
原因printf执行先将hello printf\n放入到C语言的缓冲区中,由于式往显示器打印,所以当碰到了\n时候,就会刷新缓冲区内容到屏幕显示出来;


#include<stdio.h>
int main(){
  printf("hello printf");
	while(1){;}
  return 0;
}

这段代码执行结果没有显示hello printf,并且陷入死循环;
原因:printf往显示器打印数据,是使用行刷新策略,当你printf没有换行,那么你的数据就一直保存在缓冲区中,没有被刷新到显示器,由于后面一直死循环,所以进程不会退出,进程不退出那么也不会刷新缓冲区的数据,所以不会显示内容;


这段代码本意是做一个输出重定向,把本输出到屏幕的内容输出到log.txt文件中

#include<string.h>
#include<stdio.h>
#include<fcntl.h>
int main(){
  close(1);
  int fd = open("./log.txt",O_CREAT | O_WRONLY,0644);
  printf("hello printf\n");
  fprintf(stdout,"hello fprint\n");
  close(fd);
  return 0;
}

代码的结果:
可是,当我们执行该文件时候,去观察log.txt文件中,并没有我们重定向的内容;


原因:因为当我们关闭了close(1),再打开log.txt,此时printf输出的内容,不再是往显示器打印了,而是往文件中写入了;

由于原本是往屏幕写入数据的,现在变成往文件写数据,也就是往磁盘写数据;

此时刷新缓冲区的策略从行刷新,变成了满刷新,也就是等缓冲区满了才会把数据刷新到文件中;
很明显,我们这两句打印的数据内容没有填满缓冲区;

并且在进程退出前,close(fd),直接关闭文件描述符,也就断开了进程在退出时,会主动刷新缓冲区的时机;

因为文件描述符都关闭了,也就断开了从C语言缓冲区刷新数据到内核缓冲区的过程;


这段代码:假如我们把它的执行结果使用输出重定向到log.txt文件中,我们观察 log.txt文件,发现只有
hello write ,并没有hello printf,这是为啥呢?

#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<fcntl.h>

int main(){
  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  printf("hello printf\n");
  close(1);
  return 0;
}

原因解释:很明显:当我们把结果输出重定向到log.txt中,会使得文件描述符1指向了log.txt文件,还隐含的也改变了缓冲区的刷新策略,由原来的行缓冲刷新变成满缓冲刷新;
由于write是系统调用没有缓冲区的,会显示 hello write 而printf是C语言接口,带缓冲区的,当重定向到log.txt时候,就变成了满缓冲刷新策略;
此时在进程退出前,我们关闭close(1),也就是关闭文件描述符指针,此时本来进程退出可以将缓冲区刷新到log.txt的机会,但是被close(1)关闭了,断开了刷新缓冲区的路径;所以不会打印hello printf;


#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<fcntl.h>

int main(){
  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  printf("hello printf\n");
  fork();
  return 0;
}

当我们输出重定向该代码结果到log.txt时候,观察log.txt文件会显示如下内容;
在这里插入图片描述


为什么hello printf有两句呢?而hello write只有一句呢?
解释原因:

因为当我们发生重定向时候,同时修改了刷新策略,从行刷新到满才刷新,我们的hello write 直接重定向到了log.txt,因为它没有缓冲区,而hello printf 先存入到了缓冲区;

当我们再调用fork时候,创建子进程,它会继承父类的缓冲区内容,当子进程退出时候,会刷新子进程的缓冲区内容,把它刷新到了log.txt文件中,当父进程也推出时候,也刷新了父进程的缓冲区,自然而然把父进程的缓冲区内容刷新到了log.txt中,所以hello printf有两份;

由于write是系统调用,没有缓冲区,在创建子进程之前,write就已经将内容重定向到了log.txt,所以创建子进程后,hello write只打印一份;


总结用户层缓冲区问题

我们在上面谈的缓冲区,都是用户层的缓冲区,我只不过是以C结构的IO举例,其实任何语言的IO都有自己的缓冲区,在我们把它成为用户层的缓冲区;
其实内核也有个缓冲区,这个缓冲区是和硬件:磁盘,屏幕等外设交互的,为的是提高存取效率;这里我们不关心内核缓冲区;
系统调用的IO接口时没有缓冲区的,因为这是系统调用啊,不是用户层的接口;

在这里插入图片描述


  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呋喃吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值