【Linux】进程IO|重定向|缓冲区|dup2|dup|用户级缓冲区|模拟缓冲区

news2024/9/21 1:39:54

 

目录

 前言

重定向

实验一

为什么log.txt文件的文件描述符是1 

为什么向stdout打印的信息也出现在文件中

实验二

用户级缓冲区

为什么要有用户级缓冲区

 系统调用

dup

为什么close(fd1)之后还能向log.txt写入数据?

dup2 

 缓冲区

观察现象

测试1 

 测试2

测试3

测试4

 现象解释

现象1 解释

现象2解释

现象3解释

现象4解释

 缓冲区的刷新策略

用户级缓冲区与OS的关系

实现一个缓存区 

myStdio.h 

myStdio.c 

testmyStdio.c 


 前言

在【Linux】进程IO|系统调用|文件描述符fd|封装|理解一切皆文件-CSDN博客

中讲述了文件描述符,0代表着键盘,1和2代表着显示器,只不过一个是标准输出,一个是标准错误;

重定向

重定向的本质是上层使用的文件描述符不变(即数组下标不变),数组里面的内容发生变化,即在内核中更改文件描述符指向的文件对象,使另一个文件对象指向原有的文件对象,从而使原有的文件对象被另一个文件对象覆盖 

实验一

 观察下面代码

原本应该向屏幕输出的信息,却没有显示任何信息,但是打印生成的文件发现文件描述符fd是1;

为什么log.txt文件的文件描述符是1 

这跟上篇讲述的文件描述符分配有关;调用 open 打开 log.txt 之前关闭了标准输出,其对应的文件描述符1就闲置了出来,而 fd 的分配规则是从小到大依次寻找未被使用的最小值,所以 log.txt 对应的 fd 就为1;

为什么向stdout打印的信息也出现在文件中

stdout是个FILE*结构体类型,其封装的文件描述符默认为1,当分配1给文件后,printf()和fprintf(stdout)实际上是在向文件描述符为1的文件写入数据(语言的文件操作是对系统调用的封装),底层的系统调用write只认文件描述符。此时文件描述符为1的文件是log.txt,所以数据会写入到该文件中。
fprintf(stdout,…)等价于write(1,…)

 在文件创建之前,我们关闭了标准输出流,即 close(1) ,原指向标准输出的文件描述符不再指向标准输出,对应的 1号文件描述符就闲置了
调用 open 打开 log.txt 根据分配规则,所以log.txt的文件描述符就为 1, fd_array[1] 指向的是新的文件对象, fd_array[1] 不再指向标准输出

实验二

关闭用户级刷新 

现象: 注释掉fflush(...)后,文件没有被写入数据,

出现原因:数据在close(fd)之前还在缓冲区内。

fflush的作用是将用户级缓冲区的数据刷新到内核级的缓冲区。

用户级缓冲区

该缓冲区是应用程序直接管理和控制的内存区域。这些缓冲区位于应用程序的地址空间中,应用程序可以直接访问和操作它们,而无需进行系统调用。 

在语言层面上也有一个的缓冲区,使用库函数printf()或者fprintf()向文件写入数据时,会先加载到这个用户级缓冲区里面,并不会直接加载到内核级缓冲区中。 

当用户缓冲区刷新到内核缓冲区后,后面就是内核操作了,操作系统会适时来刷新,该数据流被刷新到磁盘的文件中了。 

我们用printf()和fpritnf()函数向文件写入数据时,这些数据会先进入用户级缓冲区里面

当我们注释掉fflush(),实际上就是没有主动的刷新用户级缓冲区里面的数据

紧接着关闭文件,即使程序结束时会自动刷新用户级缓冲区,但由于在此之前已经关闭了文件log.txt,那些数据也就丢失了。这就是重定向

为什么要有用户级缓冲区
  • 用户级缓冲区通常用于提高数据传输的效率,减少应用程序与操作系统之间的频繁交互。不必每次数据交互都访问内核缓冲区,而是可以先存在一个区域中,达到一定量了之后再刷新到内核缓冲区(相当于寄快递,统一时间段拉走)。
  • 对于操作系统来说,如果没有用户级缓冲区,我们每次向文件读写数据都要访问内核,间接加剧了内核访问磁盘的次数。
  • 对于用户来说,每次读写操作都要等待操作系统响应,这样无疑会降低用户的体验。所以用户级缓冲区可以提高用户的体验,也可以提高数据传输的效率。

综上所述:当改变原fd指向的内容时,就会发生重定向,因为非内核操作只认文件描述符 

 系统调用

操作系统提供了一个系统调用,可以直接实现重定向。

dup

  • 复制当前文件描述符的内容,返回一个新的文件描述符(当前可用的最小的描述符)。

实际上就是复制在文件数组(fd_array[N])中下标为fd的内容到文件数组(fd_array[N])中的另一个地址空间中。新旧描述符指向的文件是同一个。

  1. 打开文件log.txt,得到描述符fd1;
  2. 拷贝一份fd1文件表项内容,得到fd2,此时fd1和fd2描述同一个的文件
  3. 向fd1写入数据msg1之后关闭fd1;
  4. 向fd2写入数据msg2之后关闭fd2;

为什么close(fd1)之后还能向log.txt写入数据?

close()会不会直接关闭文件,取决于是否还有其它文件描述符指向该文件

这里采用了引用计数的原理:

  • 每个被打开的文件都会有一个计数器记录该文件被引用的次数。
  • 每多一个描述符指向该文件,该文件的引用计数器就会加一。反之,就会减一。一旦引用计数器为0,表示没有可用的描述符指向该文件,该文件也就才能真正地关闭。
  • 所以close(fd)的本质,是清空fd再使文件fd的引用计数器减一。
  • fd1和fd2指向的文件是同一个,关闭close(fd1)等于关闭了文件log.txt

虽然dup可以复制文件描述符,但是得到的新的描述符是不可控制的

比如:不能拷贝一个文件描述符到另一个已存在的文件描述符(dup函数得到的描述符是新的)

dup2 

 int dup2(int oldfd, int newfd);

dup2会将 fd_array[oldfd] 的内容拷贝到 fd_array[newfd] 当中,即把 fd_array[newfd] 的内容覆盖 ; 

头文件:<unistd.h>
 
oldfd:旧的文件描述符
newfd:新的文件描述符
 
函数返回值,成功返回 newfd,失败返回-1

dup2函数可以将一个已存在的文件描述符复制到另一个文件描述符上,并允许自定义新文件描述符的编号 

  1. 打开文件log.txt并获得其描述符fd
  2. 复制fd到1中。此时fd和1都是指向log.txt。此刻完成输出重定向,原本向屏幕文件输出的变成了向log.txt文件输出。

 缓冲区

  • 操作系统级别的缓冲区是内核的一部分,用于在硬件设备和应用程序之间提供一个临时存储区域。这些缓冲区用于提高I/O操作的效率,比如文件读写、网络通信等。
  • 用户级缓冲区是指在用户程序中显式创建和管理的缓冲区。需要自己负责缓冲区的分配、初始化、使用和释放。

观察现象

测试代码 

测试1 

 测试2

将运行程序的结果重定向到文件中;

测试3

在原有的代码末尾使用 fork 创建子进程,子进程不做任何事情;

测试4

输出重定向到 log.txt 文件中

 现象解释

现象1 解释

显示器采用的时行缓冲区;

printf()、fprintf():这两种语言封装的接口向标准输出(显示器)打印数据,程序中,每条打印语句后面都有\n(换行符),所以在这两个语句执行后会立即刷新缓冲区;

write():是系统调用,没有缓冲区,数据会立即被发送给操作系统,然后由操作系统负责进一步处理。它直接与底层文件描述符交互,将数据从用户空间复制到内核空间,并最终写入到相应的文件或设备上。

现象2解释

  • 通过输出重定向(>)将原本输出在显示器中的数据写在文件中,磁盘文件是采用的全缓冲刷新策略,所以printf(),fprintf()语句执行完毕后并不会立即刷新,一般会等进程退出这种特殊情况才会将所有数据刷新;
  • 标准输出被重定向到一个文件时,标准输出流被设置为全缓冲模式。这意味着标准输出流会在缓冲区填满或者显式调用fflush()时才刷新输出。

现象3解释

显示器采用行缓冲;

所以在 fork 之前 printf(),fprintf()语句的数据均已刷新到显示器上了

  • 对于进程来说,如果数据位于缓冲区内,那么该数据属于进程,此时 fork 子进程也会指向该数据
  • 但如果该数据已经写入到磁盘文件了,那么数据就不属于进程了,此时 fork 子进程也不在指向该数据了

所以,这里 fork 子进程不会做任何事情,结果和现象1一样

现象4解释

使用重定向指令将本该写入显示器文件的数据写入到磁盘文件中,而磁盘文件采用全缓冲,所以 fork 子进程时 printf(),fprintf()的数据还存在于缓冲区中 (缓冲区没满,同时父进程还没有退出,所以缓冲区没有刷新!);
此时数据还属于父进程,那么 fork 之后子进程也会指向该数据,而 fork 之后紧接着就是进程退出,父子进程某一方先退出时会刷新缓冲区,由于刷新缓冲区会清空缓冲区中的数据,为了保持进程独立性,先退出的一方会发生 写时拷贝,然后向磁盘文件中写入 printf(),fprintf()数据;然后,后退出的一方也会进行缓冲区的刷新;
所以,最终 printf(),fprintf()的数据会写入两份 (父子进程各写入一份),且 write 由于属于系统调用没有缓冲区,所以只写入一份数据且最先写入.

 缓冲区的刷新策略

缓冲区在达到一定数量后才会刷新,采取一定的刷新策略;

缓存区刷新策略有三种:

  • 立即刷新,无缓冲:缓冲区中一出现数据就立马刷新,IO 效率低,很少使用
  • 行刷新,行缓冲:每拷贝一行数据就刷新一次,显示器采用的就是这种刷新策略,因为显示器是给人看了,而按行刷新符合人的阅读习惯,同时IO效率也不会太低
  • 缓冲区满,全缓冲:待数据把缓冲区填满后再刷新,这种刷新方式 IO 效率最高

两种特殊情况:

  • 用户强制刷新缓冲区;
  • 进程退出,进程退出都要进行缓冲区刷新;

用户级缓冲区与OS的关系

C语言文件操作向磁盘文件写入数据的过程是:程序运行,进程通过 fwrite 等函数将数据拷贝到缓冲区中,然后再由缓冲区以某种刷新方式刷新 (写入) 到磁盘文件中;

并不是直接将数据写入到磁盘文件中的(用户级语言级缓冲区在用户部分),这个过程还要经过操作系统处理,然后数据才写入磁盘的文件中;

总结:

  1. 数据先通过 fwrite 等文件操作接口将进程数据拷贝到语言级的缓冲区里面
  2. 然后在语言级缓冲区再根据自己的的刷新策略通过 write 系统调用将数据拷贝到 file 结构体中的内核缓冲区中,
  3. 最后再由操作系统自主将数据(内核级缓冲区也有自己刷新数据的策略)真正的写入到磁盘的文件中,到这一步数据才真正意义上写入磁盘的文件中

实现一个缓存区 

用C语言实现一个缓冲区 

myStdio.h 

myStdio.c 

testmyStdio.c 

就是main函数 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1948376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IGV.js | 载入自己下载的gtf文件

1.安装 htslib-1.20 https://www.htslib.org/doc/tabix.html J3$ cd ~/Downloads/ $ wget https://github.com/samtools/htslib/releases/download/1.20/htslib-1.20.tar.bz2 $ tar jxvf htslib-1.20.tar.bz2编译安装&#xff1a; $ cd htslib-1.20/ $ ./configure --prefix/…

【图文多模态】

GPT4o 架构的核心是<font color"red">**Transformer**</font>&#xff0c;它通过自注意力机制&#xff08;Self-Attention&#xff09;来处理输入的序列数据端到端的方式训练&#xff1a;即从输入到输出的整个过程都在同一个网络中进行多模态大模型&…

不看后悔 养宠家庭的必备好物——最值得买的宠物空气净化器推荐

"猫毛满天飞&#xff0c;打扫永无尽头&#xff01;"这是多少养猫人的日常写照。或许你已经尝试了各种方法&#xff0c;从手动打扫到普通空气净化器&#xff0c;但效果总是不尽人意。普通空气净化器虽然有点用&#xff0c;但实际上它们并非专为宠物家庭设计&#xff0…

应用层自定义协议以及序列化和反序列化

文章目录 应用层自定义协议以及序列化和反序列化1、应用层自定义协议1.1、应用层1.2、协议 2、序列化和反序列化3、TCP 为什么支持全双工4、jsoncpp基础4.1、序列化4.2、反序列化 5、实现网络版计算器6、手写序列化和反序列化 应用层自定义协议以及序列化和反序列化 1、应用层…

【YOLOv8系列】图像分类篇----通过YOLOv8实现图像分类功能

最近需要使用YOLOv8对自己的数据集进行训练,从而实现图像分类的功能,因此记录一下整个过程。 YOLOv8的github地址:https://github.com/ultralytics/ultralytics 参考链接:超详细YOLOv8图像分类全程概述:环境、训练、验证与预测详解 文章目录 一、YOLOv8环境搭建二、准备…

【C++】:红黑树深度剖析 --- 手撕红黑树!

目录 前言一&#xff0c;红黑树的概念二&#xff0c;红黑树的性质三&#xff0c;红黑树节点的定义四&#xff0c;红黑树的插入操作4.1 第一步4.2 第二步4.3 插入操作的完整代码 五&#xff0c;红黑树的验证六&#xff0c;实现红黑树的完整代码五&#xff0c;红黑树与AVL树的比较…

从 NextJS SSRF 漏洞看 Host 头滥用所带来的危害

前言 本篇博文主要内容是通过代码审计以及场景复现一个 NextJS 的安全漏洞&#xff08;CVE-2024-34351&#xff09;来讲述滥用 Host 头的危害。 严正声明&#xff1a;本博文所讨论的技术仅用于研究学习&#xff0c;旨在增强读者的信息安全意识&#xff0c;提高信息安全防护技能…

Java Linux操作系统

1、操作系统是协助用户调度硬件工作&#xff0c;充当用户和计算机硬件之间的桥梁 2、Linux内核 提供了linux系统的主要功能 3、发行版Centos&#xff1a;内核应用程序 4、快照&#xff1a;保存虚拟机的状态&#xff0c;当虚拟机出现问题的时候&#xff0c;可以恢复原始的状态…

Hospital 14.6.0全开源医院管理预约系统源码

InfyHMS 具有 60 种功能和 9 种不同类型的用户类型&#xff0c; 他们可以登录系统并根据他们的角色访问他们的数据。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89580674 更多资源下载&#xff1a;关注我。

MyBatis--11-- 判断 Integer类型,值为0动态SQL不生效

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 MyBatis标签&#xff1a; Integer类型&#xff0c;值为0动态SQL不生效1.现象2.分析原因3.解决办法去掉判断条件 dto.status ! "即可 MyBatis标签&#xff1a; …

CSS相关记录

文章目录 backgroundposition文字displayflexjustify-contentalign-itemsflex-directionflex-wrap gridimportant transformtranslate&#xff08;位移&#xff09;scale&#xff08;缩放&#xff09;rotate&#xff08;旋转&#xff09;origin (旋转中心点)skew (倾斜 ) borde…

kafka架构+原理+源码

1.安装jdk17 sudo yum -y update sudo wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm sudo yum -y install ./jdk-17_linux-x64_bin.rpm、 sudo java -version 2.安装kafka How to easily install kafka without zookeeper | Aditya’s Blog …

C++——保持原有库头文件不变的情况下,成功编译运行工程

问&#xff1a;想要保持原来库方式&#xff0c;应该怎么操作呢&#xff1f; 答&#xff1a;如果想保持原来的方式&#xff0c;则只需要将 库所在路径 tracker/detector/rknn_model_zoo/utils 加入到 工程库包含中即可。

DVWA的安装和使用

背景介绍 DVWA是Damn Vulnerable Web Application的缩写&#xff0c;是一个用于安全脆弱性检测的开源Web应用。它旨在为安全专业人员提供一个合法的测试环境&#xff0c;帮助他们测试自己的专业技能和工具&#xff0c;同时也帮助web开发者更好地理解web应用安全防范的过程。DV…

FastAPI(七十)实战开发《在线课程学习系统》接口开发--留言功能开发

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 在之前的文章&#xff1a;FastAPI&#xff08;六十九)实战开发《在线课程学习系统》接口开发--修改密码&#xff0c;这次分享留言功能开发 我们梳理…

gstreamer使用cairo实现视频OSD叠加

前言 gstreamer中视频叠加OSD有很多种方式,比如textoverlay添加文字,gdkpixbufoverlay添加图片,clockoverlay或timeoverlay插件显示时间,pango插件进行复杂文本渲染,使用cairo插件绘制图形或者文字。 今天使用最后一种:cairo 项目源码: 如果是QT,pro文件需要导入: …

Tensorflow中高维矩阵的乘法运算tf.matmul(tf.linalg.matmul)详悉

1.问题由来 在tensorflow框架下&#xff0c;经常会用到矩阵的乘法运算&#xff0c;特别是高&#xff08;多&#xff09;维的矩阵运算&#xff0c;在这些矩阵运算时&#xff0c;经常使用到其中的tf.matmul或tf.linalg.matmul等函数。但高维矩阵在内部怎么运算的&#xff1f;其内…

跟代码执行流程,读Megatron源码(四)megatron初始化脚本initialize.py之initialize_megatron()分布式环境初始化

在前文中&#xff0c;我们讲述了pretrain函数的执行流程&#xff0c;其首要步骤是megatron分组的初始化与环境的配置。本文将深入initialize_megatron函数源码&#xff0c;剖析其初始化分布式训练环境的内部机制。 注&#xff1a;在此假设读者具备3D并行相关知识 一. initiali…

Zabbix自定义监控内容部署+邮件报警+Zabbix自愈+Zabbix批量添加主机

一、自定义监控项 1.1自定义监控项原理 1&#xff09;先明确获取监控指标数据的命令或脚本; 2&#xff09;在被监控主机配置文件子目录&#xff08;/etc/zabbix/zabbix_agent2.d/)中创建以.conf后缀的监控项配置文件&#xff0c;自定义获取监控指标数据的键值&#xff1b; …

Windows:批处理脚本学习

目录 一、第一个批处理文件 1. &&和 | | 2. | 和 & 二、变量 1.传参变量%name 2.初始化变量set命令 3.变量的使用 4.局部变量与全局变量 5.使用环境变量 6.扩充变量语法 三、注释REM和 &#xff1a;&#xff1a; 四&#xff1a;函数 1.定义函数 2.…