【Linux内核系列】:进程板块与文件板块的综合
🔥 本文专栏:Linux
🌸作者主页:努力努力再努力wz
💪 今日博客励志语录:
人生中成功只是一时的,失败却是人生的主旋律,但是如何面对失败却把人分成了不同的样子,有的人会被失败击垮,有的人会爬起来继续向前,所以你会选择成为什么样的人呢?
那么在此前的一系列的文章,我主要围绕展开讲解了Linux的进程板块与文件系统板块,学习完了这两个板块的知识点之后,那么这两个板块的内容其实并不是独立隔离开的而是有联系的,那么有了文件系统板块的知识点之后,其实我们进程板块的很多知识点就可以在进一步的完善,那么本篇文章的核心就是关联其我们进程板块与我们的文件板块,那么废话不多说,就进入正文的学习
★★★ 本文前置知识:
文件系统收尾
文件系统
缓冲区
进程的替换
进程地址空间
引入:内存管理
想必进程的创建想必大家一定非常熟悉,那么在进程创建之前,我们需要将该进程对应的可执行文件给加载到内存中,那么我们知道内存和外部设备磁盘一样,是一个物理结构,那么必然也要经过操作系统的管理,那么操作系统管理一个事物的方式是首先得为该事物建立一个逻辑映射来描述该事物,比如磁盘,那么操作系统便用一个一维的线性数组来描述磁盘这个物理结构,那么其中该一维的线性数组的每一个元素便是扇区,同理操作系统要描述内存,必然也得为内存建立一个逻辑结构
那么我们知道CPU在运行进程的代码的时候,那么会从内存中获取该进程的相关数据,而CPU要获取该进程的相关数据,必然得告诉内存目标数据在内存中的物理位置,从而内存能够定位到该数据然后交给CPU,而CPU与内存之间通过地址线相连,那么以32为机器为例,那么CPU与内存的地址线就是32根,那么每一根地址线的高低电频信号用来表示二进制的0和1,那么内存就能够获取到这32根地址线组合得到一个二进制序列,那么该二进制序列便是地址,那么32根地址线总共就能够表示出2的32次方个不同的地址,也就是从0000 0000到FFFF FFFF连续的大小为4GB的地址空间,那么该地址空间就可以用一个一维的数组来表示,其中该数组的每一个元素就是一个逻辑地址,那么每一个进程都有各自的独立的一份虚拟地址空间,那么该虚拟地址空间就是内存的一个逻辑映射
那么这些地址是逻辑上的地址,就如同我们磁盘上一维线性数组中的逻辑块的LAB地址一样,那么他们就需要经过转化映射得到实际的物理内存地址,所以便有了页表这个数据结构,那么其中页表就记录的是虚拟地址到内存的物理地址的一个映射,那么CPU拿着虚拟地址经过其MMU也就是CPU的内存管理单元,然后MMU通过页表就能实现虚拟地址到物理地址的一个转化,从而交给内存定位到目标数据
而对于物理内存来说,我们操作系统将连续的物理内存空间其中按照4KB的数据为一个集合来划分,其中这每一个集合就是一个页框,那么为了管理整个物理内存就转换为了管理这划分的一个一个的页框,那么管理的方式就是我们最为熟悉的先描述,再组织了,也就是说操作系统会为每一个页框定义一个page结构体,那么该结构体存储了该页框的各种属性,其中最关键的便是一个整形的flag作为标志位,那么该整形flag变量的每一个二进制位都有特定的含义,那么其中就包括了该页框是否被使用等,那么其中page结构体还包括一个变量count也就是引用计数,记录该页框被多少进程所共享,那么在进程的视角下,物理内存是4GB,而页框的大小是4KB,那么也就是说总共大概会有1048576个页框
struct page {unsigned long flags; // 标志位(如PG_locked、PG_dirty)atomic_t _count; // 引用计数(共享次数)atomic_t _mapcount; // 页表映射计数struct address_space *mapping; // 关联的地址空间(文件映射时使用)pgoff_t index; // 页框在文件或内存中的偏移struct list_head lru; // LRU链表(用于页面置换算法)// 其他字段(如内核slab分配器相关)};
那么为了管理这么多的页框,那么操作系统的内核中会维护一个全局的变量mem_map本质上也就是一个struct page数组,那么该数组的每一个元素就对应一个page实例,那么我们可以通过内存的逻辑地址也就是地址空间的虚拟地址来根据其页表来映射转换得到内存的物理地址,而虚拟地址的二进制序列是由页目录索引、页表索引、页内偏移所构成。
以虚拟地址 0x08048000
为例: 0x08048000
→ 分解为:页目录索引(0x20)、页表索引(0x48)、页内偏移(0x000)。
然后MMU通过页目录索引以及页表索引在页表中逐级查找得到页框号PFN再结合页内偏移最后在将两者转化得到物理内存地址
而物理地址的前12位便是该物理地址所对应的页框的page结构体在mem_map的一个偏移量也就是PFN,那么我们就可以根据偏移量来定位到其对应的page结构体,这样就完成了一个虚拟地址到物理地址再到对应的page结构体的一个映射
虚拟地址空间 (进程视角)
│
└── 页表 (Page Table)
│
└── 物理地址 (硬件层)
│
└── 物理页框 (4KB)
│
└── struct page (元数据)
│
└── mem_map[PFN] (全局管理)
而我们发现如果是对于EXT4文件系统来说,那么该文件系统下的逻辑块的大小是4KB,而页框的大小也为4kb,那么必然我们的内存与磁盘的逻辑块就能够达成一个1:1的映射关系,那么至于映射规则以及过程是什么的,那么又是说来话长,本文肯定是讲不完的,所以便不再阐述了,读者感兴趣可以下去自己了解
完善进程的创建的全过程
那么我们现在我们要结合我们之前所学的进程板块与文件板块来完善进程创建的一个完整的过程的话,那么这个过程的起点就应该从系统调用说起,
我们知道了我们在Linux上创建的各种进程,本质上都是我们命令行解释器也就是shell外壳程序的子进程,那么shell外壳程序会调用fork系统调用接口来创建一个子进程,那么此时就会复制拷贝一份父进程的task_struct结构体然后修改其中部分属性比如PID以及PPID等得到子进程自己独立的一份task_struct结构体,至于数据层面上,那么子进程是共享父进程的物理内存页面的,但是为了进程的独立性,操作系统会采取了写时拷贝机制,也就是一旦进程对共享的数据进行写入操作的时候,那么便会触发写时拷贝机制,那么此时操作系统会为写入的数据在内存中为其开辟一份副本从而做到父子进程的数据的独立,那么fork调用结束后,下一个环节便是进程的替换,那么此时会调用exec族函数,那么它会获取到替换的目标进程的对应的可执行文件的文件名以及路径,那么没错,现在这部分过程就开始与文件板块串联起来了,获取文件名以及路径是目的是要从该文件所处的目录文件中的目录项中获取对应的文件名和其inode编号的映射关系从而获取该可执行文件的inode编号,那么有了该文件的inode编号之后,那么便可以转化为磁盘中的物理内存地址从而进行定位,而inode块中有其关联的保存文件内容的数据块的索引,那么意味着此时我们便能同时定位到该可执行文件的inode块以及对应的数据块,从而将其加载到内存中,然后接着又切换到我们的进程板块
一旦该进程对应的可执行文件加载到内存中之后,那么那么进程的替换不会为替换的新的进程创建一个task_struct结构体,而是修改该进程的页表也就是映射关系,从而将该子进程的上下文替换为目标进程的上下文,那么子进程运行结束退出后接着父进程会利用waitpid接口获取子进程的退出情况也就是退出码,那么这就是结合了文件板块之后,我们完善我们对于进程的创建的全过程的一个理解
完善用户缓冲区写入
那么我们有了文件系统以及进程的概念之后,我们便能够理解以及完善用户缓冲区写入的一个全过程
那么我们知道当我们调用c语言提供的fwrite库函数向目标文件做写入时,那么写入的数据不会直接到内核中而是先保存在c语言提供的一个用户层面上的缓冲区,然后再根据特定的刷新策略比如行缓冲或者全缓冲调用write接口刷新到内核中去
而我们向目标文件写入的数据前提肯定是该目标文件被打开了,那么既然该文件被打开,那么必然要调用fopen函数或者open接口,那么open系统接口会获取该打开的目标文件的文件名以及所处路径,然后会根据该路径解析得到该目标文件所处的目录,然后扫描其目录项得到映射关系从而获取该文件的inode编号从而定位到磁盘中的相应位置将该文件的inode块以及其关联的数据块加载到内存当中,并且为其创建一个内核层面上的file结构体,以及定义一个inode结构体保存inode块的数据,那么file结构体内部就有inode结构体的间接引用,而进程内部会有一个指针数组记录其打开的文件,那么每一个元素指向一个该进程打开的文件的file结构体,那么创建完file结构体之后,就会从数组开始线性扫描该指针数组寻找空余位置然后指向该file结构体并返回该数组位置的下标
那么此时有了数组下标之后,我们便能获取该文件的file结构体,那么其中file结构体中有一个address space字段,其中保存了一个指向基数树的数据结构的指针,那么我们该打开的文件一定保存在特定位置的内存的页框中,所以得通过基数树来寻找定位到其page结构体然后写入到页缓存中去,最后再由操作系统刷新到磁盘中去,这也就是为什么要有用户缓冲区的意义,因为每次写入页缓冲,那么其在基数树的定位都需要时间开销
结语
那么这就是本篇文章的全部内容,那么大家在学习Linux的各个板块的时候,比如进程与文件系统,它们之间不是独立没有联系的,而是可以结合这两个板块的知识,去完善我们很多过程,加深这两个板块之间的理解,这就是本文想要传达的,那么我的下一篇文章是动静态库,那么我会持续更新,希望你能多多关注,如果本文有帮组到你的话,还请多多三连加关注哦,你的支持就是我创作的最大动力!