个人主页:Lei宝啊
愿所有美好如期而遇
目录
你是否听过Linux下一切皆文件?
刚开始学习Linux的时候,我们从Linux的历史,到学会使用指令入门Linux,到Linux权限理解,到gcc、make、vim、gdb、git等工具的使用,到进程(进程理解,进程创建,进程终止,进程等待,进程替换),再到IO基础等等,我们走过这样的一段路,也许不止一次听过Linux下一切皆文件的说法,也许没有听过这样的说法,不过没关系,接下来我们将会一步步去理解。
在哪里我们体悟到了Linux下一切皆文件?
文件是什么?
既然要说Linux下一切皆文件,首先我们得明白什么是文件,假如我们创建一个文件,但是没有内容,那么这个文件在磁盘占不占空间呢?
答案是占空间的,文件不仅仅只有内容,还有属性,我们在windows下可以看到文件的属性,比如说创建时间,文件大小,位置等等,所以我们应该知道,文件 = 文件内容 + 文件属性。
第二个,文件分成两种
- 磁盘文件(未被打开的)
- 内存文件(被进程打开)
我们只要不使用文件,那么文件就静静地躺在磁盘里,也就是我们的磁盘文件;但是你要是要访问文件,使用文件, 就得把文件加载进内存,加载进去就没事了吗?要是加载得多了没人管还了得?所以要被操作系统管理,建立struct file,我们也叫他内存文件,包含了磁盘文件的大部分属性以及几乎所有内容。
在哪里体悟到的?
我们初学Linux时,在命令行解释器下敲着一个又一个指令,我们当时只知道,ls,哦,这个指令敲进去,回车,当前工作目录下的所有文件都会显示出来,ls -l,再多显示个选项,但是你问我指令是什么,抱歉,当时我们不知道。
现在再回头去看,Linux下的指令都是C/C++写的可执行程序,可执行程序是什么? 不就是.exe文件?所以指令也就是文件喽,运行时在内存创建task_struct(进程控制块),将.exe文件的数据和代码加载进内存,变成进程,说到底,都是被操作系统管理的文件。
那操作系统是个啥?管理软硬件资源的软件,也就是说,操作系统也是可执行程序喽?我们开机耗费的时间干啥了,不就是将操作系统的可执行程序加载进内存吗?就连操作系统本质上都是文件!
理解了这些,我们就会发现,什么vim,什么gcc,都是可执行程序,都是文件,什么shell,也是文件。
常见疑惑
但是有些问题我就不理解了,你上面说的我都懂,但是你说Linux下一切皆文件,那好,键盘是文件吗?显示器是文件吗?各种外设是文件吗?外设明明是硬件,你怎么能说是文件呢 ?!
stdin,stdout我在学C语言的时候, 常和我说,stdin是标准输入,他对应着键盘,stdout是标准输出,他对应着显示器,那我问你
- stdin是个啥?
- stdout是个啥?
这能难倒我嘛,呵,stdin和stdout不就是C标准库提供的变量吗,类型是FILE*,这多简单,那我们接着往下看。
怎么办到的Linux下一切皆文件?
首先,我们得先理解stdin,stdout,stderr(标准错误)是个啥,他是FILE*类型的,那是不是指向FILE类型,FILE类型是什么?他是个结构体,既然是结构体,里面就得封装变量,那么封装了什么呢?我们只提及两个
- 文件描述符fd
- 缓冲区
- (两者详细参考Linux重定向原理,由重定向原理贯穿讲解 进程、文件描述符及缓冲区,这里不是重点,不多说)
(点击看完蓝色字体的文章,到这里当做你已经懂了stdin和stdout),现在我们懂了stdin,stdout,那怎么理解stdin对应着键盘,stdout对应着显示器,而且硬件怎么能被当成文件呢?
我们从Linux的设计哲学来说,原因就体现Linux操作系统的软件设计,Linux是什么写的?C语言。
我们抛出第一个问题:C语言可以实现面向对象吗?
我们从C++这门面向对象的语言来看,他之所以被叫做面向对象,是不是因为有类的存在?那么类包含了什么?
- 成员属性
- 成员方法
C语言的struct结构体可以包含成员方法吗?当然是不能的,但是没有别的办法了吗?
当然有,聪明的程序员们使用了函数指针,去指向方法,这就变相实现了面向对象 ,我们看图:
第二个问题:不同的硬件读写方法相同吗?
显示器,键盘,网卡等等都是外设,既然是外部设备,那么就会有读写方式,但是不同的外设,他们的结构都不相同,读写方法当然也就不会相同,所以你显示器有你的read,write方法,我键盘自然也有我自己的read,write方法。
所以不同硬件的read,write方法的实现一定不同!那么这些硬件的读写方法我们叫做什么呢?硬件的驱动方法!
所以我们有下图:
第三个问题:如何办到Linux下一切皆文件?
我们说,有一个文件我想读写,首先他被加载进内存,创建了struct file内存文件(这个文件里就包含了磁盘文件的几乎所有内容,不仅仅是属性,而且里面有对应外设的读写方法,即驱动方法),接着将这个结构体的地址填入读写他的进程的files指向的files_struct里的fd_array[]中,给open函数返回了fd,然后有FILE封装fd给用户返回FILE*。
从此,我们就可以对这个文件进行一系列操作了,那么是什么时候我们把磁盘当做文件的,或者说在哪个步骤,我们用户忽略了硬件?
是的,就是在struct file创建的时候,他一但创建返回了fd,我们就可以找到他,之后就可以调用他里面对应硬件的驱动方法去对文件做一系列操作。
也就是说,因为struct file的存在,不管是什么外设,我们也不用管驱动方法是否相同,我只需要找到你的struct file,不管是向内核缓冲区写入(只谈一般情况),还是内核缓冲区里的数据要刷新到对应的硬件上,那都不是我用户该操心的,这都是你操作系统的事情,因为struct file是你操作系统的内核数据结构,关我用户什么事。
至此,我们也就明白,硬件其实一直都是硬件,他当然不是文件,只是我们通过一系列的软件设计,让我们用户不再关心底层,能够统一看待struct file内存文件,对我们用户而言,我不管你底层的东西,对我用户而言,我只要能找到struct file内存文件,那么对硬件的读写就不是问题,这是你操作系统该操心的,我们用户的关心只停留在内存文件,所以我们才说Linux下一切皆文件!
我们最后整体梳理一下流程:
- 用户调用fopen
- fopen封装了open系统调用
- open打开文件返回了fd
- FILE*->fileno = fd
- 给用户返回FILE*
- 用户调用fwrite,传入参数FILE*
- fwrite封装了write系统调用
- write传入参数FILE*->fileno
- 操作系统执行write,操作系统找到调用write的进程
- 操作系统找到该进程中的struct files_struct *files
- 根据files指针找到files_struct
- 在files_struct中找到fd_array[]
- 根据fd_array[FILE*->fileno]找到对应的struct file
- ----------------------------------------------------------------
- 操作系统执行struct file里的驱动方法
- 操作系统......
到这里,我们关于Linux下一切皆文件全部讲解完,如果觉得博主写的还可以,点个关注,我们下期再见。