前言:我们都知道,我们学习的C/C++是无法直接与底层硬件进行交互的,所有需要与底层硬件的交互都是通过操作系统作为中介完成的,那Linux到底是怎么做到的呢?接下来我们将揭开它神秘的面纱。
目录
一,操作系统如何管理文件
1)Linux之下一切皆文件
2)先描述,再组织
3)具体管理方法
二,C/C++读写文件的函数是如何完成的?
1)fwirte , fread ,fopen ,fclose
2)open,write,read,close
3)为什么要包装一层
三,重定项
1)重定项的使用
2)重定项的原理——dup2
一,操作系统如何管理文件
首先我们要明白操作系统是人类发明出来的,那么它的结构创造一定要符合我们人类的逻辑,对于一大堆文件我们该如何管理呢?
1)Linux之下一切皆文件
这是什么意思呢?难道Linux管理的键盘,鼠标,显示器也是文件吗?当然它们实际上并不是文件,那为什么所有人默认这个准则呢?因为Linux提供了一个统一的视角来看待所有被管理的东西,在Linux看来所有被他管理的东西都是文件,这样子有什么好处呢?举个例子
假设一个新校长要高效管理一个学校,这里面什么人都有,有教务处处长,教授,执教老师,助教,学生,保安,宿管阿姨。它们之间的各种差异这么大,我们想要直接全部管理无疑难度很大,那怎么办?我们不妨把他们全部看作被管理者,把他们在学校的身份的关键信息全部总结起来,记录成一张表格,表格记录他们的职位,年龄,性别,学号,电话号码等关键信息,当我们需要找他们办事的时候通过这张纸联系他们,并对他们进行操作。我们对他们操作转需要借助这个表格,表格给我们提供了一个统一的视角来管理各种完全不同身份的人,我们可以无视他们的差距,这无疑提高了我们的效率。
在上面的例子里面,各种身份的人就是各种硬件,校长就算操作系统,表格就是我们说Linux之下一切皆文件的文件,但实际上他们的特性可能天差地别,但是对于我们操作系统来说并不重要,反正我们通过管理文件的方式来管理你们。
补充:上面的例子里面的表格在Linux里面其实就算struct结构体,因为Linux是C写的,那时候还没出现C++类之类的概念,也就是操作系统是通过各种struct结构体管理硬件等。
2)先描述,再组织
这也是我们人类认识新事物的方法和过程,假如我们从来没有见过螃蟹,我们第一次看见,我们首先肯定会感到奇怪,然后我们第一件事肯定先用我们学习的词汇来试图描述它,比如红色的,甲壳,很多条腿,只有我们把他们的特征描述起来,我们脑海里才会对它有一个概念。那我们为什么要组织呢?假如因为我们对螃蟹这种从来没见的东西而产生了好奇,我们绝对对它进行研究。这时候我们有了很多只螃蟹,我们对它进行管理,仅仅知道它们是螃蟹已经明显不够了,我们需要对它们进行编号或者起名字之类的,这就算组织,我们为什么要组织呢?肯定是为了方便管理啊。这条包括上面的概念是Linux及其重要的,希望大家理解。
3)具体管理方法
首先我们把各种硬件之间我们需要特性全部进行总结,形成一个struct结构体,在Linux里面这个结构体采用的是数组的形式,数组里面是文件指针,0号下标指向的是标准输入,1号下标指向的是标准输出,2号下标指向的是标准错误我们看下图来理解。
二,C/C++读写文件的函数是如何完成的?
1)fwirte , fread ,fopen ,fclose
我们都知道fwirte , fread ,fopen是语言层面的函数,它们是无法直接调用系统底层文件的,只能通过操作系统的帮助,所有我们可以推断fwrite,fread,open里面绝对包装了系统接口,fwrite调用的是wirte的接口,fread是调用系统的read接口,fopen则是调用open接口,fclose自然是调用了close。
2)open,write,read,close
在上面我们说了,操作系统对“文件”管理是通过struct结构体数组,那我们打开一个文件只需要找到这个文件的下标和这个数组的地址,这个数组的地址我们不需要提供,是操作系统管理的,我们拿到数组的下标就可以管理“文件”。没错在open,write,read函数中这个下标是一个很重要的参数。
int open(const char *pathname, int flags, mode_t mode);
//mode是设定的文件权限,后面不做讲解,不理解可以看我往期权限博客
上面的是open函数,第一个参数pathname是要创建或者打开的文件,第二个参数flag是打开的方式,打开方式要细讲。
define O_RDONLY 1
define O_WRONLY 2
define O_RDWR 4
define O_CREAT 8
define O_APPEND 16
void test(int a){
if(a&1){
cout<<"只读打开"<<endl;
}
if(a&2){
cout<<"只写打开"<<endl;
}
if(a&4){
cout<<"读写打开"<<endl;
}
if(a&8){
cout<<"若文件不存在就打开"<<endl;
}
if(a&16){
cout<<"追加写"<<endl;
}
}
int main(){
test(O_RDWR|O_CREAT|O_APPEND);//可读可写并追加写
return 0;
}
很多人这时候就有疑问了,我用不同的数字大小表示不同的状态不也行吗?
但是这个就产生了一个问题,我们写出来是要考虑用户的,如果我们采取数字的,甚至给数字再定义一个英文名字,英文名字的起名要蕴含多重意思自然就变长了,可读性就差了,综上所述这样子没有位图这样子直观和方便。
返回值则是“文件”数组的下标
ssize_t write(int fd, const void *buf, size_t count);
fd是文件描述符的下标,也就是“文件”数组的下标,将buf里面的内容写入文件,count则是写入的大小。如果返回值=-1则write出现了错误,一般来说是写入内容的大小。(sizt_t是无符号整形,-1被转换后就是无符号整形能存储的最大值)
ssize_t read(int fd, void *buf, size_t count);
同样fd是“文件”数组下标,读取出来的内容会存储再buf里面,count是读取的大小。返回值是实际读取的大小,如果读取失败返回-1.
int close(int fd);
关闭文件只需要“文件”数组下标,返回值为0代表关闭成功,为-1代表关闭失败。
补充:为什么一定要关闭文件,首先打开一个文件需要加载到内存,创建对于的struct结构体,如果打开很多文件而无法关闭对于计算机不是一个很大的负担吗?造成了效率和资源的浪费
3)为什么要包装一层
跨平台:每个平台的底层系统接口是不一样的,如果不包装,当代码换一个平台跑,我们就需要掌握一个平台的系统调用接口
用户体验:使用系统调用的成本很高,要理解一定的底层才能准确把握,而系统调用可以减少学习成本
三,重定项
1)重定项的使用
在编程中,重定向(Redirection)是一种将程序的输入或输出从一个默认的设备或位置转移到另一个设备或位置的操作。这种机制允许程序员将程序的输出结果保存到文件中,或者将程序的输出结果发送到网络或其他设备上。在C语言中,重定向的概念主要是通过修改标准输入输出流来实现的。
a>b//将a要进行的打印之类的程序运行结果操作到b里面输出 //会将b里面的内容清空
a<b//a将以b程序运行的结果代替键盘等默认设备作为输入 //只支持一行
a>>b// 不会清空原本b的内容,会追加输出
a<<b// 允许定义一个多行的输入,将其作为a的输入
2)重定项的原理——dup2
dup2函数可以改变文件指针的指向,我们将原本指向输入输出默认设备的文件指针改为我们想要的文件就可以了,想要实现这个操作很简单,通过Linux里面文件操作依赖与“文件”下标,我们只需要通过下标就能找到要改变的文件和被改变的文件,然后改变默认文件指针即可。
我们接下来看看dup2函数的参数
int dup2(int oldfd, int newfd);
oldfd和newfd文件描述符也即“文件数组”下标,会将原本指向newfd的文件转而指向oldfd,每对newfd进行操作就相当于对oldfd操作。如果操作成功返回newfd,失败则返回-1
接下来看个例子,将4号文件与默认输出重定项
int flag=dup2(4,2);
if(flag==-1){
cout<<"文件重定项失败"<<endl;
}
else{
cout<<"成功将默认输出与4号文件重定项"<<endl;
cout<<"666";//将会输入到4号文件
}
完结撒花