本章重点:
①认识文件相关系统调用接口
②认识文件描述符,理解重定向
③对比fd和FILE,理解系统调用和库函数的关系
④理解文件和内核文件缓冲区
⑤自定义shell新增重定向功能
⑥理解Glibc的IO库
一、理解"文件"
1.1 狭义理解
文件在磁盘里
磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
磁盘是外设(既是输出设备也是输出设备)
磁盘上的文件本质上对文件的所有设备,都是对外设的输入输出 简称IO
1.2 广义理解
Linux下一切皆文件(键盘、显示器、网卡、磁盘等都是抽象化的过程)
LInux系统把系统里的所有资源(硬件、设备、数据等)都抽象成"文件"的形式来统一管理
①:硬件设备被抽象成文件
像键盘、显示器、网卡、磁盘这些硬件,在Linux中都会被对应到一个"文件路径",操作硬件本质上就是读写这些对应的"文件"
②:统一了操作方式
不管是普通的文档文件、硬件设备还是网络数据,Linux都用相同的文件操作命令/接口来处理(例如read、write、open打开),不用再给不同硬件写不同的操作逻辑,
1.3 文件操作的归类认知
①对于0KB的文件是占用磁盘空间的
②文件是文件属性(元数据)和文件内容的合集(文件 = 属性(元数据)+ 内容)
③所有的文件操作本质上文件内容操作和文件属性操作
1.4 系统角度
对文件的操作本质是进程对文件的操作
磁盘的管理者是操作系统
文件的读写本质不是通过c语言/c++的库函数来操作(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的
二、回顾C文件接口
创建操作流,fread和fwrite返回期望写入或者读取的元素个数
用户缓冲区位置,每个元素的大小,期望元素个数,文件流(fopen返回值)
2.1 hello.c打开文件

打开的myfile文件查找

cwd:指向当前进程运行目录的一个符号链接
exe:指向启动当前进程的可执行文件(完整路径)的符号链接
打开文件,本质就是进程打开,所以进程知道自己在哪,即使文件不带路径,进程也知道,由此OS就知道要创建的文件要放在哪里
2.2 hello.c 写文件

2.3 hello.c 读文件

2.4 stdin & stdout & stderr
C语言会默认打开三个输入输出流,分别是stdin stdout stderr
这三个流的类型都是FILE*。fopen返回值类型,文件指针

在本章的末尾我们会总结语言级对文件的操作接口和底层系统调用的相关知识
三、系统文件I/O
打开文件的方式不仅仅是fopen等流式,语言层方案,其实系统才是打开文件最底层的方案
3.1 一种传递标志位的方法
在学习系统IO之前,要先了解如何给函数传递标志位,该方法子在系统文件IO接口中会使用到
其精髓在于:
用一个整数的"每一位二进制位"表示一个独立的布尔状态,通过按位或 | 组合多个状态,通过按位与 & 判断某个状态是否存在,从而用一个参数同时携带多个开关信息

3.2 hollo.c 写文件

3.3 hello.c 读文件

3.4 open接口介绍

open函数有两个,具体使用哪一个和具体的使用场景有关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则使用两个参数的open接口
3.5 open函数返回值
先理解系统调用和库函数:

语言级别的文件操作函数都是对系统调用的封装,方便二次开发
3.6 文件描述符fd

我们知道文件都是被存储在磁盘上,一个文件只会被加载到操作系统中一次(属性和内容分别加载存放),但是一个文件可以被不同的进程重复打开,也就是可以被打开多次,文件每一次被open都会创建一个struct file表示一个被打开的实例,但并不是文件的实体,里面存储着包括但不限于指向文件实体Inode的指针和读写的偏移,有一个指针数组,里面的元素都是一个一个指向struct file的指针,fd文件描述符就是这个指针数组的下标,通过fd文件描述符这个句柄,我们就可以向不同的文件进行操作(对文件的操作本质是对文件属性和内容的操作),综上,文件描述符的本质即是一个个指针数组的下标,是一个个的整数。
3.6.1 0 & 1 & 2
Linux进程默认会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
0,1,2对应的物理设备一般是:键盘,显示器,显示器
所以输入输出还可以这样:


3.6.2 文件描述符的分配规则
一句话:在files_struct数组当中,找到 当前没有被使用的最小的一个下标,作为新的文件描述符
3.6.3 重定向
重定向就是在不改变程序代码的情况下,把进程默认的文件描述符(如 0/1/2)从终端改为指向其他文件或设备,使输入输出流向发生改变。

最基本的重定向也就是手动关闭文件描述符,再重新打开一个,根据文件描述符的分配规则也就完成了重定向
重定向的本质:

3.6.4 使用dup2 系统调用重定向


四、理解"一切皆文件"
Linux中不仅仅普通磁盘文件是文件,进程信息、磁盘、终端、键盘、显示器、管道乃至网络Socket等资源也都被内核统一抽象为文件,因此开发者可以通过同一套文件IO接口(open、read、write、close)来访问和操作这些不同类型的资源,
这样做的好处是:用统一的API和编程模型,就能管理系统中的绝大部分资源,大大简化了系统设计和开发的复杂

五、缓冲区
5.1 什么是缓冲区
缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓 冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设 备,分为输入缓冲区和输出缓冲区
5.2 为什么要引入缓冲区
1.解决速度不匹配
CPU 的运算速度极快,而磁盘、网络、打印机等 I/O 设备的读写速度则慢得多。如果没有缓冲区,高速的 CPU 在处理数据时,将不得不长时间等待低速的 I/O 设备完成操作,造成 CPU 资源的巨大浪费。缓冲区作为一块内存区域,充当了"中转站"的角色。CPU 可以快速地将数据写入或读出缓冲区,然后立即去处理其他任务,而 I/O 设备则可以按照自己的节奏从缓冲区中"慢慢"读取或写入数据
2.减少昂贵的系统调用
在操作系统中,直接与硬件交互需要通过"系统调用"(如 read、write)。每次系统调用都涉及从用户态到内核态的切换,这个上下文切换的过程开销很大。
无缓冲区时 :如果程序要写入1000个字符,可能需要调用1000次 write 函数,产生1000次昂贵的上下文切换
有缓冲区时 :程序先将1000个字符写入内存中的缓冲区,然后只需调用1次 write 函数,就能将这批数据一次性写入目标设备,极大地减少了系统调用次数和CPU开销
5.3 缓冲类型
全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。对于磁盘文件的操作通 常使用全缓冲的方式访问
行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准I/O库函数将会执行系统调用 操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准 I/O库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行 I/O系统调用操作,默认行缓冲区的大小为1024。
无缓冲区:无缓冲区是指标准I/O库不对字符进行缓存,直接调用系统调用。标准出错流stderr通 常是不带缓冲区的,这使得出错信息能够尽快地显示出来