1. 从接口到底层
1.1 open()函数
打开一个文件,成功返回int类型的文件描述符,失败返回-1。
pathname:路径/文件名。
flags:文件访问模式和行为控制标志,可通过位或操作组合使用。
- O_RDONLY: 只读模式。
- O_WRONLY: 只写模式。
- O_RDWR: 读写模式。
- O_CREAT: 如果文件不存在则创建。
- O_TRUNC: 打开文件时清空内容(仅在写模式下有效)。
- O_APPEND: 每次写入时从文件末尾追加。
- O_EXCL: 与O_CREAT一起使用时,如果文件已存在则返回错误。
- O_NONBLOCK: 非阻塞模式。
C进行了封装使"w","r","a","w+","r+","a+" 具有其功能,其他的语言同样是对flag进行封装。
等效关系:
w--->O_WRONLY + O_CREAT + O_TRUNC r----> O_RDONLY a----->O_WRONLY + O_CREAT + O_APPEND w+----->O_RDWR + O_CREAT + O_TRUNC r+------>O_RDWR a+--------> O_RDWR + O_CREAT + O_APPEND
mode: 当使用O_CREAT标志时,指定新文件的权限(八进制表示)。
S_IRUSR: 文件所有者可读,S_IWUSR: 文件所有者可写,S_IXUSR: 文件所有者可执行。
也可以直接使用0644(000 110 100 100)等直接表示权限。
1.2 write函数
write(int fd , const char * buf , int count)
fd: 文件描述符,标识要写入的文件或设备。
buf: 指向数据缓冲区的指针,即要写入的数据。
count: 要写入的字节数。
1.3 open和write代码
int main()
{
int ret = open("my_test", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (ret == -1)
{
cout << "文件打开失败" << endl;
exit(2);
}
int n = 0;
while (n < 10)
{
char buffer[1024];
sprintf(buffer,"GuYu_test_%d\n",n++);
int n = write(ret, buffer, strlen(buffer)+1); // 预留\0的位置
}
}

1.4不同函数看待数据
为什么my_test文件出现了^@ ,而cat打印没有出现?
buf中实际的存储: 47 75 59 75 20 74 65 73 74 3a 20 30 0a 00 (\n \0)。
write 不能识别 \0(空字符)是因为 write 是底层系统调用,它处理的是字节流,而不是字符串。write 系统调用将内存中的原始字节直接写入文件描述符,不进行任何解释,不同查看工具的解释不同,cat对^@的解释是\0。
|--------|-----------|---------|
| 函数 | 对 \0 的处理 | 用途 |
| write | 当作普通字节写入 | 底层字节操作 |
| printf | 字符串终止符 | 格式化输出 |
| strlen | 字符串终止符 | 计算字符串长度 |
| fputs | 字符串终止符 | 字符串输出 |
| fwrite | 当作普通字节写入 | 二进制数据操作 |
1.5 文件描述符(返回值)
int main()
{
int fd1 = open("my_test_1", O_RDWR | O_CREAT | O_TRUNC);
int fd2 = open("my_test_2", O_RDWR | O_CREAT | O_TRUNC);
int fd3 = open("my_test_3", O_RDWR | O_CREAT | O_TRUNC);
int fd4 = open("my_test_4", O_RDWR | O_CREAT | O_TRUNC);
printf("fd1 = %d\nfd2 = %d\nfd3 = %d\nfd4 = %d\n",fd1,fd2,fd3,fd4);
exit(0);
}
[root@VM-8-15-opencloudos test_IO]# make clean;make;./*.out
rm -rf *.out
g++ -o fun.out fun_intro.cpp
fd1 = 3
fd2 = 4
fd3 = 5
fd4 = 6
这个fd是个数字,单独存在表示大小,但在write里面表示的是文本描述符。
为什么文本描述符从3开始? 0 1 2 是OS自动为我们打开的文件描述符,标准输入,输出,错误
1.6 open函数调用的过程
有OS进行路径的解析,解析路径 例: /home/user/report.txt。
inode:是文件系统的相对位置标识符,帮助管理和维护分区结构,确保各操作正确执行。
目录的数据块是存储的是文件名和文件inode编号的映射表,文件的数据块存储的是文件真正的数据内容。
Documents 目录在磁盘上的数据块内容,就是这样一个简单的线性表:
+----------------------------------------------------+
| Documents 目录的数据块 (一个线性表)
+----------------------------------------------------+
| 目录项1 | 文件名: "report.txt" | inode 号: 105
+---------+-----------------------+------------------+
| 目录项2 | 文件名: "photo.jpg" | inode 号: 271
+---------+-----------------------+------------------+
| 目录项3 | 文件名: "notes.pdf" | inode 号: 42
+---------+-----------------------+------------------+
| ... | ... | ... |
+---------+-----------------------+------------------+
ls Documents 命令时,发生了以下事情:
1. 操作系统读取 Documents 目录的 inode,找到其数据块的位置。
2. 将这些数据块作为线性表读入内存。
3. 简单地遍历这张表,提取出每个目录项中的 "文件名" 字段,并显示在屏幕上。
步骤:
1.先通过根目录的inode编号加载根目录的数据块到内存。
-
在数据块中找到名称为home的项获取对应的inode编号,检查home文件夹的'X权限。
-
重复操作,直到将report.txt的数据块加载到内存,检查权限。
/(根目录)--> inode-->/数据块-->home--> inode-->home数据块-->user
-->inode-->user数据块-->report.txt--> inode-->*>txt数据块-->
读取的元数据-->检查权限和文件是否存在
获取了report.txt的数据块,OS就要用结构体描述,组织文件的属性和内容,会分配一个struct file结构体。
// 每个文件描述符表项指向 file 结构
struct file {
unsigned int f_flags; // 文件标志
mode_t f_mode; // 文件模式 例:0666
loff_t f_pos; // 文件偏移
struct file_operations *f_op; // 文件操作
struct inode *f_inode; // 指向inode结构体
refcount_t f_count; // 引用计数
......
}
然后由进程查找struct file_struct ,获取新的文件描述符,并将struct file指针放入file_struct进行统一管理。
// 内核中的进程文件描述符表
struct files_struct {
atomic_t count; // 引用计数
struct fdtable *fdt; // 文件描述符表(数组的下标就是文件描述符)
// 实际的文件描述符数组
struct file **fd_array; // 这就是struct file *fd_arr[]
};

2. 重定向
关闭默认输出描述符fd=1,并将fd=1分配给新打开的文件。
int main()
{
close(1); // 关闭标准输出fd
umask(0); //掩码为0
int fd=open("a.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
printf("fd = %d\n",fd);
exit(0);
}

关闭了文件描述符1,打开文件a,txt OS将1给新fd,在C中,OS会自动命名stdout对应fd=1 (对struct file* fd_array[1]的命名) 由于printf和fprintf使用stdout---->fd_array[1], 将屏幕输出换为文件输出。
重定向函数
dup()
分配一个新的文件描述符,该描述符引用与旧文件描述符 old_fd 相同的打开文件描述(将oldfd对应的struct file复制一份,并为新的文件结构体分配新的文件描述符),不同的文件描述符指向同一个文件。

int main()
{
close(1);
int fd1 = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
int fd2 =dup(fd1);
printf("fd1 = %d\n",fd1);
printf("fd2 = %d\n",fd2);
exit(0);
}

fd1和fd2的值不一样,当他们的数据都写入了a.txt文件中,说明了两种的struct file结构体的内容是一样的,
dup2()
dup2()与dup()相比不是使用编号最小的未使用文件描述符,而是使用 newfd 指定的文件描述符号。换句话说,文件描述符 newfd 被调整,使其现在引用与 oldfd 相同的已打开文件描述符描述(将older的struct file替换掉newed的struct file的内容)。
int main()
{
close(1);
int fd1 = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
dup2(fd1,3); // close(0) 并使fd=0的struct file复制了fd1的struct file的内容
printf("fd1 = %d\n",fd1);
printf("fd3 = %d\n",3);
exit(0);
}

相当于先close(new_fd)(new_fd可以是已经分配的,也可以是未分配的),再复制old_fd。
命令行重定向
">" :O_WRONLY + O_CREAT + O_APPEND 功能
">>": O_WRONLY + O_CREAT + O_TRUNC 功能

"<" :读取数据获取
