linux文件——文件体系与内存管理——理解打开文件, 写入数据与文件体系的
媒介:本节课算是一点文件体系的补充内容。 但是说是文件体系的补充内容,其实我们也可以把这篇文章当作linux下的内存管理的文章来看待。 因为博主会从内存管理的角度, 将进程打开文件、写入数据的流程, 以非常底层的角度, 给剖析出来。 ——本篇文章大概讲的大概不会很过细。 但是博主信任友友们如果看完本篇文章后,一定会对于文件打开, 文件写入和磁盘之间的关联有一个很清晰的熟悉!!!ps:留意本篇内容非常抽象, 非常难以理解。没有前面文件体系、文件管理、进程管理的铺垫大概无法学习本节。发起友友们相识对应只是后再来学习本节内容!!!
目录
页框和页帧
页框
页帧
数据的交换
操作体系如何管理内存?
先形貌
再组织
从用户层到内存
重新理解文件管理
struct inode
adress_space
radix_tree剖析——补充知识
从内存到磁盘
页框和页帧
页框
对于物理内存来说, 内存可以分成一块一块的, 并且这些一块一块的内存是一连的, 也就可以被当成一个线性的逻辑结构, 如下图:
https://i-blog.csdnimg.cn/direct/a029ef89bdb94724915a90a84e53977d.png
这物理内存中一块一块的4kb的块, 就叫做页框。 ——这里为什么是4kb, 不太紧张, 而且很简单, 这里先卖关子。
页帧
不但只物理内存的逻辑结构可以是线性的。 我们也知道磁盘的逻辑结构也是线性的。 并且里面的存储内容是以块为单元的!!! 那么我们的步伐, 在磁盘中保存的时间, 其实就是一块一块的分开保存的。 如下图:
https://i-blog.csdnimg.cn/direct/210a2f06a1f3463584e1863154168c22.png
留意, 这里的块是一连的, 因为一个步伐就是一个文件, 而我们说过, 一个文件的块是一连的。 这里的这一个个的块, 就叫做页帧。
数据的交换
我们知道, 在进行IO的时间, 物理内存和磁盘的数据是来回加载过来加载过去的。 而之所以上面的块都是4kb, 就是因为方便交换, 这个4kb就是交换数据的单元。(为什么是4kb, 因为是大佬们规定的。这也就回复了物理内中的页框是4kb一个。)
https://i-blog.csdnimg.cn/direct/a60dacea3d4b41eb9e5a2eb982d19941.png
在物理内存和磁盘进行数据交换的时间, 会以4kb的巨细进行加载。 这个时间, 即便我们都要加载的数据只有1字节有效内容, 也只能将连带1字节在内的4kb一起加载进来!!!
为什么会如许呢? ——因为我们的磁盘是机械设备,而机械设备的速度相对于计算机内部的速度(光电速度)是很慢的。 那么就说明磁盘存储数据或者拿数据向内存中加载数据是很慢的, 那么我们一次性向内存中加载4kb数据和一次性向内存中加载1kb, 但是加载4次相比肯定要快的多得多。 ——这个过程只须要定位磁头, 定位磁道, 定位扇区, 将数据一连的写到磁盘上即可。
不知道友友们是否知道局部性原理——也就是我们一样平常访问代码和数据的时间, 有非常大的概率, 在该正在访问的代码地区的附近, 也会被访问。 ——所以我们在加载内存的时间, 即便只有1字节, 也要把这一字节的上下文全部加载进来。 这就是——基于局部性原理的预加载机制。
所以, 综上, 一次性加载4kb如许做有什么利益呢?
[*]减少IO的次数——减少访问外设的次数——硬件层面
[*]进行预加载——有较大概率在软件层面上是实现服从提升
操作体系如何管理内存?
这里我们须要提前知道的是——我们从内存的学习中相识到了虚拟地点空间, 而操作体系想要管理物理地点。 那么就要可以或许看到虚拟地点, 同时也要看到物理地点。 ——为什么会如许?
因为操作体系给我们的虚拟地点利用页表进行映射物理内存, 分配物理空间, 如果操作体系看不到物理内存, 如何给进程分配物理空间呢? 如果看不到虚拟地点, 进程如何实现我们看到的表现器上面的数字呢?
所以,操作体系是可以或许看到内存的物理地点的!!!那么如果操作体系想, 是不是就可以或许管理物理地点了?——那么操作体系是如何管理物理地点的呢?
详细操作体系如何管理地点, 这对于学习了如何进行文件管理, 如何进行进程管理的我们来说太简单了——也就是先形貌,再组织!!!(如果友友不知道,那么大概须要先学习一下前面的知识, 因为这六个子是整个计算机世界进行管理的本质)
先形貌
结构体的名称是page, 并且形貌的是物理内存的一个个的页框。
struct page
{
//page必要的属性信息。
}//描述的是一个个页框 再组织
如果这里我们的物理内存一共有4GB, 那么如果按照一个页框4kb来算, 物理内存就有大概一百万个页框, 计算后其实是1,048,576个页框。
一个struct page对象用来形貌一个页框, 那么操作体系要将内存组织起来就要创建一个一百万巨细的struct page对象组成的数组。 如下:
struct page page_arrar;//创建一个结构体数组进行组织 所以, 我们操作体系对内存的管理, 就转化为了对于数组的管理!!!
又因为数组天然是有下表的, 所以页框就有了下标!也就有了页号如许的概念!(相识即可, 本篇文章并没有用到页号)
那么这里就可以得出结论了——我们要访问一个内存 ,只须要先直接找到这个对应的4kb对应的page, 就能在体系中找到对应的物理页框(在操作体系中, 所有申请内存的动作, 其实都是在访问内存page数组)。
我们检察一下linux下面的page结构体, 这里先看一部门, 一个flags, 一个_count
https://i-blog.csdnimg.cn/direct/2a872ce7f39640578fd76957a583dad6.png
这两个字段, 我们虽然没有学过内存管理, 但是我们已经学习了虚拟地点空间, 学习了页表。 应该也可以或许看出来——flag代表映射的物理内存的状态, 是否是只读, 是否是只写等等。而_const就是引用计数, 什么是引用计数呢? 是否还记得我们在学习父子进程的时间, 说过:子进程刚刚被创建出来的时间, 我们的父子进程的页表大概会指向同一块代码和数据。而这里的引用计数其实就是有多少页表会指向这块空间。
操作体系是如何发现缺页中断, 是如何发现要执行写时拷贝的呢?——其实非常简单, 我们知道, 我们的子进程在创建出来的时间, 指向的物理空间应该是只读的, 并且这个时间, 这块物理空间的引用计数应该是2, 那么我们如果看到某一块的物理内存的flags是为只读, 并且这块物理空间内的引用计数大于2, 那么就要执行写时拷贝!!!
page里面还有一个链表结构——lru(这是个数据结构, 友友们大概学过), 也就是近来最少利用, 我们的操作体系要把我们的内存中不太利用的, 不长利用的革新出去。因为如果一个一个遍历, 那么就太慢了,所以就用到了一个lru的数据结构。
https://i-blog.csdnimg.cn/direct/4e0c839493c1435dbf2b7434b75b5eee.png
其实对于操作体系内存管理的上层还有伙伴体系算法, slab分派器。 这里解释一下slab分派器
slab分派器就是一种对于垃圾数据结构回收的机制——就比如我们创建进程, 必须要有虚拟地点空间, 也就要有mm_struct对应, 也要有struct page对象。 当我们开释掉这些对象的时间, 我们的操作体系就大概不去开释了, 而是直接将这些对象保存在了一个队列, 或者一个数组等等数据结构里缓存起来。 等到下次创建对象的时间就直接拿过来用, 就不用再重新向操作体系中申请内存了!!!
从用户层到内存
我们电脑开机的时间, 根本上会将文件体系的前四个信息内容, 加载到内存中, 也就是下面这四个内容:
https://i-blog.csdnimg.cn/direct/e10eb6acda094954b782067a9c05a1dd.png
那么问题就来了, 我们的磁盘里面可不止有一个分区, 而是由很多个分区。 这些分区大概文件体系不一样, 也就是super block的内容不一样。 那么怎么办呢? 没关系, 其实操作体系就是利用一个链表结构, 将所有分区的super block进行链接起来。 然后在操作体系的层面上就知道了我们分区的位置, 然后还有文件体系的样子等等。如下图:https://i-blog.csdnimg.cn/direct/54dca53593f3440d82b69c11fa290159.png
重新理解文件管理
struct inode
如今我们来回想一下文件fd
我们在本日这节课之前理解的文件fd其实就是下面这一张图
https://i-blog.csdnimg.cn/direct/47816083b6dd4e2482c6d01ce74a35db.png
本日, 我们要重新理解一下这张图了。
其实, 我们在加载inode属性信息的时间, 不是将磁盘中对应的inode属性直接加载到struct file里面。 而是先创建另一个结构体对象, 这个结构体对象叫做struct inode, 然后将大部门文件的属性保存到这个结构体里面。 然后struct file里面包罗一个指向这个结构体的指针, 如下图:
https://i-blog.csdnimg.cn/direct/cd392afa372543d185f1fdb2e5bfcf31.png
然后我们的上层, 也就是应用层。 我们是如何打开的一个文件? ——这里以前讲过, 这里相称于重新复习。 这里只是简单的说一下流程。
起首我们要知道这个文件的路径+文件名。 ——》这就相称于知道了当前目录, 然后我们就可以读取当前目录的数据块, 找到对应目录的inode, 进而找到对应的struct file 对象的inode, 然后检察已经加载到内存中的inode bitmap, 看这个inode是否存在, 如果存在, 再讲磁盘中等等属性信息加载到内存中。
adress_space
别的其着实struct file里面, 存在一个数据结构的指针, 这个数据结构叫做adress_space(地点空间)。 如下图:
https://i-blog.csdnimg.cn/direct/e0916f40292145ecab11ac33b14594e0.png
adress_space这个数据结构里面包罗一颗radix树, 这棵树叫做page_tree
https://i-blog.csdnimg.cn/direct/5690a0868c7947d4a724bf56d6334107.png
radix_tree这个结构的界说如下:
https://i-blog.csdnimg.cn/direct/7662b34e466c468b8afb7248579f19c6.png
这是整颗树的结构体界说。这棵树是一颗多叉树:
https://i-blog.csdnimg.cn/direct/e407f090e31240d5bc9a5f7ac6f2167b.png
这个叶子结点, 其实就是上面的radix_tree_node类型的对象。
真正的树节点的结构是下面这张图:
https://i-blog.csdnimg.cn/direct/653f31eb200b4ab7a96ebc8156f517e7.png
radix_tree_node这个结构体里面, 我们看一下slots指针数组。 这个指针数组其实就是当前节点的叶子节点的指针。 上图中有RADIX_TREE_MAP_SIZE个数组元素, 说明这是一颗除叶子结点外,每个节点都有RADIX_TREE_MAP_SIZE个孩子节点的多叉树。
那么大概有的友友们会疑惑了, 这颗树的叶子结点连接的是什么呢?是不是指向空呢?——答案是指向的是我们的page!!!——我们知道, 我们利用fopen, fprintf本质上就是先向内存中写入数据, 再将内存中的数据革新到磁盘里面。而page就是物理内存, page如今被一颗树指向了!!那么是不是也就是说, 我们的用户层, 在利用fopen, fprintf向文件中打印的时间, 一定会先按照一种流程, 这种流程从上到下贯穿整个文件管理, 不停进入地点空间, 然后穿过整颗radix树, 末了找到page, 将数据写到物理内存里面!!!——而这, 就是整个的fopen, fprintf写入数据的过程。 这, 也是内存进行管理管理的过程。 这里的page,这颗树连接的所有的page, 组成的这个page_array[]数组, 其实就是文件页缓冲区, 也是我们所说的内核级缓冲区!!!
画成图就是如许子:
https://i-blog.csdnimg.cn/direct/2060b3f2641a4a8caee5829b5e377f7b.png
这其实就是用户层到内存的整个流程, 也可以说是结论。
radix_tree剖析——补充知识
radix_tree到底是一棵什么样的树呢?
其实, 这棵树叫做基数树 或者 基数——》本质上就是一棵字典树。
这棵树的节点的结构大致如下:
struct node
{
int level; //层数
void* slot; //这个NUM可以是很多数, 可以是26, 可以是3, 可以是5
} 我们这里假设NUM是3, 也就是说这棵树每一个节点都有3个子节点。 并且, 字典树的层数一样平常和这个NUM是雷同的。 也就是说, 每一个节点有几个孩子节点, 这棵树就有几层。 假设NUM为3, 那么这棵树就有三层, 并且每个节点的子节点都是三个。
https://i-blog.csdnimg.cn/direct/300af6559ccf4b0d9211adc6349025d9.png
上面这就是一颗字典树,末了叶子节点的slot指向的是要保存的数据. 也就是说, 一个叶子节点, 可以保存三个数据块。 那么如何找到某个数据呢?——如果我们要找bca这个数据, 那么就先找第一个节点的b子节点, 再找b子节点的cc子节点。 以此类推, 直到找到末了要查找到的数据, 上图中用红线画的就是整个查找流程。
文件的内容按照4kb是有偏移量的, 参加本日我有一个10MB的巨细的文件。 如下图:
https://i-blog.csdnimg.cn/direct/0cf20a936bea465c97a1a501125fa299.png
所以这里块的个数就是10 * 1024 * 1024 / 1024 / 4 = 2560
然后每一个数据块的下标乘以4kb就能计算出这个数据在原始内容中的偏移量。
所以, 如果我们提前构建出一颗字典树, 那么我们如果将我们的文件内容的下标(下表是int类型), 以比特位为单元, 按照某种规则构成特定的几个地区, 在字典树当中找到对应的偏移量和page的映射关系。 就能通过偏移量等等直接查找物理内存了。
那么将来我们读写文件, 都是通过偏移量, 我们一旦有了偏移量, 根据这棵字典树里面的映射关系, 我们就可以找到对应的page。 ——这个偏移量的详细细节博主也不清楚, 知道就行
偏移量不太紧张, 但是下面的这个结论是非常紧张的!
这里的这个文件, 是有自己的文件页缓冲区的。 其实, 在linux中, 我们的每一个进程, 打开的每一个文件, 都要有自己的文件页缓冲区!!!而这个文件页缓冲区, 就是字典树可以或许找到的所有的page结构体对象!!!
从内存到磁盘
上面我们已经很清楚的讲解了数据如何从上层用户写到内存中。 那么接下里的一步就是如何从内存中写到磁盘里面。
https://i-blog.csdnimg.cn/direct/64883f994793469792e0605515ac01f9.png
其实我们从这张图里面可以看到, 进程是不关心文件管理如何向内存中写入数据的。并且, 写到了物理内存之后, 文件管理也不管物理内存了。 那么从物理内存到磁盘, 到底是怎么管理的呢?——其实, 之后的任务就是驱动步伐的工作了, 这个也叫做IO子体系。
而要管理好将来我们的大量的IO哀求, 操作体系就要对这些哀求进行形貌, 再组织起来。
而形貌的结构体是一个驱动级别的结构体(知道就行,博主也不理解什么是驱动级别的结构体)
struct request
{
//这里面包含了与请求相关的各个page;
//这里面也包含了LBA, 也就是包含了要在磁盘的哪个位置写入数据!!!
} 形貌了之后, 体系如果想要将这些IO哀求组织起来, 用的是大量的request 队列——为什么利用队列, 这是因为IO也须要列队的。
https://i-blog.csdnimg.cn/direct/47a50ea00c174c8caa3cb8dc6df48dcc.png
但是, 这里有一个问题——大概我们的这个队列里面存在了如许一个情况, 比如进程1访问1号扇区, 进程2访问6号扇区, 但是进程3访问3号扇区等等。 就是说磁盘在进行访问的时间, 如果直接按照序次访问队列的进程, 那么磁头就大概访问不到一连的地区。 而磁头的重新定位会耗费大量的时间(相对于计算机内部), 所以我们再进行定位的时间要尽大概让我们的哀求访问同一个盘面的, 同一个磁道的, 一连的扇区。 如许服从才能包管最高。 所以, 我们的操作体系就可以对上面的队列进行IO排序, 然后再IO归并。 以此来包管可以或许访问一连的扇区。
颠末操作体系的组织之后, 将request对象拿给磁盘驱动, 再由磁盘驱动去执行, 就可以或许将数据从内存写到我们的磁盘里面了!!!
那么上面就是数据从内存到磁盘到整个过程。
————————以上, 就是本节的全部内容, 下面为本节课条记
https://i-blog.csdnimg.cn/direct/3a6de6f80f9345f0b3edb0d3c70558f7.png
https://i-blog.csdnimg.cn/direct/58652252cbcb4333a6c21d07dfc00c07.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]