2024年【Linux】基础IO —— 缓冲区深度剖析_linux io 缓冲区大小(4),字节跳动Linux运维高级工程师

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

我们最后调用fork,上面的函数已经被执行完了,但不代表数据已经被刷新了

🥑缓冲区是谁提供的

🔥曾经“我们所谈的缓冲区”,绝对不是由OS提供的,如果是OS同一提供,那么我们上面的代码,表现应该是一样的,而不是C的IO接口打印两次,所以是C标准库提供并且维护的用户级缓冲区

fputs把不是直接把数据直接放进操作系统,而是加载进C标准库的缓冲区中,加载完后自己可以直接返回;如果直接调用的是write接口,则是直接写给OS,不经过缓冲区

在这里插入图片描述

  1. C语言提供的接口都是向显示器打印的,刷新策略都是行刷新,那么最后执行fork的时候 —— 一定是函数执行完了 && 数据已经被刷新了(因为都带\n),所以fork执行无意义
  2. 如你对应的程序进行了重定向 ——> 要向磁盘文件打印 ——> 隐形的刷新策略变成了全缓冲!—— > \n便没有意义了 ——> 函数一定执行完了,数据还没有刷新!! 在当前进程对应的C标准库中的缓冲区中!!

这缓冲区的部分数据是父进程的数据吗? 是的
fork之后,父子分流,父进程的数据发生写时拷贝给子进程,所以C标准库会打印两次

在这里插入图片描述

总结:

  • 重定向到文件导致:刷新策略改变(变成全缓冲)
  • 写时拷贝:父子进程各自刷新一次
🥑用户级缓冲区在哪里?

当我们用fflush强制刷新的时候

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
 	//C语言提供的
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    const char \*s = "hello fputs\n";
    fputs(s, stdout);

    //OS提供的
    const char \*ss = "hello write\n";
    write(1, ss, strlen(ss));
    
    //fork之前,强制刷新
    fflush(stdout);
    
    //最后调用fork的时候,上面的函数已经被执行完了
    fork();//创建子进程 
    return 0;
}

结果如下:

在这里插入图片描述

数据在fork之前,已经被fflush刷新了,缓冲区里没有数据了,也就不存在写时拷贝。

这里更夸张的是,fflush(stdout)只告诉了stdout就能知道缓冲区在哪里?

FILE \*fopen(const char \*path, const char \*mode);

  • C语言中,open打开文件,返回的是FILE * ,struct FILE结构体 — 内部封装了fd,还包含了该文件fd对应的语言层的缓冲区结构!(远在天边,近在眼前)

我们可以看看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
};

所以在C语言上,进行写入的时候放进缓冲区,定期刷新

C语言打开的FILE是文件流。C++中的cout 是类;里面必定包含了 fd、buffer(缓冲区)

🌏设计用户层缓冲区的代码 ~ 实战
💢struct file的设计

在这里插入图片描述

struct MyFILE\_{                  
 	 int fd;            //文件描述符
	 char buffer[1024]; //缓冲区
	 int end;           //当前缓冲区的结尾
};

💢主函数

open文件 —— fputs输入 —— fclose关闭,接口函数都要我们逐一实现

int main()
{
	MyFILE \*fp = fopen\_("./log.txt", "r");
	if(fp = NULL)
	{
 		 printf("open file error");
 		return 0;
	}

	 fputs\_("hello world error", fp);
	 fclose\_(fp);
}

我们发现:C语言的接口一旦打开成功,全部都要带上FILE*结构,原因很简单,因为什么数据都在这个FILE结构体中

FILE \*fopen(const char \*path, const char \*mode);
//以下全是要带FILE\*
int fputc(int c, FILE \*stream);
int fclose(FILE \*fp);
size_t fread(void \*ptr, size_t size, size_t nmemb, FILE \*stream);

💢接口实现

💦fputs

在这里插入图片描述

    //此处刷新策略还没定 全部放进缓冲区
    void fputs\_(const char \*message, MyFILE \*fp)                                 
    {                                                                            
      assert(message);                                                           
      assert(fp);                                                                
                                                                                 
      strcpy(fp->buffer + fp->end, message);//abcde\0
      fp->end += strlen(message);                                                              
    }

运行结果:
在这里插入图片描述

上面覆盖了\0,strcpy会在结尾时候自动添加\0

若要往显示器上打印:变成行刷新

    if(fp->fd == 0)
    {
        //标准输入
    }
    else if(fp->fd == 1)
    {
        //标准输出
        if(fp->buffer[fp->end-1] =='\n' )
        {
            //fprintf(stderr, "fflush: %s", fp->buffer); //2
            write(fp->fd, fp->buffer, fp->end);
            fp->end = 0;
        }
    }
    else if(fp->fd == 2)
    {
        //标准错误
    }
    else
    {
        //其他文件
    }
}

测试用例:

fputs\_("one:hello world error", fp);
fputs\_("two:hello world error\n", fp);
fputs\_("three:hello world error", fp);
fputs\_("four:hello world error\n", fp);

结果:当遇到\n,才刷新
在这里插入图片描述

💦fflush刷新
当end!=0 ,就刷新进内核
内核刷新进外设,这就要用一个函数syncfs

#include <unistd.h>
//将缓冲区缓存提交到磁盘
int syncfs(int fd);

具体实现:

    void fflush(MyFILE \*fp)                  
    {                                        
      assert(fp);                            
      if(fp->end != 0)                       
      {                                      
        //暂且认为刷新了 ——其实是把数据写到 内核
        write(fp->fd, fp->buffer, fp->end);  
        syncfs(fp->fd); //将数据写入到磁盘 
        fp->end = 0;
      }                                                                          
    }  

💦fclose
关闭之前要先刷新

  void fclose(MyFILE \*fp)
  {
    assert(fp);
    fflush(fp);                                                                                
    close(fp->fd);
    free(fp);
  }

💢附源码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>

#define NUM 1024

struct MyFILE\_{
    int fd;             //文件描述符
    char buffer[1024];  // 缓冲区
    int end;            //当前缓冲区的结尾
};

typedef struct MyFILE\_ MyFILE;//类型重命名

MyFILE \*fopen\_(const char \*pathname, const char \*mode)
{
    assert(pathname);
    assert(mode);

    MyFILE \*fp = NULL;//什么也没做,最后返回NULL

    if(strcmp(mode, "r") == 0)
    {
    }
    else if(strcmp(mode, "r+") == 0)
    {

    }
    else if(strcmp(mode, "w") == 0)
    {

        int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
        if(fd >= 0)
        {
            fp = (MyFILE\*)malloc(sizeof(MyFILE));
            memset(fp, 0, sizeof(MyFILE));
            fp->fd = fd;
        }
    }
    else if(strcmp(mode, "w+") == 0)
    {

    }


### 最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

### 资料预览

给大家整理的视频资料:

![](https://img-blog.csdnimg.cn/img_convert/54b9c054f960501d6950b930cd51c803.png)

给大家整理的电子书资料:

  

![](https://img-blog.csdnimg.cn/img_convert/fa0e8c2bec987257d5db18293ce21792.png)



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

存中...(img-VtLIi5nx-1715216769685)]

给大家整理的电子书资料:

  

[外链图片转存中...(img-oiWDBvep-1715216769685)]



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值