【Linux】基础IO(二)
📝前言:
上篇文章我们对Linux的基础IO有了一定的了解,这篇文章我们来讲讲IO更底层的东西:
- 重定向及其原理
- 感受
file_operation
- 文件缓冲区
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
目录
- 一,重定向及其原理
- 系统中的重定向
- 输出重定向
- 输入重定向
- 重定向的原理
- dup2系统调用
- myshell中添加重定向操作
- 二,感受 file_operation
- 三,文件缓冲区
- 意义
- 缓冲区刷新
- 数据在缓冲区的流动
- 系统调用 write
- 库函数
- 示例一
- 示例二
一,重定向及其原理
系统中的重定向
简单回顾一下。
输出重定向
- 覆盖输出(>)
echo "Hello, World!" > test.txt
这会把命令的输出(原来echo
命令的输出是到显示器上的)写入文件,如果文件原本就存在,内容会被覆盖。
- 追加输出(>>)
echo "Append this line" >> test.txt
此操作会把命令的输出添加到文件末尾,不会覆盖原有的内容。
输入重定向
- 标准输入重定向(<)
wc -l < test.txt
该命令会从文件中读取输入,而不是从键盘获取。(将命令的输入源从默认的键盘,转变为文件或者其他命令的输出)
重定向的原理
以输出重定向>
为例:
可见,重定向操作的本质就是,把原本要输出到a
文件流的数据,输出到了b
文件流。
这是什么做到的呢?
知识储备:
- 文件描述符的分配原则(找最小没被分配的)
- C语言的输入输出库函数底层是对系统调用的封装,传的是固定的文件描述符编号
- 以
printf
为例,假如printf
封装的是write
,向标准输出流输出,则fd == 1
(这个在C语言库里实现是固定死的)
- 以
以ls > myfile.txt
为例,重定向的流程就是:
- 先用
myfile.txt
的fd
覆盖文件描述表里下标为1
的位置- 先创建
myfile.txt
,则fd == 3
,然后再直接覆盖 - 或者:先
close(1)
,然后open
,此时给myfile.txt
分配的fd
就是1
- 先创建
下面以直接覆盖为例:
- 此时,
1
和3
都指向myfile.txt
- 然后,
ls
本来是往stdout
打印(也就是往fd == 1
),这个是固定死的。ls
的打印只认fd == 1
- 但是,此时
fd == 1
指向myfile.txt
,于是就向myfile.txt
里面打印了
总结,重定向操作就是:
- 让文件描述表的下标“重定向”(指向不同文件)
- 由于上层的接口不知道下层指向改变了,于是原来输出到原来下标对应的文件的内容,就输出到了新执行的文件里面
示例:
我们写用重定向操作的时候,还可以指定要重定向的文件描述表的下标。如:
ls 1> myfile.txt
- 让文件描述表
1
指向myfile.txt
。ls
往1
输出,此时1
指向myfile.txt
,于是就往myfile.txt
输出了。
- 让文件描述表
ls 1>myfile.txt 2>&1
(实现重定向两个)1
重定向到myfile.txt
,2
再重定向到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
系统调用,直接输出到内核文件缓冲区 - 但是对应库函数
fwrite
和printf
,输出的内容还在用户层缓冲区,未被刷新。 - 没有
fork
的时候,在进程结束的时候,被刷新到内核文件缓冲区(只有一份) - 有
fork
的时候- 父子各自刷一份语言层缓冲区
fwrie
和printf
写的内容 - 但系统调用的是只有父进程的那一份。因为,代码已经被执行过了,并且数据是直接写到内核文件缓冲区的
- 父子各自刷一份语言层缓冲区
- 对于
- 对于结果1 和 结果3:
- 因为没有重定向,显示器是行刷新,遇到
\n
就刷新 - 所以在代码执行到
fork
的时候,语言层的缓冲区的内容已经被刷出来了 - 并且
fork
在最后,前面的代码只由父进程执行一次
- 因为没有重定向,显示器是行刷新,遇到
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!
相关文章:

【Linux】基础IO(二)
📝前言: 上篇文章我们对Linux的基础IO有了一定的了解,这篇文章我们来讲讲IO更底层的东西: 重定向及其原理感受file_operation文件缓冲区 🎬个人简介:努力学习ing 📋个人专栏:Linux…...
SpringBoot异步处理@Async深度解析:从基础到高阶实战
一、异步编程基础概念 1.1 同步 vs 异步 特性同步异步执行方式顺序执行,阻塞调用非阻塞,调用后立即返回线程使用单线程完成所有任务多线程并行处理响应性较差,需等待前任务完成较好,可立即响应新请求复杂度简单直观较复杂&#…...

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

SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!
SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁! 目录 SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于SHAP分析…...
Tcp 通信简单demo思路
Server 端 -------------------------- 初始化部分 ------------------------------- 1.创建监听套接字: 使用socket(协议家族,套接字的类型,0) 套接字类型有 SOCK_STREAM:表示面向连接的套接字(Tcp协议)&…...
MySQL 8.0安装(压缩包方式)
MySQL 8.0安装(压缩包方式) 下载安装包并解压 下载 https://dev.mysql.com/downloads/mysql/可关注“后端码匠”回复“MySQL8”关键字获取 解压(我解压到D:\dev\mysql-8.4.5-winx64目录下) 创建mysql服务 注意,这步之前一定要保证自己电…...
常见标签语言的对比
XML、JSON 和 YAML 是常见的数据序列化格式 相同点 结构化数据表示 三者均支持嵌套结构,能描述复杂的数据层级关系(如对象、数组、键值对)。跨平台兼容性 均为纯文本格式,可被多种编程语言解析,适用于跨系统数据交换…...

知名人工智能AI培训公开课内训课程培训师培训老师专家咨询顾问唐兴通AI在金融零售制造业医药服务业创新实践应用
AI赋能未来工作:引爆效率与价值创造的实战营 AI驱动的工作革命:从效率提升到价值共创 培训时长: 本课程不仅是AI工具的操作指南,更是面向未来的工作方式升级罗盘。旨在帮助学员系统掌握AI(特别是生成式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 通讯协议文档,适用于 BLE 5.0 及以上标准,兼容 iOS / Android 平台: 智能手表蓝牙 GATT 通讯协议文档 文档版本:V1.0 编写日期:2025年xx月xx日 产品型号: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的目的为了更快的搭载工程,使用Rt-Thread丰富的组件和第三方包资源,解耦硬件,在更换芯片时可以移植应用层代码。你是要RTT的目的什么呢? 文章项目背景 以STM32L475RCT6为例 RTC使用的为LSE外部低速32 .756k Hz 的…...
MySQL解决主从复制的报错问题
MySQL 8.4 非 GTID 模式部分数据库主从复制指南 在进行MySQL 8.4非GTID模式下部分数据库主从复制时,以下是详细的操作步骤以及对应的执行位置说明,还有报错处理方法介绍: 操作步骤 1. 备份主库指定数据库(db1、db2)…...

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

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

用python清除PDF文件中的水印(Adobe Acrobat 无法删除)
学校老师发的资料,有时候会带水印,有点强迫症的都想给它去掉。用Adobe Acrobat试了下,检测不到水印,无法删除!分析发现原来这类PDF文件是用word编辑的,其中的水印是加在了页眉中! 自己动手想办法…...
kotlin 数据类
一 kotlin数据类与java普通类区别 Kotlin 的 data class 与 Java 中的普通类(POJO)相比,确实大大减少了样板代码(boilerplate),但它的优势不止于自动生成 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数据库
数据管理和分析的领域,将Excel中的数据导入到Oracle数据库是一个常见的需求,无论是为了利用Oracle强大的数据处理能力,还是为了实现数据的集中存储和管理,这一过程都需要一定的步骤和技巧,本文将详细介绍如何从Excel导…...
PyTorch API 6 - 编译、fft、fx、函数转换、调试、符号追踪
文章目录 torch.compiler延伸阅读 torch.fft快速傅里叶变换辅助函数 torch.func什么是可组合的函数变换?为什么需要可组合的函数变换?延伸阅读 torch.futurestorch.fx概述编写转换函数图结构快速入门图操作直接操作计算图使用 replace_pattern() 进行子图…...

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

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

Webug4.0通关笔记25- 第30关SSRF
目录 一、SSRF简介 1.SSRF原理 2.渗透方法 二、第30关SSRF渗透实战 1.打开靶场 2.渗透实战 (1)Windows靶场修复 (2)Docker靶场修复 (3)获取敏感文件信息 (4)内网端口与服务…...
Android学习总结之线程池篇
一、线程池参数调优实战真题 真题 1:直播 APP 弹幕加载线程池设计 题目描述:直播 APP 需要实时加载弹幕数据(网络请求,IO 密集型),同时渲染弹幕视图(UI 操作需切主线程)࿰…...

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

MacOS 上构建 gem5
MacOS 中只存在 python3,但是scons 只认 python,不在 系统中创建 软连接,一个是因为比较难操作;另一个是尽量不要更改系统。所以独立构件python 和scons: 1,安装python 下载源代码: 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函数:字符串函数
目录 为什么需要字符串函数? 1️⃣ LENGTH(str) — 这个字符串有几个“字节”? 2️⃣ CHAR_LENGTH(str) — 这个字符串有几个“字符”? 3️⃣ TRIM(str) — 把两边的空格剪掉 4️⃣ REPLACE(str, a, b) — 把 a 替换成 b 使用这些函数时…...
DAY04:Vue.js 指令与事件处理深度解析之从基础到实战
1. 指令系统核心概念 1.1 插值表达式与基础指令 Vue.js 的指令系统是其响应式编程模型的核心,我们首先从最基础的插值表达式开始: <div id"app"><!-- 基础文本插值 --><p>{{ message }}</p><!-- JavaScript 表达…...