1.open的进一步介绍
int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mode);
open的返回值就是打开文件的对应的fd(文件描述符).其中0,1,2分别对应了标准输入,标准输出,和标准错误.
c语言的文件操作主要由FILE这个结构体控制,因此能推测出FILE中有类似于fd的这个结构体成员.
(FILE中的_fileno成员就是对应的fd,而fd的本质就是数组下标)
2.标准库有可移植性的原因
不同OS之间的代码不具有可移植性,但一门语言的标准库可以分别给不同OS设计不同的代码,从而使用户在语言层使用同一个接口的情况在不同OS时都可以运行,以便让用户的代码具备可移植性。

OS中将用于管理文件所用的结构体叫struct_file file(集中在OS中管理,进程中是在fiels_struct中的fd_array[]中存储或调用),而file中就管理着一个文件中的所有信息。
在file中有一个成员指向一块他自己的文件缓冲区。
一个进程可以打开多个文件,进程中的task_struct存在一个struct files_struct * file的成员指向一个结构体,该结构体中有中有一个成员struct file * fd_array[],存储该进程所需文件的struct file的地址,而fd就对应着该数组中的下标,而所有的file统一由OS管理。
例:read函数就是将内核数据通过buffer层间接拷贝给用户。
加载:对文件内容进行任何操作都必须先把文件加载到内核对应的文件缓冲区中,也就是从磁盘到内存的拷贝。
3.重定向原理
文件描述符的分配原则:最小且没有被使用的作为新的fd分配给用户新建的文件。
介绍一下方式就是:文件的fd的分配是由OS操作的,printf内的stdout中对应的fd一直为1,因此printf就只会往fd==1处写入,也就是用户可以通过OS的底层真实fd来修改原本库中的函数功能,说白了就是修改文函数功能或为在文件描述符表下标不变的前提下修改文件描述符表的指针指向。
int dup2(int oldfd,int newfd);
//成功时return newfd,否则返回oldfd
//底层是将oldfd中的地址覆盖到newfd中的空间中
总结而言重定向的操作就是打开文件的方式(open)+dup2(修改文件指向)
4.shell实现重定向的方式
//首先对< > >> 和没有重定向的情况进行定义
#define NoneRedir 0
#define InputRedir 1
#define OutputRedir 2
#define AppRedir 3
//初始化重定义情况
int redir = NoneRedir;
//被重定向的文件名
char *filename = nullptr;
//去掉重定向符号后面到文件名之间的空格(前面的空格不用管,strtok会处理)
#define TrimSpace(pos) do{\
while(isspace(*pos)){\
pos++;\
}\
}while(0)
void ParseRedir(char command_buffer[], int len)
{
int end = len - 1;
while(end >= 0)
{
//对三种情况进行分析
//都有修改redir情况,filename指向和去空格
if(command_buffer[end] == '<')
{
redir = InputRedir;
command_buffer[end] = 0;
filename = &command_buffer[end] + 1;
TrimSpace(filename);
break;
}
else if(command_buffer[end] == '>')
{
//>>的情况
if(command_buffer[end-1] == '>')
{
redir = AppRedir;
command_buffer[end] = 0;
command_buffer[end-1] = 0;
filename = &command_buffer[end]+1;
TrimSpace(filename);
break;
}
//为>的情况
else
{
redir = OutputRedir;
command_buffer[end] = 0;
filename = &command_buffer[end]+1;
TrimSpace(filename);
break;
}
}
else
{
//都不是时就往下找
end--;
}
}
}
void DoRedir()
{
//对不同redir进行不同的处理,核心区别就在open的符号调用(open打开失败的情况就省略了)
if(redir == InputRedir)
{
if(filename)
{
int fd = open(filename, O_RDONLY);
dup2(fd, 0);
}
else
{
exit(1);
}
}
else if(redir == OutputRedir)
{
if(filename)
{
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
dup2(fd, 1);
}
else
{
exit(3);
}
}
else if(redir == AppRedir)
{
if(filename)
{
int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
dup2(fd, 1);
}
else
{
exit(5);
}
}
else
{
// 没有重定向,Do Nothong!
}
}
//在处理指令前调用该函数
ParseRedir(//..);
//⼦进程中调用该函数
DoRedir();
进程替换是不会影响处于OS管理中的重定向结果的(因为替换不会改变原本PCB)
内建命令进行重定向的方式:先创建一个临时文件,先将要被覆盖的文件地址存在该文件中,然后等重定向操作后就恢复最初的模样。
一个文件可以被多个进程打开,因此一个进程中的此刻没有指向一个文件的指针,该文件也可能由于被其他进程使用着从而处于被打开状态。
5.缓冲区
1.重定向时被覆盖的文件是可以进行指定的(可以省略,有默认的fd),例:
//1就是说明cat写入的位置fd==1处的文件,也就是明确说明log.txt的fd是重定向到1这个位置上的
cat 1 > log.txt
stdout和stderr其实都是显示器文件,但输出重定向只是修改stdout,stderr并未进行修改,也就是stderr还是往显示器写入,此时采用cat 2 > log.txt,就可以对stderr进行重定向操作了。
cat 1 > log.txt 2 > log.txt
cat 1 > log.txt 2 > &1
第一条代码最后只会存下原本传给stderr的数据,但第二条代码会同时存下stdout和stderr的数据,原因在于使用>时log.txt之前的数据会被清空(由于open的原因),第二条&1代表此次的重定向不用再open一次,因此不会被清空而是往后追加。
2.
一个文件的内容整体可以视为一个一维的字符数组。
OS中存在很多4KB大小的内存块,采用先描述,在组织的方式管理。
总结:task_struct中的一个成员files_struct*files,该指针指向空间中存在一个struct_file*fd_array[]存文件描述符表,成员中每个地址指向得空间中有存属性空间(inode)和存数据空间(内存中的缓冲区)
6.一切皆文件的本质
1.各个硬件会有对应的操作方式存储在一个特定的空间中,OS中用不同的结构体管理不同的硬件并连接起来,对应的结构体管理着该硬件的功能(就是各种函数)
2.每个硬件有其自己的struct_file来将其包装成文件,这个struct_file中存储着这个硬件各种的函数指针和函数声明。
3.OS对不同硬件中的相似功能的函数进行了函数指针类型,参数类型等的形式上的统一,从而保障了用户在调用不同硬件时操作的一致性。如显示器和键盘都有write功能,我们对两个文件都打开(open),然后都能使用同一个write函数进行写入。
4.总结有:
帮助用户管理硬件的媒介就是文件地址表中的各个struct_file,这个结构体存着对应硬件的数据,属性和功能的接口以帮助用户,而这些被OS管理着的所有struct_ file统称叫VFS.
可以说每个struct_file是相同的,但是其却指向了不同的硬件管理,也就是struct_file其实是对不同硬件结构体的基类,可以说这是一种多态思想。
因此本质就是我们调用任何设备的表象好像只是调用一个文件,实际上只是调用到对应的struct_file而已,后续操作全由OS处理,只是我们不知情罢了。
作用:在OS或用户某一方暂时不在线时,让另一方能去干其他事。
7.库函数的写入在关闭了对应文件时就会被清空而OS接口的写入再关闭时并不会清空
原因:c标准库中有一个用户级,语言层缓冲区,当用户往文件写入时数据会存在这个缓冲区中。当用户有(1)强制刷新(2)刷新条件满足(3)进程退出,这三个点中有一个满足时,c标准库才会通过fd将数据拷贝到文件内核缓冲区(方式是通过OS接口的调用),因此三个都不满足时,此时关闭文件对应的fd没了也就无法拷贝文件到文件缓冲区中了。