内建命令:在bash上运行的指令
正常指令:bash建立一个子进程之后,进行进程替换,然后进行指令的执行

新建 bash 会话会自动加载执行配置文件,变量才生效;已运行的 bash 不会主动重读
环境变量配置文件是用shell语言写的
$ i=0;while [$i -le 100]; do echo $i; let i++;done
echo既然是内建命令就不应该磁盘上有文件,但是却有
原因是,因为shell语言有交互和命令两种方式,在交互这个方式的时候,需要echo磁盘文件

基础io
1文件=属性+内容
对内容做操作 fread /fwrite
对属性做操作 fstat
打开文件的本质:
把文件加载到内存
对文件的操作本质是进程对文件的操作
磁盘的管理者是操作系统
文件的读写本质不是通过c语言/c++的库函数来操作的,这些库函数只是为用户提供方便,而是通过文件相关的系统调用接口来实现的

磁盘文件
a.被进程打开的文件
b.没有被进程打开的文件 磁盘上没有被打开的文件。

磁盘是硬件 打开文件的本质就是在访问磁盘。访问硬件-> 操作系统+磁盘驱动 操作系统必须提供系统调用 c语言帮我们封装了系统调用
FILE fopen (const charpath,const char*mode);
句柄

相对路径 = 相对于【进程工作路径】
进程工作路径 = 继承自 bash
和程序放哪 完全没关系
int fclose (FILE &fp) 这个就是关闭文件夹
使用完文件之后一定要进行文件的关闭,要不然会占用内存资源

输入重定向。 >log.txt 这个时候就是打开
echo 默认有\n
w 是清空文件然后写入
a 是追加 在文件末尾追加写入
echo > 输出重定向 是以w形式打开文件之后往里面写入的
echo>> 追加重定向

r r+ w w+ a a+

对文件内容的理解,和对读写的理解
os视角 :abcd\n1234. 文件的内容 彻底想象成为一个一纬数组 char content[]
cat||vim
abcd
1234
你怎么知道你读写到文件的哪个位置了,int index

系统函数
open函数 返回值可以表示文件描述符

句柄就是系统给资源的编号 / 身份证,用来代指文件、窗口、网络连接等资源。
int fd=open(“log.txt”,o_CREAT|O_WRONLY|O_APPEND,0666);

为啥bash指令需要创建一个子进程去执行,而不是bash自己去执行
为了不破坏当前 Shell 环境、实现隔离、保证 Shell 本身持续运行,所以 Bash 默认用子进程执行外部命令。

read函数
ssize_t read(int fd,void *buf,size_t count);

fopen有一下几种打开方式
r r+ w w+ a a+
其实就是内部封装了不同的第二个参数的open函数

fopen返回值是file* 这个叫做句柄 句柄:句柄 = 操作文件的 “钥匙 / 通行证”**
file*是文件描述符
最终权限 = 指定权限 & (~ 掩码)

一个进程可以打开多个文件吗?
可以!
open函数返回值从3开始
1:为什么从3开始
任何一个程序/进程,标准输入输出流
stdin:标准输入,键盘 printf(“stdin:%d\n”,stdin->fileno);0
stdout。标准输出,显示器 printf(“stdout:%d”,stdout->fileno)1
stderr。标准错误 ,显示器printf(“stderr:%d”,stderr->fileno)2
操作系统 只认文件描述符
fprintf(stdout,“helloworld/n”) 这个就相当于printf
FILE是结构体
fd是文件描述符
file结构封装了fd
文件在磁盘上 文件由属性和内容两部分组成
当你打开文件的时候,文件由磁盘上加载(不一定完全加载,可能部分加载)到内存中,读文件/写文件/修改文件
如果你想读文件,必须先把文件的内容加载到内存里面,然后再由cpu把你的数据读上来,读到寄存器里面
读 改 写规则
数据读写,必须先加载到内存中
一个进程可以打开多个文件吗??->
答案是可以 加载文件。加载文件到内存当中
进程要不要管理被打开的文件

操作系统要不要管理被打开的文件
要管理。如何管理:先描述,在组织
在内核当中,被打开的文件交struct file。每一个打开的文件都对应有一个 struct file
struct file 里面有这几部分。 属性数据 缓冲区数据,保存内容数据。 链接字段 struct list head XXX;//链接字段
struct pcb里面有一个部分是struct files_struct struct file fd array[] 文件描述符表
这里面对应的就是文件对象的地址
2:为什么是连续的
3:为什么它可以表示文件
4:fd的本质是什么 文件描述名的本质是数组下标
FILE
fp=fopen(“log.txt”,“w”);
printf(“log.txt:%d”,fp->_fileno);

系统和c进行打通
1.源码验证
2.文件描述符分配规则
从最小的没有被使用的下边开始分配,分配下标需要在数组角度连续
系统调用函数是操作系统内核提供的
3.系统和c的封装关系
函数封装喝系统调用fd之间的关系 在系统层面 只能用fd,只能用系统调用函数
c++ python java任何语言都可以在linux上跑, std::cout std::cin std::cerr 这些是类 必定要封装fd cout往显示器文件里面写内容,在操作系统层面需要知道显示器文件的fd,所以在cout和cin和cerr这些类中封装了fd
解释器虚拟机都是用c++写的 比如说java中写打开一个文件,这个时候虚拟机就解释:调用fopen()函数,返回一个句柄,因为只有操作系统提供系统调用函数,让你去访问
所有语言对应的文件操作,内核原理一样,操作原理不一样
静态库 = 把饭直接装进饭盒带走
好处:走到哪都能吃,不用找饭店
坏处:饭盒很重,饭店换菜了,你得重新装一盒
动态库 = 只带一张饭店地址
好处:很轻,饭店更新菜品,你不用换地址
坏处:吃饭时必须找到饭店才行
因为不同操作系统的系统调用函数不一样,所以c标准库在不同的环境之下也是不一样的
c语言是依靠动静态库来实现的,python是依靠解释器来实现的,java是通过虚拟机来实现的
4.关闭fd的实验

每一个进程都有自己独立的文件描述符表
终极结论
从物理设备 / 屏幕 / 键盘来说:是同一个!
从内核结构来说:不是同一个文件表!
我给你拆开讲,一听就懂👇

  1. 你看到的结果:是同一个!
    进程 A 往 stdout(1) 输出 → 屏幕显示
    进程 B 往 stdout(1) 输出 → 同一个屏幕显示
    所有进程默认都往同一个屏幕打印
    所以你看到的输出设备是同一个!
  2. 内核内部:不是同一个打开文件!
    Linux 内核有三层结构:
    进程文件描述符表(每个进程独立)
    打开文件表(struct file)
    磁盘 inode
    真实情况:
    进程 A 的 fd 1 → 打开文件表 A
    进程 B 的 fd 1 → 打开文件表 B
    但它们最终指向同一个屏幕设备(tty)
    重点:
    每个进程默认都会独立打开 0、1、2 这三个标准文件,
    它们用的是不同的 struct file,
    但最终指向同一个物理设备(屏幕 / 键盘)。
  3. 最经典考试答案(背这句)
    不同进程的标准输入 / 输出:
    指向的物理设备(屏幕、键盘)是同一个,
    但在内核中属于不同的打开文件(独立的 struct file),
    互不影响。
  4. 最关键区别
    ✅ 父子进程(fork 后):
    共享同一个打开文件表!
    (所以重定向会一起生效)
    ✅ 无关进程(你开两个终端):
    独立打开文件表!
    (你重定向一个,不影响另一个)

程序是加工处理数据的,程序要有办法获得数据 键盘获取和显示器打印 支持debug
stdin stdout
./a.out 1>log.xxx 2>log.err
创建一个子进程,不需要把父进程的文件给文件拷贝
文件描述符表,需要给子进程拷贝一份
父进程打开了012 子进程直接继承了012 从bash上继承的012
问题来了 bash是如何打开的标准输入输出错误呢
在你启动的时候,就打开了stdin stdout stderr
属性的内容不一样,但是属性的类别一样
键盘 磁盘 网卡 显示器都是设备。 先描述在组织
struct device{
//name;
//vendor;
id;
status
driver
} 大家都有这些属性,但是每个人对于这些属性的内容是不一样的 有些设备有的属性没有直接设置为空就可以

底层抽象出device上层使用一个叫文件的东西统一管理下层devicd从而达到统一管理的目的

linux一切皆文件:就是在进程内部操作device的时候统一都是内部结构一样的文件

缓冲区
1.内核文件缓冲区
2.语言缓冲区
3.用户缓冲区

进程1我向文件1里面写入,进程2我向文件1里面写入,在两个进程中的文件描述符表中的structfile是一个还是两个
两个进程独立打开同一个文件:它们的 struct file 是【两个】,完全独立!

file* log.txt 文件内核缓冲区 刷新动作
文件操作表的 write 函数(f_op->write)
作用:把数据从用户空间 → 写到 内核缓冲区(Page Cache)
✅ 不直接写磁盘!
把数据从内核缓冲区 → 刷到磁盘
这个动作不是 write 干的!
是内核的 pdflush 线程 / 异步刷盘 或者 fsync() 系统调用 干的!
log.txt 1.属性 2.文件内核缓冲区 3.文件操作表
write和read函数本身其实都是拷贝
int fsync(int fd)
这个是刷新缓冲区函数,要是内容在文件内核缓冲区中还没有刷新到磁盘上,就BBQ了
缓冲区核心作用:提高io应答效率,解决的事使用缓冲区的人的效率问题
内核缓冲区的存在
1.提高用户拷贝数据的效率,避免用户直接进行io访问
2.提高os真实的io效率
常规:
1:立即刷新
2:行刷新
3:全缓冲,缓冲区写满->刷新 (os)
特殊情况
1.强制刷新
2.进程退出,强制刷新

一、CPU 缓存(L1/L2/L3 Cache)
✅ 和主存(内存条)是两套独立硬件
位置:L1/L2 集成在 CPU 内核里,L3 一般在 CPU 封装内;主存是主板上单独的内存条。
速度:CPU 缓存远快于主存,容量远小于主存。
关系:CPU 优先读缓存,缓存不命中才去读内存条。
二、Linux 内核页缓存(文件 / IO 缓冲区)
❌ 不是独立硬件,就位于主存(内存条)里
你之前聊的文件内核缓冲区 / Page Cache,本质是操作系统划出来的一块物理内存,和程序运行用的内存、栈堆内存,共享同一根内存条。
只是逻辑分区,硬件上是同一片内存颗粒。
系统会动态调配:空闲内存多,就多划给页缓存;程序需要内存时,再回收缓存空间。

  1. 磁盘太慢,内存太快,必须用缓冲区 “解耦”
    你刚才记住了速度排序:
    内存(ns) >>> 磁盘(ms)
    差 10 万~100 万倍!
    如果没有内核缓冲区:
    你程序 write 一次,就要等磁盘转一圈
    你写 1 个字节,磁盘也要动一次
    电脑直接卡成 PPT
    有了内核缓冲区:
    write 直接写到内存,瞬间完成
    磁盘后台慢慢写
    程序完全不卡顿
  2. 减少磁盘读写次数,延长寿命
    磁盘最怕频繁小 IO!
    没有缓冲区:
    写 1 字节 → 磁盘 IO 一次
    写 1000 次 → 磁盘 IO 1000 次
    有缓冲区:
    写 1000 次 → 都攒在内存
    凑够一批,一次性刷盘
    磁盘 IO 大大减少
    内核缓冲区 = 磁盘保护神
  3. 让 “写操作” 变成非阻塞,程序飞快
    你调用:
    c
    运行
    write(fd, buf, 100);
    没有缓冲区:必须等磁盘写完才返回(慢死)
    有缓冲区:写到内存就返回(飞快)
    这就是 Linux 快的根本原因!
  4. 缓存数据,重复读取不需要访问磁盘
    文件读一次后,就放在内核缓冲区。
    第二次读:
    直接从内存读
    不用动磁盘
    速度提升 1000 倍
    这就是Page Cache = 文件高速缓存
  5. 统一管理所有 IO,让操作系统更稳定
    所有进程读写文件,都走内核缓冲区:
    内核统一调度
    统一刷盘
    统一缓存
    避免冲突、混乱
    内核缓冲区 = 文件 IO 的中央调度中心
    终极一句话总结(背这个就够)
    ** 内核缓冲区(Page Cache)存在的目的:
    用内存抵消磁盘速度差,减少磁盘 IO,让程序不阻塞,让系统飞快!**

2.语言缓冲区
1.我们调用系统调用,也是有成本的,也是要话费时间的
空间配置器,内存池,vector扩容
扩容本质就是申请内存空间->就必定要调用系统调用函数
申请内存,内存就一定要有吗?
挂起进程 swap 腾出空间 返回给你

c缓冲区+fd
fwrite printf fprintf write
这些都是stdout打印的
我们在调用这个fprintf函数的时候 我们不会立刻就去调用write这些系统调用函数,而是在c缓冲区中
1.减少系统调用 是从用户到用户的
2.提高函数调用的响应速度

语言缓冲区在哪里啊
FILE*->stdout stdin stderr ->FILE *fp …
结构体
1.fd
2.输入输出缓冲区
struct FILE
{
fd
输入输出缓冲区
}

C 语言 / 标准库的 “文件缓冲区”:
是 FILE 结构体(用户态,stdio) 里封装的,不在内核 struct file 里。
Linux 内核里的文件缓冲(page cache):
不封装在 struct file 里面,struct file 只是 “打开文件的上下文”,不存数据缓冲区。

用户态 FILE = C 语言标准库给你的文件结构(带语言缓冲区)
内核态 struct file = Linux 内核管理打开文件的结构(不带语言缓冲区)

file*是句柄,里面含有fd和缓冲区
终端:人机交互页面
:严格来说,显示器(终端)没有磁盘那种内核页缓存(Page Cache),但存在驱动层极小的临时缓冲

在这里插入图片描述
子进程会继承父进程的文件描述符表
在这里插入图片描述

创建了一个子进程
先把write的给刷新了
然后子进程和父进程都有一个文件缓冲区
这个时候就会把各自文件缓冲区刷新 刷新两次
在这里插入图片描述
站在c语言呢的角度,只要把数据给os(操作系统)这个时候就认为是数据发完了

printf(“hello”);//语言缓冲区->stdout. oubuffer
sleep(4);
这个进程结束了 不打印hello

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐