当前位置: 首页 > news >正文

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

ch13_2 源码分析

boot/head.s

页表初始化:

  • 目标:初始化分页机制,将线性地址空间映射到物理内存(前 16MB),为保护模式下的内存管理做准备。
  • 核心流程
    • 分配页目录表和页表的物理内存空间(通过 .org 指令指定地址)。
    • 初始化1个页目录 + 4个页表
    • 设置页目录项,指向4个页表(属性:Present+User/RW):
    • 反向填充页表项,把四个页表的页表项:一共4k个页表项填满,对应的是16M的物理内存,4k个物理页面。
    • 通过 CR3 指向页目录表的基地址(物理地址0)和 CR0 寄存器的PG位启用分页机制。
.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 值变化:0xFFF0070xFFE007...0x000007  4k次操作; 初始	pg3+4092	pg3 的第10230xFFF000~0xFFFFFF (4KB); 终止	pg0+0		pg0 的第00x000000~0x000FFFmovl $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,%eaxjge 1bcld						; 清除方向标志(DF=0; 设置PG位启用分页xorl %eax,%eax		movl %eax,%cr3			; cr3指向页目录(物理地址0)movl %cr0,%eaxorl $0x80000000,%eaxmovl %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 返回到用户程序。
/**  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 %ecxpushl %edxpush %dspush %espush %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,%edxmov %dx,%dsmov %dx,%esmov %dx,%fs;CR2 寄存器存储触发页错误的线性地址,压栈后作为 do_no_page 或 do_wp_page 的参数。movl %cr2,%edxpushl %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_pagejmp 2f
1:	call do_wp_page;恢复现场:
;	按逆序恢复之前保存的寄存器和段寄存器。
;	压栈的反顺序:
2:	addl $8,%esp		 ;跳过栈顶的 8 字节数据(相当于清理 2 个 pushl 操作压入的未弹出数据)。pop %fs				;跳过错误码和 CR2 参数(已传递给 C 函数),使栈指针指向 FS 保存的位置。pop %espop %dspopl %edxpopl %ecxpopl %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(内核特权级)

下面给出流程示意图:

                       +-----------------------+|  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

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

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

GNU 内联汇编的基本格式

__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 等)传递操作数整数
    qr 的别名,等价于 r整数
    g使用 任意寄存器、内存或立即数传递操作数(编译器自动选择)整数、内存变量、立即数
    m使用 内存地址传递操作数(操作数在内存中)内存变量(如数组、结构体成员)
    o使用 内存地址传递操作数,且地址是 可优化的(编译器可能选择更优寻址方式)内存变量
    V使用 内存地址传递操作数,且地址是 不可优化的(强制使用给定寻址方式)内存变量
    i操作数是 立即数,且可作为指令的操作码部分(如移位指令的移位次数)立即数(常量表达式)
    F操作数是 浮点常数(如浮点数立即数)浮点数常量
    f使用 浮点寄存器传递操作数浮点数变量
    t使用 第一个寄存器(通常是 EAX)传递操作数整数
    u使用 第二个寄存器(通常是 EDX)传递操作数整数
    w允许使用 字长寄存器(如 AX、BX 等 16 位寄存器)16 位整数
    x通用约束符,等价于 g整数、内存、立即数

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

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 缓存,迫使后续虚拟地址转换时重新查询页目录和页表,确保使用最新的地址映射关系。


相关文章:

  • DeepSeek系列(9):团队协作最佳实践
  • 数字喜好判断之谜
  • 财务管理域——绩效管理系统设计
  • 代理专栏总结
  • 【微知】/proc中如何查看Linux内核是否允许加载内核模块?(/proc/sys/kernel/modules_disabled)
  • SpringBoot UserAgentUtils获取用户浏览器 操作系统设备统计 信息统计 日志入库
  • Arduino+ESP01S烧录
  • 【数据结构】优先级队列
  • 基于共享上下文和自主协作的 RD Agent 生态系统
  • 【计算机网络】TCP的四种拥塞控制算法
  • 驱动开发(1)|鲁班猫rk356x内核编译,及helloworld驱动程序编译
  • 学习设计模式《六》——抽象工厂方法模式
  • Android Gradle插件开发
  • 4月26日随笔
  • 毕业项目-基于深度学习的入侵检测系统
  • asammdf 库的信号处理和数据分析:深入挖掘测量数据
  • CSS 定位学习笔记
  • 使用Django框架表单
  • flutter 引擎初始化
  • 【Castle-X机器人】四、智能机械臂安装与调试
  • 上海市政府党组赴全面从严治党警示教育基地参观学习,推进作风建设走深走实
  • 保利、北京建工联合体45.45亿元竞得北京海淀区宅地,溢价率11.95%
  • 甘肃白银煤矿透水事故仍有3人失联,现场约510立方米煤泥拥堵巷道
  • 信俗与共:清代新疆回疆儒释道庙宇的中华政教
  • 全国林业院校校长论坛举行,聚焦林业教育的创新与突破
  • 尹锡悦宣布退出国民力量党