Linux0.11内存管理:相关代码

ch13_2 源码分析

boot/head.s

页表初始化:

  • 目标:初始化分页机制,将线性地址空间映射到物理内存(前 16MB),为保护模式下的内存管理做准备。
  • 核心流程
    • 分配页目录表和页表的物理内存空间(通过 .org 指令指定地址)。
    • 初始化1个页目录 + 4个页表
    • 设置页目录项,指向4个页表(属性:Present+User/RW):
    • 反向填充页表项,把四个页表的页表项:一共4k个页表项填满,对应的是16M的物理内存,4k个物理页面。
    • 通过 CR3 指向页目录表的基地址(物理地址0)和 CR0 寄存器的PG位启用分页机制。
c 复制代码
.org 0x1000				; 告诉汇编器,将接下来的代码或数据从内存地址 0x1000 开始放置。
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
.align 2
setup_paging:

	; 初始化1个页目录 + 4个页表
	movl $1024*5,%ecx		; 初始化5页(1页目录+4页表)
	xorl %eax,%eax			; 异或自身,置零eax(填充值)
	xorl %edi,%edi			; 置零edi(页目录起始地址)
	cld						; 清除方向标志(DF=0),确保地址递增,edi += 4
	;rep stosl				; 循环将 EAX 的值写入 EDI 指向的内存。
							; ECX = 循环填充次数 每次写入edi += 4(因 DF=0)。
	
	; 设置页目录项,指向4个页表(属性:Present+User/RW)
	movl $pg0+7,pg_dir		; 页表项(0x1007)包括高二十位的页框地址(0x1去掉后12位)+ 12位属性(7)
	movl $pg1+7,pg_dir+4	; 0x7表示Present(存在位)、R/W(可写)、U/S(用户可访问)。
	movl $pg2+7,pg_dir+8	
	movl $pg3+7,pg_dir+12	

	; 反向填充页表项,映射16MB物理内存
	; 仅需设置 pg3+4092 作为初始地址,结合循环即可覆盖 pg3 → pg2 → pg1 → pg0 的全部 4k 个页表项。
	; eax 值变化:0xFFF007 → 0xFFE007 → ... → 0x000007  4k次操作
	; 初始	pg3+4092	pg3 的第1023项	0xFFF000~0xFFFFFF (4KB)
	; 终止	pg0+0		pg0 的第0项		0x000000~0x000FFF
	movl $pg3+4092,%edi		; edi = 0x4FFF(pg3 的最后一个4字节项)  
	movl $0xfff007,%eax		; eax = 0xFFF007(物理地址的高20位 + 属性0x7) 
	std						;(DF=1),edi -= 4
1:	stosl					; 写入4k个页表项:将eax值写入[edi],同时 edi-= 4 
	subl $0x1000,%eax
	jge 1b
	cld						; 清除方向标志(DF=0)

	; 设置PG位启用分页
	xorl %eax,%eax		
	movl %eax,%cr3			; cr3指向页目录(物理地址0)
	movl %cr0,%eax
	orl $0x80000000,%eax
	movl %eax,%cr0			; 设置CR0的PG位(分页使能)
	ret						; 返回并刷新预取队列
page.s

核心功能总结

  1. 保存用户态上下文:保护寄存器,确保处理程序不破坏用户进程的运行状态。
  2. 切换到内核特权级:通过设置段寄存器访问内核数据结构。
  3. 提取关键信息
    • CR2 寄存器:获取触发页错误的线性地址。
    • 错误码:分析错误类型(缺页或写保护)。
  4. 分支处理
    • 缺页(P=0) :调用 do_no_page 分配或加载页面。
    • 写保护(P=1) :调用 do_wp_page 处理写时复制(COW)。
  5. 恢复现场并返回 :清理栈空间,恢复寄存器,通过 iret 返回到用户程序。
c 复制代码
/*
 *  linux/mm/page.s
 *
 *  (C) 1991  Linus Torvalds
 */
/*
 * page.s contains the low-level page-exception code.
 * the real work is done in mm.c
 */
.globl page_fault
; 处理器触发页错误(如访问未映射或受保护的地址)时跳转到 page_fault。
page_fault:
	
	; 在页错误发生时:
	;	1. 栈顶是错误码(由 CPU 自动压入)
	;	2. 交换 EAX 和栈顶的值后,EAX = 错误码,栈顶存储原 EAX 的值
	xchgl %eax,(%esp)

	;寄存器保护:
	;	保存EAX、ECX、EDX、DS、ES、FS 以确保处理程序不会破坏用户进程的上下文。
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	push %fs

	;内核模式设置:
	;	将 DS/ES/FS 设置为 0x10(内核数据段),确保后续内存操作在内核特权级进行。
	;	该指令将立即数 0x10(二进制 00000000 00010000)加载到 %edx 寄存器:
	;		Index: 00000000 0010(高 13 位,即 0x10 >> 3 = 2,对应 GDT 的第 2 项)。
	;		TI: 0(使用 GDT)。
	;		RPL: 00(内核特权级)
	movl $0x10,%edx
	mov %dx,%ds
	mov %dx,%es
	mov %dx,%fs

	;CR2 寄存器存储触发页错误的线性地址,压栈后作为 do_no_page 或 do_wp_page 的参数。
	movl %cr2,%edx
	pushl %edx

	;错误码最低位(P 位)决定异常类型:
	;P=0 → 缺页异常,调用 do_no_page() 分配或加载页面。
	;P=1 → 写保护异常,调用 do_wp_page() 处理写时复制(COW)。
	pushl %eax			
	testl $1,%eax			;检查 %eax 的最低位(等价于 eax & 1)
	jne 1f					;如果 %eax 的最低位=1(ZF=0 零标志位为非),跳转到标签 1:;否则继续执行
	call do_no_page
	jmp 2f
1:	call do_wp_page

;恢复现场:
;	按逆序恢复之前保存的寄存器和段寄存器。
;	压栈的反顺序:
2:	addl $8,%esp		 ;跳过栈顶的 8 字节数据(相当于清理 2 个 pushl 操作压入的未弹出数据)。
	pop %fs				;跳过错误码和 CR2 参数(已传递给 C 函数),使栈指针指向 FS 保存的位置。
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	
	;中断返回:iret 恢复 CS、EIP、EFLAGS,返回到触发页错误的指令继续执行。
	iret

为什么设置 DS/ES/FS=0x10(内核数据段)?

  • 确保后续内存操作在内核特权级进行
    • 虽然 CPU 在异常处理中自动切换到内核态(CPL=0) ,但段寄存器(如 DS)的段选择子可能仍指向用户态描述符 (例如用户数据段是 0x17,TI=0、Index=3、RPL=3)。
  • DS/ES/FS 设置为 0x10(内核数据段),该指令将立即数 0x10(二进制 00000000 00010000)加载到 %edx 寄存器:
    • Index: 00000000 0010(高 13 位,即 0x10 >> 3 = 2,对应 GDT 的第 2 项)。
    • TI: 0(使用 GDT)。
    • RPL: 00(内核特权级)

下面给出流程示意图:

c 复制代码
                       +-----------------------+
                       |  CPU 触发页错误         |
                       | 硬件自动执行以下操作:    |
                       | 1. 压入错误码到栈       |
                       | 2. 跳转到 page_fault    |
                       +-----------+------------+
                                   v
                      +------------+-------------+
                      | page_fault 处理程序开始     |
                      +------------+-------------+
                                   |
                        +----------+------------+
                        | 交换 eax 和栈顶值       |
                        | (xchgl %eax, (%esp))  |
                        +----------+------------+
                                   |
                        +----------+------------+    保存用户进程的寄存器上下文
                        | 压入 ecx, edx, ds, es, fs |
                        +----------+------------+
                                   |
                        +----------+------------+
                        | 设置内核数据段 (DS/ES/FS=0x10) |
                        +----------+------------+    确保内核内存访问安全
                                   |
                        +----------+------------+
                        | 读取 CR2 → edx,压入栈   |
                        +----------+------------+
                                   |
                        +----------+------------+    压入错误码 (eax) 到栈
                        | 测试错误码最低位(P位)    | 
                        +----------+------------+
                                   |
         +-------------------------+-------------------------+
         | P=0(缺页异常)           | P=1(写保护异常)        |
         v                         v
+------------------+       +------------------+
| 调用 do_no_page() |       | 调用 do_wp_page() |
+------------------+       +------------------+
         |                         |
         +-------------------------+
                                   |
                        +----------v------------+
                        | 清理栈空间(addl $8, %esp)|
                        +----------+------------+    跳过错误码和 CR2
                                   |
                        +----------+------------+
                        | 逆序恢复寄存器(fs, es, ds, edx...)|
                        +----------+------------+
                                   |
                        +----------v------------+
                        | iret 返回到用户态       |    恢复用户程序执行
                        +-----------------------+
memory.c

这个是主要文件:分成一段一段的去看


invalidate()宏:刷新TLB

c 复制代码
// 刷新快表TLB
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))

首先要看懂GNU 内联汇编(GNU Inline Assembly) 的语法,在C语言中嵌入汇编指令。

GNU 内联汇编的基本格式

c 复制代码
__asm__ [volatile] (
    "汇编指令模板"        // 必选:汇编指令字符串
    : 输出操作数约束      // 可选:指定输出操作数及其约束
    : 输入操作数约束      // 可选:指定输入操作数及其约束
    : 破坏的寄存器列表    // 可选:声明被指令修改的寄存器
);
  • __asm__ 是关键字,也可写作 asm,表示开始内联汇编。

  • volatile 是可选关键字,用于禁止编译器优化该汇编指令(内核中常用)。

  • 四个部分用 : 分隔,即使某部分无内容,对应的 : 也不能省略。

  • 基本操作数约束(单个字符)

    约束符 含义 适用操作数类型
    a 使用 CPU 的 EAX/AX/AL 寄存器传递操作数 整数(int、long 等)
    b 使用 EBX/BX/BL 寄存器传递操作数 整数
    c 使用 ECX/CX/CL 寄存器传递操作数 整数
    d 使用 EDX/DX/DL 寄存器传递操作数 整数
    S 使用 ESI 寄存器传递操作数 整数
    D 使用 EDI 寄存器传递操作数 整数
    r 使用 任意通用寄存器(EAX/EBX/ECX/EDX/ESI/EDI 等)传递操作数 整数
    q r 的别名,等价于 r 整数
    g 使用 任意寄存器、内存或立即数传递操作数(编译器自动选择) 整数、内存变量、立即数
    m 使用 内存地址传递操作数(操作数在内存中) 内存变量(如数组、结构体成员)
    o 使用 内存地址 传递操作数,且地址是 可优化的(编译器可能选择更优寻址方式) 内存变量
    V 使用 内存地址 传递操作数,且地址是 不可优化的(强制使用给定寻址方式) 内存变量
    i 操作数是 立即数,且可作为指令的操作码部分(如移位指令的移位次数) 立即数(常量表达式)
    F 操作数是 浮点常数(如浮点数立即数) 浮点数常量
    f 使用 浮点寄存器传递操作数 浮点数变量
    t 使用 第一个寄存器(通常是 EAX)传递操作数 整数
    u 使用 第二个寄存器(通常是 EDX)传递操作数 整数
    w 允许使用 字长寄存器(如 AX、BX 等 16 位寄存器) 16 位整数
    x 通用约束符,等价于 g 整数、内存、立即数

那么这个代码可以看成,把CR3寄存器设置成0

c 复制代码
mov eax, 0       ; 将 0 存入 eax
mov cr3, eax     ; 将 eax 的值写入 cr3

为什么设置 CR3 为 0?

为了刷新TLB:只要是写入CR3,不i管值变不变都会刷新。

  • CR3 寄存器在head.s里面就被设置成0了,始终指向页目录基址 0,再置零不改变他的值。
  • 此处调用 invalidate() 的目的并非修改CR3的值,而是通过 写入 CR3 寄存器(即使值不变)触发 CPU 的 TLB 刷新机制

x86 架构规定:当向 CR3 写入数据时,无论值是否变化,CPU 都会 清空 TLB 缓存,迫使后续虚拟地址转换时重新查询页目录和页表,确保使用最新的地址映射关系。


相关推荐
0509151 分钟前
实验四 进程调度实验
linux·数据结构·算法·课程设计
程序猿(雷霆之王)10 分钟前
Linux——动静态库
linux·运维·服务器
JhonKI10 分钟前
【Linux网络】打造初级网络计算器 - 从协议设计到服务实现
linux·运维·网络
Herbig30 分钟前
服务器上安装node
linux·node.js
minji...1 小时前
C语言 函数递归
c语言·开发语言·算法
敖云岚1 小时前
【Linux】Centos7 安装 Docker 详细教程
linux·运维·服务器
JhonKI1 小时前
【Linux网络】构建HTTP响应与请求处理系统 - HttpResponse从理解到实现
linux·网络·http
CHTXRT1 小时前
2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup
c语言·网络·web安全·网络安全·蓝桥杯·wireshark
FJW0208142 小时前
【Linux】web服务器的部署和优化
linux·运维·服务器·rhce
Linux运维老纪2 小时前
Python文件操作及数据库交互(Python File Manipulation and Database Interaction)
linux·服务器·数据库·python·云计算·运维开发