Linux:认识文件
https://i-blog.csdnimg.cn/direct/0e01b269e10e42f3963496dfc8dcf533.gif一、文件fd
1.1 共识原理
1、文件=内容+属性
2、文件分为打开的文件和没打开的文件 (如c中的fopen和fclose)
可以用以下的例子去理解:快递(文件) 有被人(历程)取走的快递(打开的文件)和没被取走的快递(没打开的文件),被人取走的快递研究的是人和快递的关系(历程和文件的关系) ,而没被人取走的快递,他会被暂时安防在菜鸟驿站(磁盘) 他的数目许多(文件非常多) 所以我们打算去取的时间其实我们是会收到一个取件码的(查找该文件的信息) 然后我们根据这个号码比方说3-1113 我们会找到这个地区 然后再去找号码 所以最关键的是快递怎样被按照地区划分好(对文件分门别类地存储) 这样才能方便人去取(方便我们快速找到文件并对文件的增删查改)
3、打开的文件是由历程打开的,所以研究打开的文件本质上就是研究历程和文件的关系!!
4、没打开的文件有特殊多,而且在磁盘上放着,所以研究没打开的文件关键在于文件怎样被分门别类地放置好从而方便用户快速找到文件并进行相干的增删查改工作!!
1.2 被打开的文件
由于:
(1)文件要被打开,必然要先被加载到内存中。
(2)一个历程可能打开多个文件,一个文件也可能被多个历程打开。
——>在操纵系统内部肯定存在大量被打开的文件 !!
——>操纵系统必须按照先描述再组织的方式把被打开的文件管理起来!!
1.3 回忆C的文件操纵接口
1.3.1 文件的打开和关闭
https://i-blog.csdnimg.cn/direct/a44effdd19c743e381c72652699e7ea7.png
https://i-blog.csdnimg.cn/direct/7714bae065a94e0294c05514e3ee4f5f.pnghttps://i-blog.csdnimg.cn/direct/1a99b32f59ab4820b17f1e6358556e4f.png 问题1:为什么我们默认会新建在当前路径,凭什么???
——>当前路径,其实就是历程的路径,因为历程在执行的过程中,他必要知道自己从哪来,也要知道如果自己产生一些临时性的文件时应该放在哪里,所以他必要一个默认路径cwd。表明的是他当前的工作目录。
——>因为历程PCB布局体内部有一个cwd属性,如果我们更改了历程的,cwd属性,就可以将文件新建到别的地方!!
问题2: 先被加载到内存的是文件的属性还是文件的内容??
——>当你fopen的时间,其实就必要创建一个文件的内核数据布局,内里包含了文件的一些必要属性,所以是属性先被加载进去了!! 至于内容需不必要被加载,关键看你有没有通过一些接口来对该文件历程增删查改!!
1.3.2 文件的增删查改
https://i-blog.csdnimg.cn/direct/9705956efb374100ac58c14dde18cc25.png https://i-blog.csdnimg.cn/direct/75b3f4a5dc96453ab8726b04ec158b5d.png
w:在写入之前,会对文件进行清空处理!! 所以我们可以知道echo重定向方式写入也会先清空文件,所以底层必然也是w形式!!
https://i-blog.csdnimg.cn/direct/8bd7f45348d04c0d82457b8271db65e4.png strlen默认是不加/0,如果我们+1带上/0,此时会打出一个乱码,但是这个乱码是什么并不重要,重要的是我们发现/0也是一个可以被写进去的字符!!
为什么???——>因为字符串以/0结尾,是C语言的规定,跟文件、跟操纵系统没有任何关系!!
https://i-blog.csdnimg.cn/direct/6daf22dc116c479ca611c2375ded90b7.png
a:在文件的结尾追加写!!
1.4 过渡文件系统调用https://i-blog.csdnimg.cn/direct/c8f5df25b8c04b9981d394c9edd955a6.png
因为文件在硬盘上,所以我们想要访问文件其实就是访问硬件,因此几乎全部的库想要访问硬件设备,就必须封装系统调用!!
https://i-blog.csdnimg.cn/direct/ecda27b49cee438b87318e1805d19d12.png
https://i-blog.csdnimg.cn/direct/caa30a07cb194bf787db086ffdf42d20.png
参数pathname是文件名,参数flags是打开的模式,而mode是权限设置 因此第一个open是用来打开一个已经存在的文件,而第二个open打开的是新建的文件(因为我们必要给新建的文件设置权限!)
https://i-blog.csdnimg.cn/direct/ae7bee6863144aa5b0979371ad5850e3.png
https://i-blog.csdnimg.cn/direct/d98080ee99b14fe2842090cbec575c7c.png
如果用第一个open去新建不存在的文件,会出现文件的权限错误!!所以必须用第二个open!
https://i-blog.csdnimg.cn/direct/783cdbbba72e4644abbbcbc9f5cb27d7.png 关于文件权限的传递,我们要记得因为有粘滞位umask 所以我们想要设置的权限可能并不是我们最终想要的,所以我们要向用umask把该历程的粘滞位变成0!!
https://i-blog.csdnimg.cn/direct/7e82982a68ec4c6680a1d2007d6803dc.png
https://i-blog.csdnimg.cn/direct/74ff33f75c464f9aa7354d915bdc5961.png 1.4.1 比特位方式的标记位传递原理
https://i-blog.csdnimg.cn/direct/3984731081c9483a9ea6b18bb189c3d3.png
状态的组合方式有许多种,但是为什么操纵系统只用一个int类型就可以表明这些情况??
——>以往我们必要许多标记位的时间我们本能想到的是多创建几个参数来表现,但当位图出现后,我们想到了可以用int类型的32个bit位来表现各种不同的组合。
模拟实现:
https://i-blog.csdnimg.cn/direct/cfb12a77980c4a02a5a3139839578fd6.png
https://i-blog.csdnimg.cn/direct/a63387912c3f4f9e9b68db0bdb064056.png
https://i-blog.csdnimg.cn/direct/1d0bc4e2410140e791902b00abffe28a.png
通过位图的方式一次向一个调用传递多个标记位,这是操纵系统传递参数的一种方式!!
——>本质上是在外部用 | 的方式组合 在内部的方式用& 的方式检测!!
1.4.2 访问文件的本质
问题1:以前我们学C语言的时间,fopen的返回值是一个FILE* 那个时间我们知道这个是C库封装的一个布局体,但是为什么系统调用办理ooen的返回值是一个整形呢???
https://i-blog.csdnimg.cn/direct/9232e42585214c529a6ddc30be7f5b14.png
因为一个历程可能打开多个文件,那么我们想要快速地找到任意一个文件,如果仅仅是用链表的方式组织,确实太慢了!!
所以在PCB布局体内部,其实有一个file_struct*指针,该指针指向一个file_struct布局体,该布局体就是操纵系统给该历程提供的一个文件描述符表,内里除了一些必要的字段信息,另有一个存放file*指针的指针数组,这些file*指针分别指向一个个被该历程打开的文件!!
https://i-blog.csdnimg.cn/direct/abbc4b60a8c2468d8a6034c8f3c6555e.png
——>所以fd我们称之为文件描述符,他的本质就是文件描述符表的下标,我们可以通过这个下标里存储的file指针找到我们想操纵的被打开的文件!!
问题2:file布局体内里有什么呢??
——>肯定直接大概间接(间接的意思是可能内部另有别的布局对象)包含如下属性:
(1)在磁盘的什么位置
(2)基本的属性:权限、巨细、读写位置、谁打开的)
(3)文件的内核缓冲区
(4)引用计数(因为一个文件可能会被多个历程打开,所以当一个历程关闭该文件的时间不代表这个文件的布局体就被释放了,而是要引用计数为0时才释放)
(5)file* next:链表节点布局,可以跟其他的文件链接成双链表的形式做管理!
……
1.4.3 理解文件描述符fd
一个历程在打开的时间默认会打开3个文件:尺度输入文件(键盘)、尺度输出文件(表现器)、尺度错误文件(表现器)…… 他们的fd分别为 0 1 2
问题1:有没有觉得很眼熟??因为我们在学C语言的时间也知道C程序会默认打开3个流!有什么关系??
——> 尺度输入流、尺度输出流、尺度错误流其实并不是C语言的特性!!而是操纵系统的特性!!
https://i-blog.csdnimg.cn/direct/884afaa68e4244b5a4b5a85f2a629a2e.png 问题2:FILE* 是什么??怎样理解?
——>FILE* 是一个C库自己封装的布局体,由于系统调用接口用的是fd文件描述符来对指定的文件进行操纵,所以我们可以知道FILE布局体内部必然封装着文件描述符fd!
问题3:为什么肯定要打开这三个流呢??
——> 因为我们的电脑开机的时间,我们的操纵系统就默认检测到了表现器、键盘这类的设备,所以历程打开的时间就必然必要有这些,因为我们程序员天然必要通过键盘、表现器来观察结果。
1.4.4 文件描述符的使用
close是在3号手册的系统调用接口,可以把传进去的文件描述符对应的文件给关掉!!
https://i-blog.csdnimg.cn/direct/ec834a338ea5416ca5cc1a0ee870fea4.png write是系统调用接口,用来向指定的文件描述符文件输出内容,其中
buf:表现我们要写入的内容,是void* 也就是说他并不限定你传什么内容
count:期望(最多)写多少字节的内容
返回值ssize_t:实际写了多少字节的内容
https://i-blog.csdnimg.cn/direct/17cf5a9082374acbaca81bb7032981a6.png
https://i-blog.csdnimg.cn/direct/09ff03ae3cec41f1b7e9969430f89ab7.png
问题1:为什么要在反面+个/0??
——> 因为我们使用的是系统调用接口,而且参数buf也是void*指针,所以他并不知道你传的是什么,只知道默认把一个个字符放在缓冲区里,所以如果我们想让他按照C语言字符串的形式去读取出来,那么就必要加个/0 因为这是C语言的规则。
1.4.5 三个流的理解
https://i-blog.csdnimg.cn/direct/ecc3849f7f79457ea961ef06fff48033.png
https://i-blog.csdnimg.cn/direct/94b0490fa59f48e68ba6668eb6a214fd.png printf是C库函数 底层封装的时间默认向 stdout的1号描述符里写入,所以如果你把1号给关了,printf底层调用write这个函数会失败,但是printf自己并不知道,所以他是有返回值的。而fprintf的长处是可以指定我们想要输出的流,所以当我们想stderr的2号描述符写入的时间,恰恰也是指向表现器文件,所以就会被打印出来!!
——>侧面可以证实 文件的file布局体里必然存在引用计数!! 因为在1号描述符关闭之后,表现器文件并没有被关闭,所以close底层的操纵就是对count计数--,然后将文件描述符表的指针置空,但是表现器文件还是打开着的,因为2号描述符还指向表现器文件!!
总结:任何一门语言对文件描述符的封装不一样,是千变万化的,比如在C++中可能还会有继续和多态的体系。但是万变不离其中,在底层都是使用的操纵系统提供的系统调用接口,对fd文件描述符进行封装。 所以底层理解了,其实任何语言都是学习他的应用而已!!
1.4.6 内核的源码下载
https://i-blog.csdnimg.cn/direct/bd1864bbc1954f7dbab06f26481c99e2.png
https://i-blog.csdnimg.cn/direct/8404247ca7794e38a16f0cdee27af3fd.png
二、重定向再理解
2.1 输出重定向和追加重定向
https://i-blog.csdnimg.cn/direct/62b3f579cee946658507693931030cbe.png
https://i-blog.csdnimg.cn/direct/8c7239a6862641c9aa34bf153cd03aad.png
但是我们把他1号关了,然后又打开了一个新的文件, 发现本来应该写到1号的尺度输出文件里! 这说明在close将1号位置置空后,该文件就补上了这个位置!! 说明文件描述符的放置规则是从0下标开始,探求最小的没使用的数组位置,他的下标就是新的文件描述符!!
这不就有点像 我们之前学的输出重定向,所以我们可以发现其实输出重定向的本质就是将文件的输出描述符对应的指针信息更换成文件的指针信息!!
https://i-blog.csdnimg.cn/direct/e40714a832544457846e74c51f66110d.png
问题1:岂非我们必须要先把1号文件关闭了再打开新的文件才能完成重定向吗??
——>其实本质上就是将新文件的指针覆盖掉原来1号位置的指针就行了,系统提供了一个接口叫dup来帮助我们办理这个问题!!所以输出重定向和追加重定向底层肯定使用了dup接口!
https://i-blog.csdnimg.cn/direct/6a3192e6e45f433d833aa7e555e0fe5d.png
他是用oldfd覆盖掉newfd
https://i-blog.csdnimg.cn/direct/e5b0da2fa94647d399f7df4cfb284d50.png
问题2:历程更换会影响文件的重定向吗??
——>历程汗青打开的文件与进行的各种重定向关系都和将来进行的程序更换无关!! 他们是两个模块,程序更换并不影响文件访问!!
https://i-blog.csdnimg.cn/direct/41802f3b76b64e4eaaf2bbaaefb17925.png
2.2 输入重定向
read的使用:https://i-blog.csdnimg.cn/direct/0b12c597415c4a05a87cee2a3ea966d3.png
输入重定向:
https://i-blog.csdnimg.cn/direct/6eb452fc5c2f4109b8b8122f4becf050.png
2.3 一个测试
我们知道printf默认是向1号文件写入,但如果我们将1号的表现器文件关闭后,然后用一个新的文件去更换该位置,于是printf就变成了向文件打印。
但是我们会发现,我们用fprintf的时间传参写入的是stdout,可是该文件却也写到了文件内里而不是写到了表现器上。
——>这说明了,其实不管是stdin、stdout、stderr都是一个布局体,底层都是封装文件描述符对应的是0、1、2, ——>说明上层只知道要向文件描述符位置写入。纵然你通过系统调用将该位置的文件信息改了,他也并不知情。
https://i-blog.csdnimg.cn/direct/893abedda519474f99312a1a9ebbcceb.png
2.4 shell的重定向封装
所以今后可能我们会遇到这样的指令,所以我们必要在字符串拆分的部分去做一些特殊处理,来判定究竟是输出重定向、追加重定向还是输入重定向!
https://i-blog.csdnimg.cn/direct/d04f8b942c274149b42c84d9bd5df4b3.png
先对这些情况做一个宏的定义,为了后期在普通下令执行的时间做区分
https://i-blog.csdnimg.cn/direct/e7b11e987a964baf99cb9f4272fded5c.png
封装一个函数检查一下字符串是否涉及到重定向的问题(今后检测看看会不会遇到>大概<),如果是的话判定是哪种类型。
https://i-blog.csdnimg.cn/direct/a00c299c2cda4255af1a866914b76169.png
在普通下令的执行这边根据宏进行判定
https://i-blog.csdnimg.cn/direct/62571285298d4741b5f5575ea223d481.png
2.5 重定向的本质写法(为什么要有stderr)
https://i-blog.csdnimg.cn/direct/5049d7e90afb4cbe963d914ae9a83a5b.png
1、将程序的运行结果分别重定向到两个不同的文件(这样我们可以把运行结果放到我们的正常文件里,然后把错误的一些信息放到我们的错误文件里,方便我们观察——>这就是为什么要有stderr的缘故起因)。
https://i-blog.csdnimg.cn/direct/5ace6812e8f842b9bfef3ea099c821fc.png
这才是重定向的正确写法,只不外我们平常不写fd的话默认就是将1号文件的内容重定向。
2、将两个文件的结果都写到一个文件
https://i-blog.csdnimg.cn/direct/2327598617284613896e065d29094056.png
这个意思是1号文件的地址变成了all.log 然后2也被写入到原来1的位置,所以最后其实都被写到了all.log文件内里
三、怎样理解一切皆文件
1、盘算机上进行的全部操纵,全部使命都会被系统检测成历程,所以历程是操纵系统帮助用户完成使命的主要渠道,几乎没有之一
——>因此我们如今对文件的全部操纵其实都依赖于历程操纵!!
2、而我们全部的外设都必要提供相应的读写方法 (跟文件有点类似)!所以我们会实验把外设也当成是文件来处理,在使用的时间在内核层面搞一个file布局体, 但是这样会遇到一个问题就是,并不是全部的外设都有读写方法的!!比如说键盘只有写方法,而表现器只有读方法,那我们的file要怎样做区分呢??
——>所以操纵系统还给这些文件提供了一个 方法 布局体 内里生存了读方法和写方法的指针
这俩函数指针指向的是各个外设的读写方法。(就比如说我当前想要打开表现器,在创建file布局体的时间顺便创建了方法布局体,内里的读写的函数指针分别指向表现器的读方法和写方法,所以因为表现器只有写方法,读方法是空,于是在调用的时间就自然区分得出来了)
https://i-blog.csdnimg.cn/direct/55164235fa1e473f8db92209304645eb.png
3、其实这就是VFS 虚拟文件系统,所以可以理解Linux一切皆文件。
——>其实我们还可以发现 这个文件其实就是基类,而外设就是派生类,然后指针指向什么就调用什么对象,这就是多态,只不外Linux必须用C语言写,所以只能用函数指针来完成这个工作!!
https://i-blog.csdnimg.cn/direct/989f2adb5f0c4e048c24042ec1faea11.png
4、理解了Linux的一切皆文件后,懂得了文件操纵的底层,纵然以后在使用其他语言的文件操纵时对接口不熟,但只要给时间查一下,很快就会懂得怎么用了!!(没有太多的恐惊),这就是理论知识所带给我们的气力和底气!!
四、理解为什么会有面向对象
如果面向对象是C++的专属,那么他只能算是一种特性,但是当你发现大部分主流语言都是支持面向对象的,那么这就说明面向对象不是一两门语言的特性,而是汗青的必然!!
为什么会有人能凭空想出来面向对象的语言呢??
——>因为人们在颠末大量的工程实验后,发现我们总是或多或少要使用一些多态的特性,比如说写操纵系统的人必然也是有可能开发语言的人,他在写的时间就意识到Linux内里许多虚拟化的东西,要不是你必须拿C去写,我早就发明出一门面向对象的语言了,直接搞个基类派生类出来就很快了!! ——>因为许多地方必要对软件做分层,设置出各种虚拟化的场景(比如刚刚提到的文件虚拟系统就是,只不外Linux必须用C写,否则肯定用C++写更方便) ——>封装、继续、多态!
五、缓冲区深入理解
3.1 引入一些奇怪的征象
征象1:为什么向文件写入的时间 write会先被打印出来??
https://i-blog.csdnimg.cn/direct/05452aea4b1d43878abc04fe2f0ad295.png
https://i-blog.csdnimg.cn/direct/990a0b8bc80a4358955cc8f7f7f7c206.png
征象2、为什么加了fork之后,向文件写入时C接口会被调了两次??且向文件写入时write先被打印?
https://i-blog.csdnimg.cn/direct/5e4193955fb749c0856b3d85b0884445.png
https://i-blog.csdnimg.cn/direct/ffba9401b9784242bf94660c8818604e.png 征象3、close1号文件后,为什么就没有结果了??
https://i-blog.csdnimg.cn/direct/846e490e6845418281f5d04975f11478.png
3.2 缓冲区不在操纵系统内部!
通过征象3,我们发现一旦close之后,内容就不见了!! 因为当代操纵系统不做浪费空间和时间的问题,所以close作为系统调用接口不可能不在关闭文件之前革新缓冲区,所以这说明他根本看不到这个缓冲区!!
https://i-blog.csdnimg.cn/direct/ff048112183f4b4db80527fe39980057.png 所以我们的库函数接口是先把内容放到一个C提供的缓冲区,当必要革新的时间,才会去调用write函数进行写入!!
——>所以close后革新不出来的缘故起因就是:历程退出后想要革新的时间,文件描述符被关了,所以纵然调了write也写不进去,缓冲区的数据被丢弃了
(注意:历程退出的时间是会革新缓冲区的!!)
3.3 缓冲区的革新战略
1、无缓冲——>直接革新——>fflush函数
2、 行缓冲——>遇到/n革新——>表现器文件
3、全缓存——>写满才革新——>普通文件
问题1:为什么要有这些不同的方案??
——>一样平常来说写满再革新服从高,因为这样可以减少调用系统接口的次数,而表现器之所以是行革新,因为表现器是要给人给的,按行看符合我们的风俗,而文件接纳的就是全缓存战略,因为用户不必要马上看到这些信息,所以这样可以提高服从。 而对于一些特殊情况我们就可以用fllush之前欺凌革新出来。 ——>所以方案是根据不同的需求来的!
问题2:解释征象1
——>当我们从向表现器写入变化为向普通文件打印时,此时革新战略从行革新变化为全革新,所以前三个C接口并没有直接写入,而是暂时生存在了缓冲区内里,而write是系统调用接口优先被打印了出来,之后当历程退出的时间,缓冲区的内容才被革新出来。
问题3:解释征象2
——>跟征象1一样,前三个C接口的数据暂时被存在了缓冲区,而write的调用先被打了出来。当fork的时间,子历程会和父历程指向雷同的代码和数据,当其中一方打算革新缓冲区时,其实就相当于要修改数据,操纵系统检测到之后就会发生写时拷贝,于是缓冲区的数据被多拷贝了一份,而后该历程退出时就会再革新一次,因此C接口写入的数据被调了2次!!
3.4 为什么要有缓冲区呢??
举个例子,比方说你和你的好朋友相隔千里,而你想要给他送个键盘,如果没有快递公司和菜鸟驿站(缓冲区)的话,那么你可能得坐车好几天才能到他那里,但如果你的楼下有菜鸟驿站和快递公司,那么你只必要下楼付点钱填个单子就行了,接着你可以去忙你自己的事变,当旁边的人问你键盘去哪里的时间,你会说已经寄给朋友了,其实这个时间你的键盘可能还在快递公司放着。
——>
1、从总体来看东西是你送还是快递公司送其实都差不多,区别就是你不必要操太多心。因此缓冲区方便了用户!!
2、快递公司可以有不同的战略来提高整体的服从,比方说你这个快递不急,那么我就等快递车装满了再送(全革新) ,如果比较急,我就装满一个袋子就送(行革新),如果你特殊急,可以通过加钱(fllus欺凌革新)来加急。 所以缓冲区办理了服从问题
3、 配合格式化!比方说C语言经常必要%d这样的格式化,我们的数字123 最后被打印的时间也是要转化成字符串的123 才能调用write写入,因此我们可以将这个解格式化的工作放在缓冲区去完成!!
https://i-blog.csdnimg.cn/direct/62bca8090e334e00be05e0ea9134415a.png
3.5 用户缓冲区在哪?
我们回忆一下exit和_exit 区别就是exit会先调用一次fllush把缓冲区的数据革新出来。
https://i-blog.csdnimg.cn/direct/cf7216ae1eb34716b751cd943000fde3.png
我们会注意到他传递的参数是FILE* 类型
https://i-blog.csdnimg.cn/direct/6491a615cdba489e9af5c6808c3d9a36.png
——>FILE* 不但封装了fd的信息,还维护了对应文件的缓冲区字段和文件信息!!
——>FILE*是用户级别的缓冲区(任何语言都属于用户层),当我们打开一个文件的时间语言层给我们malloc(FILE),同时也会维护一个专属于该文件的缓冲区!! 所以如果有10个文件就会有10个缓冲区!!
https://i-blog.csdnimg.cn/direct/a811d00c68f64f1aa61ba8eb46e74432.png 3.6 内核缓冲区在哪??
内核缓冲区也是由操纵系统的file布局体维护的一段空间,和语言的缓冲区模式是类似的,作为用户我们不必要太关心操纵系统什么时间会革新,我们只必要认为数据只要革新到了内核,就必然可以到达硬件,因为当代操纵系统不做任何浪费空间和时间的事变。!!!!
https://i-blog.csdnimg.cn/direct/90901f9992e1423e87804a8cc3755f67.jpeg
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]