一个进程一个PCB,进程就是在磁盘的源代码文件被加载(拷贝)到内存中时,操作系统为了管理此文件的一个结构体,双链表管理起来所有的PCB。先描述再组织。操作系统是开机自启的在内存的一个软件。
进程PCB的内容有:
-
进程状态:记录进程当前的状态,比如运行、就绪、阻塞等。
-
程序计数器(Program Counter, PC):指向进程下一条要执行的指令的地址。
-
寄存器:包括通用寄存器、指令指针、堆栈指针等,保存了进程的硬件上下文信息。
-
进程标识符(Process ID):唯一标识系统中每个进程的数字或名称。
-
进程优先级:用于调度进程的优先级信息。
-
进程调度信息:包括进程在CPU上运行的时间、已经等待的时间等调度相关信息。
-
进程所拥有的资源:比如打开的文件、分配的内存、I/O设备等信息。
-
进程地址空间的描述符:指向进程地址空间的地址
-
文件描述符表的描述符:指向文件描述符的地址
等等变量。
进程地址空间的大小和CPU的位宽和操作系统等有关我们暂且以4G的地址空间为例:
进程地址空间分为内核空间和用户空间。进程地址空间是怎么划分内核空间和用户空间的呢,在用户空间或者内核空间内又是怎么划分具体区域的呢?
struct mm_struct 是描述进程地址空间的数据结构,它包含了多种成员变量以管理和描述进程的内存布局和虚拟地址空间。下面是 struct mm_struct 结构体可能包含的一些主要成员变量:
-
*pgd_t pgd:指向页全局目录(Page Global Directory)的指针,用于建立页表,实现虚拟地址到物理地址的映射。
-
atomic_t mm_users:用于计算当前共享该内存描述符的进程数目,采用原子操作来确保并发访问的正确性。
-
atomic_t mm_count:表示对该内存描述符的引用计数,用于跟踪该内存描述符的使用情况。
-
unsigned long start_code, end_code:表示可执行代码段的起始地址和结束地址。
-
unsigned long start_data, end_data:表示初始化数据段(data segment)的起始地址和结束地址。
-
unsigned long start_brk, brk, brkval:表示堆(heap)的起始地址、当前堆顶地址以及堆最大地址。
-
unsigned long start_stack:表示用户栈(user stack)的起始地址。
-
*struct vm_area_struct mmap:指向虚拟内存区域链表(Virtual Memory Area)的头部,用于描述进程的虚拟内存布局。
还是那句话,不同的操作系统版本有些许差别。但大差不差,由此看出进程地址空间的划分是由一大堆的unsigned long变量划分的。说到这里就一起把struct vm_area_struct* mmap说了,系统将用户空间分为了图上这几个区域,如果用户觉得不太合理或者说想多加几个区域,就可以修改unsigned long的值和struct vm_area_struct* mmap指针存储的地址内的变量值(具体变量在图中已经标记出来了)。
在进程地址空间的内核空间中,通常存储以下内容:
-
操作系统内核代码:包括操作系统的核心功能代码,如调度器、内存管理、文件系统等模块的代码。
-
内核数据结构:存储操作系统内核运行时所需的各种数据结构,如进程控制块(PCB),文件表(内核用来管理进程打开的文件的数据结构,其中记录了进程打开文件的相关信息,如文件位置指针)等。
-
系统调用接口:提供用户空间进程与内核空间进行通信的接口,允许用户程序请求操作系统提供的服务。
还有一些其他的内容,不同的操作系统内核版本会有些许差异。
在进程地址空间的用户空间中,内容也非常丰富:
环境变量,命令行参数,栈区,共享区(允许多个进程访问相同的物理内存位置,从而实现进程间的通信),堆区,数据区,代码区。
文件描述符(struct files_struct):
文件描述符表其实就是封装的指针数组,数组就有下标,0,1,2是固定的,分别对应标准输入,标准输出,标准错误。当进程再打开其他文件的时候就会按照次序分配下标,操作系统为其分配对应的struct file,struct file的前两个变量都是容易理解的,最后一个是简化版的,其实底层是字典树,这里以后再补充。暂且理解为指针指向也是够用的。
接下来说页表:
第一层:
虚拟地址映射物理地址。MMU(内存管理单元)是计算机系统中的一个重要组件,负责处理虚拟地址到物理地址的转换,以及对内存访问权限的控制。MMU通常集成在CPU中,是操作系统和硬件之间的桥梁,实现了虚拟内存管理的关键功能。
在页表中,每个页表项(Page Table Entry,PTE)通常包含了一些标志位或字段来表示对应页面的权限信息。这些权限信息用于控制程序对该页的访问方式,例如读、写、执行等权限。页表中常见的权限信息包括:
-
读取权限(Read):表示是否允许程序从该页面中读取数据。如果该权限位被设置为允许读取,则程序可以从该页面中读取数据;否则,读取操作将触发异常。
-
写入权限(Write):表示是否允许程序向该页面中写入数据。如果该权限位被设置为允许写入,则程序可以向该页面中写入数据;否则,写入操作将触发异常。
-
执行权限(Execute):表示是否允许程序在该页面上执行代码。如果该权限位被设置为允许执行,程序可以在该页面上执行指令;否则,执行操作将触发异常。
-
访问权限(Accessed):表示该页面是否被访问过。当程序访问该页面时,该权限位会被置为1,用于操作系统统计页面的访问情况。
-
修改权限(Dirty):表示该页面是否被修改过。当程序向该页面写入数据时,该权限位会被置为1,表示页面已经被修改过。
-
用户态/内核态权限(User/Supervisor):用于区分页面对用户态和内核态程序的访问权限。如果该权限位允许用户态程序访问,则用户态程序可以访问该页面;否则,只有内核态程序可以访问。
第二层:
物理内存的前置知识:
通常情况下,物理内存会以4KB(或者4096字节)为一个基本单位进行划分和管理。这个基本单位也称作“页框”(Page),它是操作系统进行内存管理的最小粒度。将物理内存划分成固定大小的页面有助于简化内存管理和提高内存利用效率。
每个页面的大小一般为4KB的原因:
-
内存碎片化的折中:较小的页面大小可以减少内存碎片化的程度。如果页面过大,会导致内部碎片增加;而如果页面过小,会导致外部碎片增加,4KB 的页面大小在内存管理的折中方面表现较好。
-
适应磁盘:磁盘的划分也是4KB为基本单位划分的,较小的页面大小可以更好地支持页面置换和页面映射,能够更灵活地管理内存。
页表详讲:
虚拟地址是如何转换到物理地址的呢?以32位宽虚拟地址为例子,32位宽虚拟地址就是32个比特位,32个比特位就是4字节,32个比特位被分为10+10+12三块,三块页表也不是一整块,假设说页表是一整块,虚拟地址4字节,物理地址4字节,权限2字节,一个地址映射就要10字节,32位的进程地址空间4G,地址从全零到全一,共有2^32个地址,2^32个虚拟地址就要 10*2^32个字节,这才是一个进程的页表,进程的独立性,每个进程一个页表,放进内存是放不开的。
所以页表其实是被拆分为了2级,一级页表和二级页表,一级页表也成为页目录表,二级页表也成为页表项。
用前10个二进制数转化为的十进制数去一级页表中查找,按下标,一级页表有1024个存储页目录项地址的空间,相当于一个从0到1023的数组。
用第二组的10个二进制数转化为十进制数,和第一级页表一样对应下标,二级页表存储的是页框的起始地址(上面讲了前置知识)。
最后12个二进制是搜索某个具体地址的偏移量,第二组是定位了页框,这一组就是定位在页框的哪个位置。二级页表内的地址+偏移量=物理内存。至此,映射完毕。
以后还会在复习时慢慢补充内容。