32 位 Linux 系统内存地址映射(三):从系统内核源码看虚拟地址描述

在前面的文章中,我们深入探讨了 32 位 Linux 系统虚拟地址映射的基本原理,包括二级页表机制、虚拟内存管理以及用户空间与内核空间的地址映射差异。今天,让我们从 Linux 内核源码的角度,理解操作系统底层是如何管理虚拟内存的。​

一、Linux 内核中进程的描述:task_struct​

在 Linux 内核中,进程是通过 ** 进程控制块(PCB,Process Control Block)** 来描述的,其数据结构为 task_struct。task_struct 定义在 Linux 内核源码的 include/linux/sched.h 文件中,它包含了大量与进程相关的信息,如进程状态、进程 ID、进程优先级、打开的文件描述符表、信号处理相关信息等。在地址映射的上下文中,task_struct 与虚拟地址空间紧密相关,它为操作系统提供了管理进程内存的关键信息。

// include/linux/sched.h 中的部分task_struct定义示例
struct task_struct {
    volatile long state;        // 进程状态
    pid_t pid;                  // 进程ID
    struct mm_struct *mm;       // 指向进程虚拟地址空间的描述结构
    // 其他众多成员...
};

进程与线程:统一的 task_struct 管理​

在 Linux 内核中,进程和线程的概念在底层都由 task_struct 来表示。线程本质上是一种特殊的进程,它们共享相同的地址空间、文件描述符等资源。通过 clone() 系统调用的不同参数,可以创建进程或线程。如果 clone() 调用时设置了特定的标志位(如 CLONE_VM 表示共享虚拟内存),则创建的是线程;否则,创建的是普通进程,拥有独立的地址空间。​

二、Linux 内核中进程虚拟地址空间的描述:mm_struct​

task_struct 中的 mm 成员是一个指向 mm_struct 结构体的指针,mm_struct 用于描述进程的整个虚拟地址空间(通常为 4GB,32 位系统)。mm_struct 同样定义在 include/linux/mm.h 文件中,它是内核管理进程虚拟地址空间的核心数据结构。

// include/linux/mm.h 中的部分mm_struct定义示例
struct mm_struct {
    pgd_t *pgd;                   // 指向进程页目录的起始地址
    struct vm_area_struct *mmap;  // 指向虚拟内存区域链表的头节点
    struct vm_area_struct *mmap_cache; // 缓存上一次查找的虚拟内存区域节点
    // 其他众多成员...
};

关键成员变量解析​

  1. pgd_t *pgd:该成员指向当前进程的页目录(Page Directory Table,PDT)的起始地址。如我们在之前的文章中提到,CR3 寄存器存储当前进程的页目录基址,而这个基址正是由 mm_struct 中的 pgd 变量给出。通过 pgd,内核可以快速定位到进程的页目录,进而开始虚拟地址到物理地址的转换过程。​
  1. vm_area_struct *mmap:这是一个指向双向链表首节点的指针,该链表用于描述进程虚拟地址空间中的各个区域。每个链表节点都是一个 vm_area_struct 结构体,用于表示一段连续的、具有相同访问权限和属性的虚拟内存区域。例如,进程的代码段、数据段、堆、栈等都由不同的 vm_area_struct 节点来描述。​
  1. vm_area_struct *mmap_cache:基于局部性原理,进程在访问内存时,往往会频繁访问近期使用过的虚拟内存区域。mmap_cache 用于缓存上一次查找的 vm_area_struct 链表节点地址。当下一次查找虚拟地址所属的内存区域时,内核首先从 mmap_cache 开始搜索,如果命中,则可以快速获取到对应的内存区域信息,提高查找效率。​

三、vm_area_struct:虚拟内存区域的细节​

vm_area_struct 结构体定义了进程虚拟地址空间中的一个区域,它包含了许多重要的成员变量,用于描述该区域的属性和范围。下面是 vm_area_struct 的部分定义(同样在 include/linux/mm.h 中):

// include/linux/mm.h 中的部分vm_area_struct定义示例
struct vm_area_struct {
    struct mm_struct *vm_mm;      // 指向所属的mm_struct
    unsigned long vm_start;       // 虚拟内存区域的起始地址
    unsigned long vm_end;         // 虚拟内存区域的结束地址
    struct vm_area_struct *vm_next; // 指向下一个vm_area_struct节点
    struct vm_area_struct *vm_prev; // 指向前一个vm_area_struct节点
    pgprot_t vm_page_prot;        // 该区域的页面保护属性
    unsigned long vm_flags;       // 该区域的标志位,如可读、可写、可执行等
    // 其他众多成员...
};

成员变量与虚拟内存区域的对应关系​

  1. vm_mm:指向该虚拟内存区域所属的 mm_struct,从而将 vm_area_struct 与进程的整个虚拟地址空间描述关联起来。​
  1. vm_start 和 vm_end:这两个变量定义了虚拟内存区域的范围。例如,一个进程的代码段可能从 0x08048000 开始(vm_start),到 0x08049000 结束(vm_end),表示该代码段占用了这一段连续的虚拟地址空间。​
  1. vm_next 和 vm_prev:用于构建双向链表,将各个 vm_area_struct 节点连接起来。通过这两个指针,内核可以遍历进程的整个虚拟地址空间,查找特定虚拟地址所属的区域。​
  1. vm_flags:包含了该虚拟内存区域的各种属性标志。例如,VM_READ 表示该区域可读,VM_WRITE 表示可写,VM_EXEC 表示可执行。这些标志位决定了进程对该区域的访问权限。如果进程试图访问一个不具备相应权限的虚拟内存区域,将触发段错误(Segmentation Fault)。​

为了更直观地理解 vm_area_struct 中各参数与虚拟内存区域的关系,我们可以通过以下示意图来说明:

 

 注意这里stack部分,由于其从高地址向低地址扩展,start和end指向不同于其他节点

四、地址映射过程:从虚拟地址到物理地址​

当进程执行取指令、读写数据等操作时,需要将虚拟地址转换为物理地址,这个过程由内存管理单元(MMU)完成。在 Linux 内核中,地址映射的流程如下:​

  1. 查找 task_struct:CPU 首先获取当前正在执行的进程的 task_struct。这通常通过硬件寄存器(如进程上下文切换时保存的当前进程信息)来实现。​
  2. 查找 mm_struct:从 task_struct 中获取指向 mm_struct 的指针 mm,从而得到进程虚拟地址空间的描述信息。​
  3. 遍历 vm_area_struct 链表:通过 mm_struct 中的 mmap 指针,开始遍历虚拟内存区域链表。内核在链表中查找目标虚拟地址所属的 vm_area_struct 节点。如果找不到对应的节点,说明该虚拟地址非法,或者进程对该地址没有访问权限,将产生错误(如段错误)。​
  4. 权限检查:一旦找到目标 vm_area_struct 节点,内核检查 vm_flags 中的权限标志,确保进程对该虚拟地址的访问操作(读、写、执行)与节点的权限设置相匹配。如果权限不匹配,同样会产生错误。​
  5. 页表映射:如果地址合法且权限匹配,内核使用 mm_struct 中的 pgd 指针,开始进行页表映射。如前所述,pgd 指向进程的页目录,通过页目录和页表的两级索引,最终找到虚拟地址对应的物理页面,并完成地址转换。​

优化措施:红黑树结构​

由于虚拟地址到物理地址的转换操作非常频繁,单纯使用链表结构遍历 vm_area_struct 可能效率较低。为了提高查找效率,Linux 内核除了链表结构外,还使用了红黑树(Red - Black Tree)结构来管理 vm_area_struct。红黑树是一种自平衡二叉搜索树,其查找、插入和删除操作的时间复杂度均为 O (log n),相比链表的 O (n) 有显著提升。​

在 mm_struct 中,除了 mmap 链表外,还有一个指向红黑树根节点的指针 mm_rb,用于维护红黑树结构。当创建或删除虚拟内存区域时,内核需要同时更新链表和红黑树,以确保两者的一致性。在查找虚拟地址所属的 vm_area_struct 节点时,内核优先尝试从红黑树中查找,如果未找到,再遍历链表。这种双重数据结构的设计,充分发挥了链表和红黑树各自的优势,提高了地址映射的整体效率。​

红黑树代码实现可以参考:c++ 手写STL篇(四) 实现红黑树(RB-Tree)_红黑树rbl-CSDN博客

通过深入研究 Linux 内核源码中与地址映射相关的数据结构和实现细节,我们对虚拟地址映射机制有了更深入的理解。task_struct、mm_struct 和 vm_area_struct 等结构体相互协作,构建了一个复杂而高效的内存管理系统。从内核源码的角度理解这些机制,不仅有助于我们优化程序性能,还能为深入研究操作系统内核提供坚实的基础。在后续的博客中,我们将继续探索更多内存管理的高级技术和内核实现细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值