一.文件管理共识
文件 = 内核 + 属性
文件分为打开文件和没打开文件
被打开的文件本质是被进程打开的(进程:打开的文件=1 :n)
没打开的文件放在磁盘上,我们关心的是这些文件是然后存储的,因为我们要快速找到文件才能进行赠删查改
操作系统内部存在大量打开的文件,那么怎么管理呢(先描述,在组织)在内核中,一个打开的文件一定存在其打开对象,包含文件的很多属性
二.了解文件
-
以C为主了解
-
当前路径是什么,当前路径其实就是进程的当前路径(cwd),如果更改了进程的当前路径那么当前路径就被修改了。
-
C在默认启动时自动打开三个标准输出输入流:stdin(键盘输入流)stdout(显示屏输出流)stderr(显示屏错误流)
-
-
通过系统了解
-
文件其实是磁盘上的,而磁盘是外部设备,所以访问磁盘文件就是访问硬件
-
几乎所有的库只要是访问硬件设备的必定要封装系统调用(因为自定向下一层一层访问)
-
-
访问文件的本质

-
左侧的和进程有关,右侧和文件管理有关,产生关联是蓝色这个表的下标
-
fd从3开始,因012分别对应C语言默认启动的输入输出流(文件)其实这个C语言的默认操作本质是操作系统的特性,操作系统本身就会打开3个文件,因此C语言默认同样也要打开3个文件(输入输出流)
-
FILE是C库自己封装的结构体,这里面必须封装文件的描述符
三. 知识补充
3.1 C对文件的操作
1.fopen
cpp
// 打开或创建一个文件,并返回对应的 FILE 流指针,用于后续 I/O 操作
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
// 参数:
// pathname - 文件路径(可以是相对路径或绝对路径)
// mode - 打开模式字符串,决定文件的访问方式和初始位置,常见值包括:
// "r" : 只读,文件必须存在
// "w" : 只写,若文件存在则清空,不存在则创建
// "a" : 追加写,文件指针定位到末尾,不存在则创建
// "r+" : 读写,文件必须存在
// "w+" : 读写,若文件存在则清空,不存在则创建
// "a+" : 读写追加,读从开头,写始终在末尾,不存在则创建
// (可附加 "b" 表示二进制模式
// 如 "rb"、"wb+",在 Linux 中效果与文本模式相同)
// 返回值:
// 成功:返回指向 FILE 结构的指针(用于后续 fread/fwrite/fclose 等)
// 失败:返回 NULL,并设置 errno
2.fread
cpp
// 从 FILE 流中读取指定数量的数据项
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
// 参数:
// ptr - 指向用户提供的缓冲区,用于存放读取的数据
// size - 每个数据项的字节大小(如 sizeof(int))
// nmemb - 要读取的数据项个数
// stream - 已打开的输入 FILE 流(如 stdin 或 fopen 返回的指针)
// 返回值:
// 成功:返回实际读取到的'完整数据项个数'(<= nmemb)
// 若返回值小于 nmemb,可能是到达文件末尾或发生错误
// 失败或 EOF:返回 0(若一开始就遇到 EOF)或部分值;
// 需配合 feof() 或 ferror() 判断具体原因
3.fwrite
cpp
// 将指定数量的数据项从缓冲区写入 FILE 流
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// 参数:
// ptr - 指向要写入的数据缓冲区
// size - 每个数据项的字节大小
// nmemb - 要写入的数据项个数
// stream - 已打开的输出流(如文件或 stdout)
// 返回值:
// 返回实际成功写入的完整数据项个数(<= nmemb)
// 若返回值小于 nmemb,表示发生错误(需用 ferror() 检查)
4.fclose
cpp
// 关闭一个已打开的 FILE 流,并刷新其缓冲区(若为输出流)
#include <stdio.h>
int fclose(FILE *stream);
// 参数:
// stream - 要关闭的 FILE 流指针(必须是由 fopen、fdopen 等返回的有效指针)
// 返回值:
// 成功:返回 0
// 失败:返回 EOF(通常因刷新输出缓冲区时出错,如磁盘满、I/O 错误等)
3.2 系统接口
1.open
cpp
// 打开或创建一个文件,并返回其文件描述符(非缓冲,底层 I/O)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, ... /* mode_t mode */);
// 参数:
// pathname - 文件路径(字符串)
// flags - 打开标志,控制访问模式和行为,常用组合如:
// O_RDONLY, O_WRONLY, O_RDWR(必须三选一)
// O_CREAT :若文件不存在则创建
// O_TRUNC :若文件存在则清空内容
// O_APPEND :写入时始终追加到文件末尾
// O_EXCL :与 O_CREAT 同用,确保文件不存在(用于原子创建)
// mode - 可选参数,仅当使用 O_CREAT 时需要,指定新文件的权限(如 0644)
// 返回值:
// 成功:返回非负整数(文件描述符,如 3、4...)
// 失败:返回 -1,并设置 errno
2. read
cpp
// 从文件描述符中读取数据到缓冲区
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
// 参数:
// fd - 文件描述符(由 open、pipe、socket 等返回的非负整数)
// buf - 指向用户提供的缓冲区,用于存放读取的数据
// count - 最多读取的字节数
// 返回值:
// 成功:返回实际读取的字节数(可能小于 count);
// 返回 0 表示已到达文件末尾(EOF)
// 失败:返回 -1,并设置 errno
3. write
cpp
// 将数据从缓冲区写入到文件描述符
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
// 参数:
// fd - 文件描述符(由 open、pipe、socket 等返回的非负整数)
// buf - 指向要写入的数据缓冲区
// count - 要写入的字节数
// 返回值:
// 成功:返回实际写入的字节数(可能小于 count,尤其在管道、套接字或信号中断时)
// 失败:返回 -1,并设置 errno
4.close
cpp
// 关闭一个文件描述符
#include <unistd.h>
int close(int fd);
// 参数:
// fd - 要关闭的文件描述符
// 返回值:
// 成功:返回 0
// 失败:返回 -1,并设置 errno
3.3 文件描述符
通过对open函数的学习,我们知道了文件描述符就是一个小整数
0 1 2
-
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
-
0,1,2对应的物理设备一般是:键盘,显示器,显示器
文件描述符的分配规则
- 在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
四.重定向
概念: 在 Linux 中,重定向 是指改变命令的默认输入/输出方向
1. 通过文件描述符的分配规则实现

当标准输出被关闭时,我myfile就会使用1这个下标,作为其文件描述符(注意要先关闭标准输出,然后再创建myfile)
2. 通过函数dup2
cpp
// 将文件描述符 oldfd 复制到 newfd,若 newfd 已打开则先关闭它
#include <unistd.h>
// 该函数实现的是newfd拷贝oldfd从而实现指向的对象
// 此时可以把oldfd关闭,因为newfd同样指向oldfd指向的对象
int dup2(int oldfd, int newfd);
// 参数:
// oldfd - 源文件描述符
// newfd - 目标文件描述符(会被强制设为 oldfd 的副本)
// 返回值:
// 成功:返回 newfd
// 失败:返回 -1,并设置 errno
例子
int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
dup2(fd, 1); // 现在 printf 会写入 output.txt,而不是屏幕
这个dup2很奇怪不符合人类直觉,要多看几遍
3. >>和>的
前者是追加重定向,后者是覆盖重定向,二者为什么是这样的呢?

O_TRUNC :若文件存在则清空内容;O_APPEND :写入时始终追加到文件末尾
4.总结和补充
- 重定向的核心机制是:将一个已打开文件的
struct file*指针复制到标准文件描述符 (如 1)在进程文件描述符表中的槽位,从而改变 I/O 的目标。 - 进程替换不会干扰文件访问操作
五.理解一切皆文件的概念
在 Linux 中,为了统一管理硬件设备,内核通过 VFS(虚拟文件系统) 将设备抽象为"文件"。用户通过 open() 获得文件描述符,内核创建 struct file 对象,其中包含指向设备专属操作函数(file_operations)的指针。当调用 read()/write() 时,内核通过该指针调用对应驱动函数,从而访问设备。这种机制利用函数指针实现了类似"多态"的效果,而设备本身的属性由驱动私有结构体管理,与 struct file 分离。
关于这部分内容就先讲到这里,本章的重点还是非常多的,比如系统接口有那些这么实现的,文件描述符是什么东西,以及重定向概念、实现方法。都是重中之重,希望大家重点记忆相关知识!