1 一切皆文件
1.1 虚拟文件系统
在系统层面,做了一层软件的封装,struct file 里有操作表(即函数指针的集合),可以调用底层的读写方法。虚拟文件系统(VFS)是一种神奇的抽象,它使得 “一切皆文件” 哲学在 Linux 中成为了可能。 (类比C++多态)
1.2 为何C/C++喜欢做封装
(1) 方便用户操作
以“文本写入 vs 二进制写入”举例,在操作系统层面全是二进制,语言层面进行转化变为文本
(2) 提高语言的可移植性
如果不是Linux而是其他操作系统呢?C库函数 ---> 系统调用;FILE ---> fd(源代码编译成不同版本的库)
封装可以“屏蔽差异”
1.3 文件内核缓冲区
写入:
打开一个 myfile 文件,创建对应的 struct file,里面包含操作表和文件内核缓冲区address_space,write() (拷贝函数,把数据从用户拷贝至内核)把要写入的内容拷贝至文件的内核缓冲区,如果需要的话,可通过file->ops->write()将数据刷新到文件中(由OS自主决定)。
空文件写入,拷贝刷新即可;修改的本质,是先读取再写入。
为何有缓冲区?因为外设比较慢,缓冲区的存在可以变相提高IO效率。
一个进程,能在PCB中找到file对象,通过file对象,既能找到文件的操作表,又能找到文件对应的属性集,还能找到文件对应的文件内核缓冲区。
1.4 重定向
close(0);int fd1 = open("return_test1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd2 = open("return_test2.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd3 = open("return_test3.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fd4 = open("return_test4.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
./ofi
fd1 of open(): 0
fd2 of open(): 3
fd3 of open(): 4
fd4 of open(): 5进程打开文件,需要给文件分配新的fd时,fd的分配规则是以最小的且没有被使用的为优先
重定向:把 fd_array 中特定下标对应的元素替换后,如close(1),上层不知道(printf->stdout->fileno=1),本来往显示器文件写入的变为了往新的文件写入。
系统调用:int dup2(int oldfd, int newfd)
dup2() makes newfd be the copy of oldfd, closing newfd if necessary
如果要做输出重定向,可以把文件的地址(fd)覆盖到 fd_array[1] 中,即 dup2(fd, 1)
(联想记忆:fd活得久,就old)
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
printf("HELLO FD: %d\n", fd);
fprintf(stdout, "HELLO FD: %d\n", fd);
fputs("HELLO LINUX\n", stdout);
const char *msg = "hello fwrite\n";
fwrite(msg, 1, strlen(msg), stdout);
往显示器里写入
HELLO FD: 3
HELLO FD: 3
HELLO LINUX
hello fwrite
使用 dup2(fd, 1);
$ cat log.txt
HELLO FD: 3
HELLO FD: 3
HELLO LINUX
hello fwrite
1.4.1 命令行中重定向内容的解析
"ls -a -l -n" < hello.txt
"ls -a -l -n" > hello.txt
"ls -a -l -n" >> hello.txt
//全局变量(重定向相关)
#define NoRedir 0
#define InRedir 1
#define OutRedir 2
#define AppRedir 3int redirtype = NoRedir;
char *filename = nullptr;
//过滤空格
#define TrimSpace(pos) do{\ //while(0)的作用是形成代码块,可以整体替换while(isspace(*pos)){\pos++;\}\
}while(0)
bool ParseCommandLine(char commandline_buffer[], int len){
//......//重定向redir = NoRedir;filename = nullptr;//"ls -a -l -n" > file.txtint end = len - 1;while(end >= 0){if(commandline_buffer[end] == '<'){redir = InRedir;filename = &commandline_buffer[end] + 1;TrimSpace(filename);commandline_buffer[end] = 0;break;}else if(commandline_buffer[end] == '>'){if(commandline_buffer[end - 1] == '>'){redir = AppRedir;commandline_buffer[end] = 0;commandline_buffer[end - 1] = 0;filename = &commandline_buffer[end] + 1;TrimSpace(filename);break;}else{redir = OutRedir;commandline_buffer[end] = 0;filename = &commandline_buffer[end] + 1;TrimSpace(filename);break;}}else{end--;}}
//......
}
2 总结
(1)文件描述符的概念,文件也是要被管理的(= 内容 + 属性),分为未打开的和被打开的
(2)标准输入输出错误之间的关系,以及系统调用接口open, close, read, write,文件流封装的底层一定有文件描述符
(3)文件描述符分配规则、012,重定向概念、原理、操作、如何进行
(4)内核角度理解“一切皆文件”,通过 struct file 对象和函数指针表屏蔽差异
(5)一个进程有自己的PCB,也要有自己打开的文件描述符表,还要有自己打开的工作文件,每个文件都要配备操作表、属性集和内核级缓冲区