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

 

目录

 前言

重定向

实验一

为什么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.mzph.cn/pingmian/50006.shtml

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

相关文章

C++初阶学习第三弹——类与对象(上)

目录 一.初步认知对象 二.类的基本组成、 1.类的定义 2.类的访问限定符及封装 3.类的作用域 4、类的大小的计算 5.this指针 三.总结 一.初步认知对象 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。…

Linux下普通用户无法执行sudo指令

当执行sudo指令时出现&#xff1a; xxx&#xff08;普通用户名字&#xff09; is not in the sudoers file 说明在/etc/sudoers文件中没有把xxx加入到可执行sudo指令的名单中&#xff0c;因此需要修改sudoers文件。 解决方法&#xff1a;1、vim /etc/sudoers &#xff08;要…

什么是hdfs如何使用驱动程序访问hdfs

目录 什么是hdfs 主要特点包括&#xff1a; 架构组成&#xff1a; 应用场景&#xff1a; 如何使用驱动程序访问hdfs 准备工作环境&#xff1a; 启动 Hadoop 服务 可能遇到的问题&#xff1a; ssh验证失败 验证Hadoop服务 对hdfs进行文件操作 什么是hdfs HDFS&#x…

vite5+vue3开发阅读APP实战笔记20240725

目前界面长成这样&#xff1a; 配置别名 修改vite.config.js import {defineConfig} from vite import vue from vitejs/plugin-vue import path from "path"// https://vitejs.dev/config/ export default defineConfig({server: {open: true,port: 8088,},plug…

论文阅读【检测】:商汤 ICLR2021 | Deformable DETR

文章目录 论文地址AbstractMotivation技术细节多尺度backbone特征MSDeformAttention 小结 论文地址 Deformable DETR 推荐视频&#xff1a;bilibili Abstract DETR消除对目标检测中许多手工设计的组件的需求&#xff0c;同时表现出良好的性能。然而&#xff0c;由于Transfor…

Odoo 17 仪表盘开发指南:打造高效的数据可视化中心

在现代企业管理中,数据驱动的决策至关重要。Odoo 17 提供了强大的平台来构建自定义仪表板, 适用于数据统计、工作台、驾驶舱、数据可视化等场景,以便用户能够一目了然地监控关键指标并做出及时反应。本文将介绍如何在 Odoo 17 中开发一个灵活且高度定制化的仪表盘系统,包括…

12_TypeScript 模块 以及 模块化封装DB 库

TypeScript 模块 1、模块中暴露方法12、模块中暴露方法23、模块中暴露方法34、封装[上一节的db 库](https://blog.csdn.net/qq_46143850/article/details/140664100)5、TypeScript 命名空间 模块的概念&#xff08;官方&#xff09;&#xff1a; 关于术语的一点说明&#xff1a…

C语言 -- 动态内存管理

C语言 -- 动态内存管理 1. 为什么要有动态内存分配2. malloc 和 free2.1 malloc2.2 free 3. calloc 和 realloc3.1 calloc3.2 realloc 4. 常见的动态内存的错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释放一块动…

05 capture软件创建元器件库(以STM32为例)

05 创建元器件库_以STM32为例 一、新建原理图库文件二、新建器件三、开始创建元器件 一些IC类元件&#xff0c;需要自己创建元器件库。 先看视频&#xff0c;然后自己创建STM32F103C8T6的LQFP48的元器件。 STM32F103C8T6是目前为止&#xff0c;自己用的最多的芯片。 先要有数据…

Qt自定义MessageToast

效果&#xff1a; 文字长度自适应&#xff0c;自动居中到parent&#xff0c;会透明渐变消失。 CustomToast::MessageToast(QS("最多添加50张图片"),this);1. CustomToast.h #pragma once#include <QFrame>class CustomToast : public QFrame {Q_OBJECT pub…

【学习笔记】解决Serial Communication Library编译问题

【学习笔记】解决编译 Serial Communication Library 时的 Catkin 依赖问题 Serial Communication Library 是一个用 C 编写的用于连接类似 rs-232 串口的跨平台库。它提供了一个现代的 C 接口&#xff0c;它的工作流程设计在外观和感觉上与 PySerial 相似&#xff0c;但串口速…

ControlNet on Stable Diffusion

ControlNet on Stable Diffusion 笔记来源&#xff1a; 1.Adding Conditional Control to Text-to-Image Diffusion Models 2.How to Use OpenPose & ControlNet in Stable Diffusion 3.ControlNet与DreamBooth&#xff1a;生成模型的精细控制与主体保持 4.Introduction t…

光猫设置桥接 路由器pppoe拨号 设置正常访问光猫 (openwrt)

网络信息展示 光猫桥接很简单吧&#xff0c;就不说了。先来列出修改前的网络接口和网络信息。 光猫192.168.1.1&#xff0c;openwrt 10.0.0.0/8 初始配置 需要记录的信息&#xff1a;WAN的网络设备&#xff08;eth1&#xff09;&#xff0c;光猫的IP&#xff08;192.168.1.1&am…

使用法国云手机进行面向法国的社媒营销

在当今数字化和全球化的时代&#xff0c;社交媒体已经成为企业营销和拓展市场的重要工具。对于想进入法国市场的企业来说&#xff0c;如何在海外社媒营销中脱颖而出、抓住更多的市场份额&#xff0c;成为了一个关键问题。法国云手机正为企业提供全新的营销工具&#xff0c;助力…

《人工智能大语言模型技术发展研究报告(2024)》【下载】

《人工智能大语言模型技术发展研究报告&#xff08;2024&#xff09;》下载 自2023年起&#xff0c;大模型技术产品的快速迭代和升级&#xff0c;已经成为全球科技竞争的关键因素。由中国软件评测中心发布的《人工智能大语言模型技术发展研究报告&#xff08;2024&#xff09;》…

kafka详解及应用场景介绍

Kafka架构 Kafka架构&#xff0c;由多个组件组成&#xff0c;如下图所示&#xff1a; 主要会包含&#xff1a;Topic、生产者、消费者、消费组等组件。 服务代理&#xff08;Broker&#xff09; Broker是Kafka集群中的一个节点&#xff0c;每个节点都是一个独立的Kafka服务器…

反激Flyback从逆向到初步设计(UC2844)

一.Flyback基本拓扑 国标gb/t 12325-2008《电能质量供电电压偏差》规定&#xff1a;220v单向供电电压偏差为标称电压的-10%&#xff0c;7%。 对应220V的标称电压&#xff0c;其浮动范围是在198~235.4V。以下运算均基于此规定进行。 首先220V进入EMI模块&#xff0c;消除差模干扰…

【Docker】Windows11环境下的安装

前置依赖环境配置 确保虚拟化开启 搜索栏直接搜索如下功能 勾选下面两个选项&#xff0c;确定 重启电脑&#xff0c;以管理员身份打开PowerShell wsl --status wsl --update打开微软应用商店选择一个Ubuntu版本下载并打开 输入一个用户名和密码 然后就可以在Windows下使…

FlowUs与生成式AI结合的未来展望:智能助手问问AI chat与自主代理Agent的应用

生成式AI在对话系统&#xff08;Chat&#xff09;和自主代理&#xff08;Agent&#xff09;中的应用将会带来显著的技术进步和商业机会。 对话系统&#xff08;Chat&#xff09; 对话系统是一种人工智能软件&#xff0c;它能够模拟人类对话&#xff0c;通过自然语言处理&…

go-kratos 学习笔记(7) 服务发现服务间通信grpc调用

服务发现 Registry 接口分为两个&#xff0c;Registrar 为实例注册和反注册&#xff0c;Discovery 为服务实例列表获取 创建一个 Discoverer 服务间的通信使用的grpc&#xff0c;放到data层&#xff0c;实现的是从uses服务调用orders服务 app/users/internal/data.go 加入 New…