Tag: Page table

Linux kernel学习-进程地址空间

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-process-address-space/ 看完 Linux kernel block I/O 层之后来到进程地址空间管理部分,本文中的很多知识和之前的 [进程基本]、[进程调度]、[内存管理] 等章节的知识相关。 1、基础知识: Linux kernel 给每个进程提供的进程地址空间一般是 32 位或 64 位(硬件相关)的平坦地址空间,但进程是没有权限访问这段地址空间中的所有地址的,能访问的一般是很多的内存地址区间。这种内存地址区间被称为内存区域,进程可以动态添加和删除内存区域到它的地址空间中。内存区域可以有不同的权限,相关进程必须遵守这些权限,例如可读、可写、可执行等。如果进程访问的地址不在一个有效的内存区域中,或者访问时的权限不正确,kernel 将会杀掉进程并给出常见的 "Segmentation Fault" 段错误日志。 内存区域通常包括: 可执行文件的代码段,称为 text 段; 可执行文件的已初始化全局变量段,称为 data 段; 未初始化全局变量段(通常以 0 page 填充),称为 bss 段; 进程的用户空间栈(通常以 0 page 填充); 每个共享库文件的额外 text、data、bss 段,也被装入进程的地址空间; 内存映射文件; 共享内存区域; 匿名内存映射(新版本的 malloc 函数就除了 brk 之外也通过 mmap 实现); 应用程序中的堆 2、内存描述符: kernel 使用 mm_struct 内存描述符结构来表示进程的地址空间信息,它定义在 <linux/mm_types.h> 头文件中,这也是一个非常大的结构。 结构的注释中已经包含比较多的注解了哦。mmap 为地址空间的内存区域(用 vm_area_struct 结构来表示啦,也是上面的代码中)链表,mm_rb 则将其以红黑树的形式进行存储,链表形式方便遍历,红黑树形式方便查找。mm_users 为以原子变量形式保护的使用此地址空间的进程数量值(例如:如果有 4 个线程共享此地址空间,则 mm_users 值为 4),mm_count 为引用计数(所有 mm_users 等于一个引用计数),当 mm_count 值为 0 时表示没有再被使用,可以被释放。total_vm 成员表示所有内存区域的数量。 所有的 mm_struct 结构以链表的形式存在 mm_struct 的 mmlist 成员中,该链表的第一个成员就是 init 进程的 mm_struct :init_mm,该链表被 mmlist_lock 锁保护。 进程的内存描述符是在 task_struct 的 mm 成员中的。fork() 进行创建进程时调用 copy_mm 函数将父进程的内存描述符拷贝给子进程,调用 clone() 函数时如果指定 CLONE_VM 参数将使父进程和子进程地址空间共享(实际上将 mm_users 计数加 1),这种子进程就被称为线程。mm_struct 结构一般是通过 alloc_mm 宏从名为 mm_cachep 的 Slab cache 中分配。 进程退出时调用 exit_mm 函数,该函数再调用 mmput() 函数,此函数中减小地址空间的 mm_users 计数,如果 mm_users 变为 0,调用 mmdrop() 函数减小 mm_count 计数,如果 mm_count 变为 0,则最终调用 free_mm() 宏来释放内存描述符(回归到 Slab cache 中)。 另外需要说明的是 kernel 线程是没有地址空间,也就没有对应的 mm_struct(值为 NULL),kernel 线程使用之前运行的进程的内存描述符,有关 kernel 线程请参考之前的 [进程基本] 文章。 3、VMA 概念: vm_area_struct 结构即内存区域常被称为虚拟内存区域(简写为 VMA),表示的是在一个地址空间中的一个连续内存地址区间,每个内存区域是一个惟一的对象。vm_area_struct...

Linux kernel学习-内存寻址

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-memory-addressing/ 近日在看 Understanding the Linux kernel(慢慢啃E文原版,以下简称 ULK),这本书虽然已经是第三版了,但它基于的 Linux kernel 版本却不是很新,现在 Linux kernel 都已经出到 3.4 版本了,这本书还是基于 2.6.11 的 kernel,不得不说 Linux kernel 的更迭速度太快了。 下面准备以我正在用的 2.6.34 版本的 kernel 为基础进行学习,这本书中不对应的地方我会尽量找到新 kernel 中的实现,并尽量自己做个了解,日后的相同日志如无意外也基于 2.6.34 版本 Linux kernel。 首先已完成第一章:Introduction(这一章没有 Linux kernel 代码),来到第二章 Memory Addressing,开始是介绍逻辑地址、线性地址、物理地址的对应关系,虽然之前用汇编写过 Linux 的 bootloader,用到过实模式和保护模式,但对 GDT、LDT 的概念并没有深入了解过。这一章开篇就介绍了 Intel 80X86 硬件上内存分段的实现,包括段选择子,段寄存器,段描述符。 1、段式内存管理: 每个内存段由 8 个字节的段描述符来表示段的特征。段描述符被存储在 GDT 或者 LDT 中。内存中 GDT 的地址和大小包含在 gdtr 控制寄存器中,LDT 的地址和大小包含在 ldtr 控制寄存器中。段寄存器的高 13 位为段描述符在 GDT 或者 LDT 中的索引,GDT 或者 LDT 结构中包含基地址、段长度等信息。通过检查指令地址和段长度并确定没有越界以及权限是否正确之后,由于 线性地址 = 段基指 + 偏移地址,GDT 或者 LDT 中的基地址加上指令中的偏移量就可以得到需要的线性地址。 备注:由于每个进程都可以有 LDT,而 GDT 只有一个,为满足需求 Intel 的做法是将 LDT 嵌套在 GDT 表中。 Linux kernel 中的内存分段: Linux中所有进程使用相同的段寄存器值,因此它们的线性地址集也是相同的,不管在用户模式还是内核模式,都可以使用相同的逻辑地址,32位 kernel下为 4G 的地址空间。 ULK 中介绍的 user code、user data、kernel code、kernel data 这四个段对应的段选择子的宏为:__USER_CS、__USER_DS、__KERNEL_CS、__KERNEL_DS,2.6.11 中这4个宏定义在 include/asm-i386/segment.h 头文件中,2.6.34 中已经挪到 arch/x86/include/asm/segment.h 里,因为 2.6.34 中 i386 和 x86_64 的代码已经尽可能的合并到 x86 目录中,而不像老版本的代码那样弄成两个目录。定义如下: 下面是 Linux kernel GDT 的实现: 由于 kernel 中每个内核需要有一个 GDT,因此就有一个 GDT table,ULK 中说的是存在 cpu_gdt_table 中,GDT 的地址和大小存在 cpu_gdt_descr 中,2.6.11 kernel 里都是放在 arch/i386/kernel/head.S,使用的地方: 到了 2.6.34 中已经改为: 可以看到 2.6.34 中去掉了原来的 cpu_gdt_table 变量(详见 kernel commit bf50467204b435421d8de33ad080fa46c6f3d50b),新增了一个 gdt_page 结构存放...