hello~ 很高兴见到大家! 这次带来的是Linux系统中关于基础 IO这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?个 人 主 页: 默|笙文章目录一、理解缓冲区1.1 文件打开和读写的基本过程1.2 内核文件缓冲区1.3 语言级缓冲区二、缓冲区刷新问题2.1 语言缓冲区2.2 内核文件缓冲区一、理解缓冲区1.1 文件打开和读写的基本过程我们知道文件在没有打开之前是放在磁盘里面的打开的过程就是将文件加载进入内存里面毕竟cpu想要访问文件只能通过内存。打开一个文件进程首先调用open()系统调用然后OS会创建一个描述该文件的结构体它直接或间接包含了文件的所有属性。然后这个结构体会分配到一个该进程的文件描述符fd方便进程对打开的文件进行管理。文件等于属性内容对文件进行读写就是对文件的内容进行读取或修改但是cpu无法直接修改磁盘里面的文件它得通过内核缓冲区来进行修改。1.2 内核文件缓冲区OS首先会把文件的内容按需加载到内核缓冲区然后将内核缓冲区的内容拷贝到用户缓冲区再将cpu修改过后的用户缓冲区的内容拷贝到内核缓冲区最后通过OS自动刷新到磁盘里面。从而完成对磁盘文件的读取和修改。所以系统调用read和write接口的真正作用其实是拷贝read是将内核缓冲区的内容拷贝到用户缓冲区而write是将用户缓冲区的内容拷贝到内核文件缓冲区。OS将内容加载到缓冲区就跟scanf从键盘读取数据一样如果没有读取到就会阻塞在那里。一开始缓冲区里面什么都没有那么OS就会根据调用的接口自动加载内容到缓冲区。内核文件缓冲区在OS内核里面。1.3 语言级缓冲区如果我们每修改一次文件内容都调用系统调用read和write对内容进行拷贝。那么它的效率将会很低。毕竟调用系统调用是有成本的尤其对于read和write这种拷贝函数。比如我们用malloc、new申请空间都是有成本的所以vector进行扩容都是2倍或1.5倍扩容而不是按需扩容就是为了减少调用malloc/new函数的次数。要提升效率也就是要减少read和write的系统调用次数。那我们是否可以这样将用户缓冲区里面的内容先放到一个地方给它存起来不立刻拷贝到内核缓存区而是等这部分存储的内容到达了一个指标之后再将它们一起打包拷贝到内核缓存区。这样就能够减少write的调用次数。这个用来存储数据的地方就是语言缓存区。减少read调用则与减少write调用相反减少write是将先把小数据攒到用户缓冲区达标后批量写而减少read是将先批量读大块数据到用户缓冲区再从缓冲区里按需取小数据。系统层面优化不了所以优化就得放在语言层面了。打一个比方就是像我们寄快递商家不会因为我们这一件快递就让快递车上路那样效率既低费用也会提升。而是等个一两天等快递数量到达一个指标之后将它们一起运送。那么这个语言缓冲区它在哪里我们知道FILE它是一个结构体用fopen打开文件后会返回一个FILE类型的指针stdout、stdin和stderr都是FILE类型。那么这个结构体它里面包含了哪些东西首先一定是有文件描述符fd这样才能够管理这个文件而语言缓冲区也是在这个结构体里面的语言缓冲区分为输出缓冲区和输入缓冲区。像我们用fgets等c语言函数而非系统调用进行读取写入时都是通过语言级缓冲区。我们平常所说的缓冲区指的是语言级缓冲区像之前我们接触到的系统接口_exit和接口exit。二者的区别就是_exit不会清空缓冲区而exit会这就说明语言级缓冲区是绝对不在OS里面的。fclose封装了系统调用closeclose的作用是关闭fd而fclose的作用是清空缓冲区关闭fd释放FILE指针。 它们都会切断语言级缓冲区和内核文件缓冲区之间的联系。看如下代码7intmain()8{9close(1);10intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);11printf(fd is : %d\n,fd);// stdout - 112fclose(stdout);13return0;14}7intmain()8{9close(1);10intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);11printf(fd is : %d\n,fd);// stdout - 112//fclose(stdout);13close(fd);14return0;15}我们先关闭文件描述符1所对应的显示器文件然后打开log.txt文件成为新的stdout所对应的文件。如果我们用fclose关闭文件它会刷新缓存区所以我们仍然能够在文件log.txt里面看到用printf输出的内容。而如果用close关闭文件它就不会刷新缓冲区而是直接切断缓冲区和内核文件缓冲区之间的联系在文件log.txt里面我们就看不到printf所输出的内容想要看到就必须刷新缓冲区这里我们可以用fflush接口来刷新缓冲区。7intmain()8{9close(1);10intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);11printf(fd is : %d\n,fd);// stdout - 112//fclose(stdout);13fflush(stdout);14close(fd);15return0;16}二、缓冲区刷新问题2.1 语言缓冲区什么时候要将语言缓冲区的内容拷贝到内核文件缓冲区刷新里面去总体可以分为三种情况进程结束的时候会自动刷新。如果目标文件是显示器文件刷新方式为行刷新。—行缓冲普通文件一般都是缓冲区写满了才会进行刷新。----全缓冲我们来看一段代码10//c语言向显示器打印文件11printf(hello printf\n);12fprintf(stdout,hello fprintf\n);13constchar*shello fputs\n;14fputs(s,stdout);1516//系统调用17constchar*sshello write\n;18write(1,ss,strlen(ss));192021fork();22return0;我们仅仅是改变了目标文件就会有这么大的差异。第一次我们执行的时候它的目标文件是显示器文件是行缓冲只要有\n换行符就会刷新缓冲区。第二次我们更改目标文件目标文件变成log.txt这个普通文件缓冲方式就变成了全缓冲用c语言库函数输出的内容会放到语言级缓冲区直到进程结束才刷新到内核文件缓冲区而系统调用write它是直接拷贝到内核文件缓冲区的这就是为什么第二次write跑到前面去了的原因。至于第二次为什么库函数会有两遍这是因为fork之后子进程继承了父进程的缓冲区第一次没有是因为父进程缓冲区空空如也。2.2 内核文件缓冲区内核文件缓冲区的刷新方式取决于OS自主刷新一般分为立即刷新和等OS不忙了之后再刷新进磁盘里面。只要把数据从用户缓冲区拷贝到内核文件缓冲区就相当于将数据交给了硬件因为接下来的工作就是OS的了我们没有办法干预。但是从客观上来讲我们只是将数据交给了内核文件缓冲区而不是磁盘什么时候交给磁盘全看OS但如果有立即刷新到磁盘的需求可以使用系统调用fsync()。今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~让我们共同努力, 一起走下去!