当前位置: 首页 > article >正文

【Linux】基础IO(二)

📝前言:

上篇文章我们对Linux的基础IO有了一定的了解,这篇文章我们来讲讲IO更底层的东西

  1. 重定向及其原理
  2. 感受file_operation
  3. 文件缓冲区

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


目录

  • 一,重定向及其原理
    • 系统中的重定向
      • 输出重定向
      • 输入重定向
    • 重定向的原理
    • dup2系统调用
    • myshell中添加重定向操作
  • 二,感受 file_operation
  • 三,文件缓冲区
    • 意义
    • 缓冲区刷新
    • 数据在缓冲区的流动
      • 系统调用 write
      • 库函数
      • 示例一
      • 示例二

一,重定向及其原理

系统中的重定向

简单回顾一下。

输出重定向

  1. 覆盖输出(>)
echo "Hello, World!" > test.txt

这会把命令的输出(原来echo命令的输出是到显示器上的)写入文件,如果文件原本就存在,内容会被覆盖。

  1. 追加输出(>>)
echo "Append this line" >> test.txt

此操作会把命令的输出添加到文件末尾,不会覆盖原有的内容。

输入重定向

  1. 标准输入重定向(<)
wc -l < test.txt

该命令会从文件中读取输入,而不是从键盘获取。(将命令的输入源从默认的键盘,转变为文件或者其他命令的输出)

重定向的原理

以输出重定向>为例:

可见,重定向操作的本质就是,把原本要输出到a文件流的数据,输出到了b文件流。

这是什么做到的呢?
知识储备:

  1. 文件描述符的分配原则(找最小没被分配的)
  2. C语言的输入输出库函数底层是对系统调用的封装,传的是固定的文件描述符编号
    • printf为例,假如printf封装的是write,向标准输出流输出,则fd == 1(这个在C语言库里实现是固定死的)

ls > myfile.txt为例,重定向的流程就是:

  • 先用myfile.txtfd覆盖文件描述表里下标为1的位置
    • 先创建myfile.txt,则fd == 3,然后再直接覆盖
    • 或者:先close(1),然后open,此时给myfile.txt分配的fd就是1

下面以直接覆盖为例:

  • 此时,13都指向myfile.txt
  • 然后,ls本来是往stdout打印(也就是往fd == 1),这个是固定死的。ls的打印只认fd == 1
  • 但是,此时fd == 1指向myfile.txt,于是就向myfile.txt里面打印了

在这里插入图片描述
总结,重定向操作就是:

  • 让文件描述表的下标“重定向”(指向不同文件)
  • 由于上层的接口不知道下层指向改变了,于是原来输出到原来下标对应的文件的内容,就输出到了新执行的文件里面

示例:
在这里插入图片描述
我们写用重定向操作的时候,还可以指定要重定向的文件描述表的下标。如:

  • ls 1> myfile.txt
    • 让文件描述表1指向myfile.txtls1输出,此时1指向myfile.txt,于是就往myfile.txt输出了。
  • ls 1>myfile.txt 2>&1(实现重定向两个)
    • 1重定向到myfile.txt2再重定向到1(也相当于指向myfile.txt

dup2系统调用

dup2,用于复制文件描述符:

  • 原型:int dup2(int oldfd, int newfd);
  • newfd原来的要关闭
  • 然后把下标为oldfd的文件指针复制到文件描述符表下标为newfd的位置(直接覆盖)

使用示例:

 60 int main()61 {62     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);63     dup2(fd, 1);64     printf("hello world!\n");                                                                                                                                                                                65     return 0;66 } 

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

myshell中添加重定向操作

知识储备:

  • 进程程序替换,不会影响被打开的文件(即:不会影响文件描述表)

重要代码:

 35 // 全局变量与重定向有关36 #define NoneRedir 0 37 #define InputRedir 138 #define OutputRedir 239 #define AppRedir 340 int redir = NoneRedir;41 char *filename = nullptr;229 char* TrimSpace(char* pos)
230 {
231     while(isspace(*pos))
232         pos++;                                                                                                                                                                                               
233     return pos;
234 }236 void ParseRedir(char command_buffer[], int len)
237 {
238     int end = len - 1;
239     while(end >= 0)
240     {
241         if(command_buffer[end] == '<')
242         {
243             redir = InputRedir;
244             command_buffer[end] = 0;
245             filename = TrimSpace(&command_buffer[end] + 1);
246             break;
247         }
248         else if(command_buffer[end] == '>')
249         {
250             if(command_buffer[end-1] == '>')
251             {
252                 redir = AppRedir;
253                 command_buffer[end] = 0;
254                 command_buffer[end-1] = 0;
255                 filename = TrimSpace(&command_buffer[end] + 1);
256                 break;
257             }
258             else
259             {
260                 redir = OutputRedir;
261                 command_buffer[end] = 0;
262                 filename = TrimSpace(&command_buffer[end] + 1);
263                 break;
264             }
265         }
266         else
267         {
268             end--;
269         }
270     }
271 }273 void DoRedir()
274 {
275     if(redir == InputRedir)
276     {
277         if(filename)
278         {
279             int fd = open(filename, O_RDONLY);
280             if(fd < 0) exit(2);
281             dup2(fd, 0);
282         }
283         else exit(1);
284     }
285     else if(redir == OutputRedir)
286     {
287         if(filename)
288         {
289             int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
290             if(fd < 0) exit(4);
291             dup2(fd, 1);
292         }
293         else exit(3);
294     }
295     else if(redir == AppRedir)
296     {
297         if(filename)
298         {
299             int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
300             if(fd < 0) exit(6);
301             dup2(fd, 1);
302         }
303         else exit(5);
304     }
305     else
306     {}
307 }

注意:ParseRedir是在分析指令的时候调用,DoRedir()则是在执行指令之前,DoRedir以后就会影响指令的IO结果

运行结果:

在这里插入图片描述

二,感受 file_operation

理解一下:一切皆文件

  • 在Linux下,一切皆文件。是Linux的重要设计理念。
  • 不同硬件设备的访问是不一样的,但是Linux都把他们抽象成了文件(如进程、磁盘、显示器、键盘…)
  • 使得用户可以使用统一的文件操作接口(如open、read、write、close等)来访问这些资源

如何实现的呢?
在这里插入图片描述

  • 在Linux下,大部分设备都被抽象成了struct file(对于不同类型的设备,struct file的属性和操作函数可能会有所不同)
  • 但,struct file 中都有一个 f_op 指针,指向了⼀个 file_operations 结构体
  • struct file_operations 是一个函数指针集合,定义了对设备的各种操作方法
  • 尽管不同设备的访问方式不同,但是在 file_operations这一层,都会有相同的,如open、read、write、close等方法
  • 当访问一个设备的时候(如对磁盘进行read),进程会找到磁盘的struct file,通过f_op 指针找到file_operations,然后调用里面的read,这个read的是磁盘提供的read
  • 对于file_operations的上层(进程)而言,对设备read,都是去file_operations里面找read,操作一样(所以,一切皆文件)
  • 对应进程的再上层(用户),也是一切皆文件
  • 本质:找到read以后,调用不同设备所提供的不同方法

总结:在file_operations层统一接口

三,文件缓冲区

意义

为什么要有缓冲区?

  • 缓冲区用来存放文件的内容。
  • 因为系统调用是有成本的,现将内容存放到缓冲区,而不是直接刷新
  • 可以减少系统调用的次数,提高使用者的效率。

缓冲区刷新

前置知识储备:

  • 对应文件而言,语言层有一个FILE结构体,在内核层也有一个struct file结构体
  • 语言层文件有缓冲区(FILE维护),内核层文件也有缓冲区(struct file维护)
  • 不同文件通常有各自独立的缓冲区
  • 对于C语言的文件缓冲区,刷新缓冲区,就是把数据拷贝给内核文件缓冲区(一旦拷贝过去,我们就认为已经刷新好了,内核层的刷新策略复杂,由OS自主决定,我们不研究)
  • C语言文件缓冲区的刷新条件
    • 立即刷新:约等于没有缓冲区(如:stderr)
    • 全刷新:当缓冲区满的时候才刷新(普通文件通常是)
    • 行刷新:满了一行的长度 / 遇到换行符\n(显示器通常是)
  • 当然fflush可以强制刷新,进程退出也会自动刷新缓冲区(这些说的都是从语言层刷新到内核层,刷到内核层我们就认为刷好了,能看见了)

数据在缓冲区的流动

系统调用 write

对于系统调用而言,write的数据,直接到内核文件缓冲区,然后被刷新。

库函数

对于printf而言

  • printf并不是直接写到内核文件缓冲区额,而是先把数据输出到了语言层的缓冲区
  • 当遇到刷新缓冲区时,要通过fd(找到对应的文件),调用系统调用比如(write),把语言层缓冲区的内容拷贝给内核文件缓冲区,才完成真正的刷新

我们可以看下面几个代码:

示例一

代码1:

 68 int main()69 {70     close(1);71     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);                                                                                                                                                      72     printf("hello world!\n");73     return 0;74 }

在这里插入图片描述
成功写入

代码2:

 68 int main()69 {70     close(1);71     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);72     printf("hello world!\n");73     close(1);                                                                                                                                                                                                74     return 0;75 }

在这里插入图片描述
写入失败
为什么?

  • 首先,重定向会改变刷新条件
    • printf("hello world!\n"); 如果往显示器上输出,显示器是行刷新,但是重定向以后 1 指向普通文件了,是全刷新
  • 现象解释
    • printf往文件log.txt里面输出了"hello world!\n",但是没有刷新
    • 在进程结束前close(1)close会直接触发fflush,但文件已关闭,文件描述符1已失效,找不到对应的文件和内核文件缓冲区,刷新失败!

示例二

代码1:

 78 #include <stdio.h>79 #include <string.h>80 int main()81 {82     const char *msg0="hello printf\n";83     const char *msg1="hello fwrite\n";84     const char *msg2="hello write\n";85     printf("%s", msg0);86     fwrite(msg1, strlen(msg0), 1, stdout);87     write(1, msg2, strlen(msg2));88     // fork();                                                                                                                                                                                               89     return 0;90 }

输出结果1(无重定向):
在这里插入图片描述
输出结果2(有重定向):
在这里插入图片描述

代码2(结尾加个fork):

 78 #include <stdio.h>79 #include <string.h>80 int main()81 {82     const char *msg0="hello printf\n";83     const char *msg1="hello fwrite\n";84     const char *msg2="hello write\n";85     printf("%s", msg0);86     fwrite(msg1, strlen(msg0), 1, stdout);87     write(1, msg2, strlen(msg2));88     fork();                                                                                                                                                                                                  89     return 0;90 }

输出结果3(无重定向):
在这里插入图片描述
输出结果4(有重定向):
在这里插入图片描述

为什么会这样呢?

  • 先解释 结果2 和 结果 4
    • fork会创建子进程,文件描述符对应的用户层缓冲区会被复制,内核层文件缓冲区会被共享
    • 重定向时,往log.txt文件里面输出
      • 对于write系统调用,直接输出到内核文件缓冲区
      • 但是对应库函数fwriteprintf,输出的内容还在用户层缓冲区,未被刷新。
      • 没有fork的时候,在进程结束的时候,被刷新到内核文件缓冲区(只有一份)
      • fork的时候
        • 父子各自刷一份语言层缓冲区fwrieprintf写的内容
        • 但系统调用的是只有父进程的那一份。因为,代码已经被执行过了,并且数据是直接写到内核文件缓冲区的
  • 对于结果1 和 结果3:
    • 因为没有重定向,显示器是行刷新,遇到\n就刷新
    • 所以在代码执行到fork的时候,语言层的缓冲区的内容已经被刷出来了
    • 并且fork在最后,前面的代码只由父进程执行一次

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

相关文章:

【Linux】基础IO(二)

&#x1f4dd;前言&#xff1a; 上篇文章我们对Linux的基础IO有了一定的了解&#xff0c;这篇文章我们来讲讲IO更底层的东西&#xff1a; 重定向及其原理感受file_operation文件缓冲区 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux…...

SpringBoot异步处理@Async深度解析:从基础到高阶实战

一、异步编程基础概念 1.1 同步 vs 异步 特性同步异步执行方式顺序执行&#xff0c;阻塞调用非阻塞&#xff0c;调用后立即返回线程使用单线程完成所有任务多线程并行处理响应性较差&#xff0c;需等待前任务完成较好&#xff0c;可立即响应新请求复杂度简单直观较复杂&#…...

【生存技能】ubuntu 24.04 如何pip install

目录 原因解决方案说明关于忽略系统路径 在接手一个新项目需要安装python库时弹出了以下提示: 原因 这个报错是因为在ubuntu中尝试直接使用 pip 安装 Python 包到系统环境中&#xff0c;ubuntu 系统 出于稳定性考虑禁止了这种操作 这里的kali是因为这台机器的用户起名叫kali…...

SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!

SHAP分析&#xff01;Transformer-GRU组合模型SHAP分析&#xff0c;模型可解释不在发愁&#xff01; 目录 SHAP分析&#xff01;Transformer-GRU组合模型SHAP分析&#xff0c;模型可解释不在发愁&#xff01;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于SHAP分析…...

Tcp 通信简单demo思路

Server 端 -------------------------- 初始化部分 ------------------------------- 1.创建监听套接字&#xff1a; 使用socket(协议家族&#xff0c;套接字的类型&#xff0c;0) 套接字类型有 SOCK_STREAM&#xff1a;表示面向连接的套接字&#xff08;Tcp协议&#xff09;&…...

MySQL 8.0安装(压缩包方式)

MySQL 8.0安装(压缩包方式) 下载安装包并解压 下载 https://dev.mysql.com/downloads/mysql/可关注“后端码匠”回复“MySQL8”关键字获取 解压&#xff08;我解压到D:\dev\mysql-8.4.5-winx64目录下&#xff09; 创建mysql服务 注意&#xff0c;这步之前一定要保证自己电…...

常见标签语言的对比

XML、JSON 和 YAML 是常见的数据序列化格式 相同点 结构化数据表示 三者均支持嵌套结构&#xff0c;能描述复杂的数据层级关系&#xff08;如对象、数组、键值对&#xff09;。跨平台兼容性 均为纯文本格式&#xff0c;可被多种编程语言解析&#xff0c;适用于跨系统数据交换…...

知名人工智能AI培训公开课内训课程培训师培训老师专家咨询顾问唐兴通AI在金融零售制造业医药服务业创新实践应用

AI赋能未来工作&#xff1a;引爆效率与价值创造的实战营 AI驱动的工作革命&#xff1a;从效率提升到价值共创 培训时长&#xff1a; 本课程不仅是AI工具的操作指南&#xff0c;更是面向未来的工作方式升级罗盘。旨在帮助学员系统掌握AI&#xff08;特别是生成式AI/大语言模型…...

Qt Creator 配置 Android 编译环境

Qt Creator 配置 Android 编译环境 环境配置流程下载JDK修改Qt Creator默认android配置文件修改sdk_definitions.json配置修改的内容 Qt Creator配置 异常处理删除提示占用编译报错连接安卓机调试APP闪退 环境 Qt Creator 版本 qtcreator-16.0.1Win10 嗯, Qt这个开发环境有点难…...

智能手表蓝牙 GATT 通讯协议文档

以下是一份适用于智能手表的 蓝牙 GATT 通讯协议文档&#xff0c;适用于 BLE 5.0 及以上标准&#xff0c;兼容 iOS / Android 平台&#xff1a; 智能手表蓝牙 GATT 通讯协议文档 文档版本&#xff1a;V1.0 编写日期&#xff1a;2025年xx月xx日 产品型号&#xff1a;Aurora Wat…...

AWS EC2源代码安装valkey命令行客户端

sudo yum -y install openssl-devel gcc wget https://github.com/valkey-io/valkey/archive/refs/tags/8.1.1.tar.gz tar xvzf 8.1.1.tar.gz cd valkey-8.1.1/ make distclean make valkey-cli BUILD_TLSyes参考 Connecting to nodes...

RT-THREAD RTC组件中Alarm功能驱动完善

使用Rt-Thread的目的为了更快的搭载工程&#xff0c;使用Rt-Thread丰富的组件和第三方包资源&#xff0c;解耦硬件&#xff0c;在更换芯片时可以移植应用层代码。你是要RTT的目的什么呢&#xff1f; 文章项目背景 以STM32L475RCT6为例 RTC使用的为LSE外部低速32 .756k Hz 的…...

MySQL解决主从复制的报错问题

MySQL 8.4 非 GTID 模式部分数据库主从复制指南 在进行MySQL 8.4非GTID模式下部分数据库主从复制时&#xff0c;以下是详细的操作步骤以及对应的执行位置说明&#xff0c;还有报错处理方法介绍&#xff1a; 操作步骤 1. 备份主库指定数据库&#xff08;db1、db2&#xff09;…...

用ffmpeg压缩视频参数建议

注意:代码中的斜杠\可以删除 一、基础压缩命令&#xff08;画质优先) libx265​​推荐配置 ffmpeg -i input.mp4 -c:v libx265 -crf 25 -preset medium -c:a aac -b:a 128k output.mp4-crf&#xff1a;建议25-28&#xff08;值越小画质越高&#xff09; -preset&#xff1a;平…...

输入顶点坐标输出立方体长宽高的神经网络 Snipaste贴图软件安装

写一个神经网络&#xff0c;我输入立方体投影线段的三视图坐标&#xff0c;输出分类和长宽高 放这了明天接着搞 -------------------------------------------- 开搞 然而我的数据是这样的 winget install Snipaste f1启动&#xff0c;双击贴图隐藏 用右边4个数据做输入…...

用python清除PDF文件中的水印(Adobe Acrobat 无法删除)

学校老师发的资料&#xff0c;有时候会带水印&#xff0c;有点强迫症的都想给它去掉。用Adobe Acrobat试了下&#xff0c;检测不到水印&#xff0c;无法删除&#xff01;分析发现原来这类PDF文件是用word编辑的&#xff0c;其中的水印是加在了页眉中&#xff01; 自己动手想办法…...

kotlin 数据类

一 kotlin数据类与java普通类区别 Kotlin 的 data class 与 Java 中的普通类&#xff08;POJO&#xff09;相比&#xff0c;确实大大减少了样板代码&#xff08;boilerplate&#xff09;&#xff0c;但它的优势不止于自动生成 getter/setter、copy()、equals()、toString()&am…...

豆瓣电影Top250数据工程实践:从爬虫到智能存储的技术演进(含完整代码)

目录 引言:当豆瓣榜单遇见大数据技术 项目文档 1.1 选题背景 1.2 项目目标 2. 项目概述 2.1 系统架构设计 2.2 技术选型 2.3 项目环境搭建 2.3.1 基础环境准备 2.3.2 爬虫环境配置 2.3.3 Docker安装ES连接Kibana 安装IK插件 2.3.4 vscode依赖服务安装 3. 核心模…...

把Excel数据文件导入到Oracle数据库

数据管理和分析的领域&#xff0c;将Excel中的数据导入到Oracle数据库是一个常见的需求&#xff0c;无论是为了利用Oracle强大的数据处理能力&#xff0c;还是为了实现数据的集中存储和管理&#xff0c;这一过程都需要一定的步骤和技巧&#xff0c;本文将详细介绍如何从Excel导…...

PyTorch API 6 - 编译、fft、fx、函数转换、调试、符号追踪

文章目录 torch.compiler延伸阅读 torch.fft快速傅里叶变换辅助函数 torch.func什么是可组合的函数变换&#xff1f;为什么需要可组合的函数变换&#xff1f;延伸阅读 torch.futurestorch.fx概述编写转换函数图结构快速入门图操作直接操作计算图使用 replace_pattern() 进行子图…...

Dagster Pipes系列-1:调用外部Python脚本

本文是"Dagster Pipes教程"的第一部分&#xff0c;介绍如何通过Dagster资产调用外部Python脚本并集成到数据管道中。首先&#xff0c;创建Dagster资产subprocess_asset&#xff0c;利用PipesSubprocessClient资源执行外部脚本external_code.py&#xff0c;实现跨进程…...

python shutil 指定文件夹打包文件为 zip 压缩包

python shutil 指定文件夹打包文件为 zip 压缩包&#xff0c;具体代码如下&#xff1a; import shutil# 指定要打包的文件夹路径 src_doc ./test# 指定输出的压缩包文件名&#xff08;不包含扩展名&#xff09; output_filename testfromat_ zip# 打包并压缩文件夹为 ZIP …...

Webug4.0通关笔记25- 第30关SSRF

目录 一、SSRF简介 1.SSRF原理 2.渗透方法 二、第30关SSRF渗透实战 1.打开靶场 2.渗透实战 &#xff08;1&#xff09;Windows靶场修复 &#xff08;2&#xff09;Docker靶场修复 &#xff08;3&#xff09;获取敏感文件信息 &#xff08;4&#xff09;内网端口与服务…...

Android学习总结之线程池篇

一、线程池参数调优实战真题 真题 1&#xff1a;直播 APP 弹幕加载线程池设计 题目描述&#xff1a;直播 APP 需要实时加载弹幕数据&#xff08;网络请求&#xff0c;IO 密集型&#xff09;&#xff0c;同时渲染弹幕视图&#xff08;UI 操作需切主线程&#xff09;&#xff0…...

OpenCV 中用于背景分割的一个类cv::bgsegm::BackgroundSubtractorLSBP

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::bgsegm::BackgroundSubtractorLSBP 是 OpenCV 中用于背景分割的一个类&#xff0c;它基于局部样本二进制模式&#xff08;Local Sample Bina…...

MacOS 上构建 gem5

MacOS 中只存在 python3&#xff0c;但是scons 只认 python&#xff0c;不在 系统中创建 软连接&#xff0c;一个是因为比较难操作&#xff1b;另一个是尽量不要更改系统。所以独立构件python 和scons&#xff1a; 1&#xff0c;安装python 下载源代码&#xff1a; Python S…...

认识中间件-以及两个简单的示例

认识中间件-以及两个简单的示例 什么是中间件一个响应处理中间件老朋友 nest g如何使用为某个module引入全局引入编写逻辑一个日志中间件nest g mi 生成引入思考代码进度什么是中间件 官方文档 中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及…...

(五)毛子整洁架构(分布式日志/Redis缓存/OutBox Pattern)

文章目录 项目地址一、结构化日志1.1 使用Serilog1. 安装所需要的包2. 注册服务和配置3. 安装Seq服务 1.2 添加分布式id中间件1. 添加中间件2. 注册服务3. 修改Application的LoggingBehavior 二、Redis缓存2.1 添加缓存1. 创建接口ICaching接口2. 实现ICaching接口3. 注册Cachi…...

SQL:MySQL函数:字符串函数

目录 为什么需要字符串函数&#xff1f; 1️⃣ LENGTH(str) — 这个字符串有几个“字节”&#xff1f; 2️⃣ CHAR_LENGTH(str) — 这个字符串有几个“字符”&#xff1f; 3️⃣ TRIM(str) — 把两边的空格剪掉 4️⃣ REPLACE(str, a, b) — 把 a 替换成 b 使用这些函数时…...

DAY04:Vue.js 指令与事件处理深度解析之从基础到实战

1. 指令系统核心概念 1.1 插值表达式与基础指令 Vue.js 的指令系统是其响应式编程模型的核心&#xff0c;我们首先从最基础的插值表达式开始&#xff1a; <div id"app"><!-- 基础文本插值 --><p>{{ message }}</p><!-- JavaScript 表达…...