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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/78653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】set类型

目录 1、介绍2、底层实现【1】整数集合【2】哈希表 3、常用指令 1、介绍 Redis的set集合类型是一种无序且元素唯一的数据结构,支持高效的成员判断、集合运算和随机访问。 2、底层实现 【1】整数集合 适用场景 当集合中所有的元素都是整数,且元素数量…

web技术与nginx网站环境部署

一:web基础 1.域名和DNS 1.1域名的概念 网络是基于TCP/IP协议进行通信和连接的,每一台主机都有一个唯一的标识(固定的IP地址),用以区别在网络上成千上万个用户和计算机。网络在区分所有与之相连的网络和主机时,均采用一种唯一、通用的地址…

LeetCode【剑指offer】系列(动态规划篇)

剑指offer10-I.斐波那契数列 题目链接 题目:斐波那契数(通常用F(n)表示)形成的序列称为斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n) F(…

JVM 内存分配策略

引言 在 Java 虚拟机(JVM)中,内存分配与垃圾回收是影响程序性能的核心机制。内存分配的高效性直接决定了对象创建的速率,而垃圾回收策略则决定了内存的利用率以及系统的稳定性。为了在复杂多变的应用场景中实现高效的内存管理&am…

【二分查找】寻找峰值(medium)

6. 寻找峰值(medium) 题⽬描述:解法⼆(⼆分查找算法):算法思路:C 算法代码:Java 算法代码: 题⽬链接:162. 寻找峰值 题⽬描述: 峰值元素是指其值…

MongoDB与PHP7的集成与优化

MongoDB与PHP7的集成与优化 引言 随着互联网技术的飞速发展,数据库技术在现代软件开发中扮演着越来越重要的角色。MongoDB作为一种流行的NoSQL数据库,以其灵活的数据模型和强大的扩展性受到众多开发者的青睐。PHP7作为当前最流行的服务器端脚本语言之一,其性能和稳定性也得…

【GIT】github中的仓库如何删除?

你可以按照以下步骤删除 GitHub 上的仓库(repository): 🚨 注意事项: ❗️删除仓库是不可恢复的操作,所有代码、issue、pull request、release 等内容都会被永久删除。 🧭 删除 GitHub 仓库步骤…

焊接机排错

焊接机 一、前定位后焊接 两个机台,①极柱定位,相机定位所有极柱点和mark点;②焊接机,相机定位mark点原理:极柱定位在成功定位到所有极柱点和mark点后,可以建立mark点和极柱点的关系。焊接机定位到mark点…

认识和使用Vuex-案例

集中管理共享的数据,易于开发和后期维护;能够高效的实现组件之间的数据共享,提高开发效率;存储在Vuex的数据是响应式的,能够实时保持页面和数据的同步; 安装Vuex依赖包 npm install vuex --save导入包 im…

LLM大模型中的基础数学工具—— 信号处理与傅里叶分析

Q51: 推导傅里叶变换 的 Parseval 定理 傅里叶变换的 Parseval 定理揭示了啥关系? Parseval 定理揭示了傅里叶变换中时域与频域的能量守恒关系,即信号在时域的总能量等于其在频域的总能量。这就好比一个物体无论从哪个角度称重,重量始终不…

对Mac文字双击或三击鼠标左键没有任何反应

目录 项目场景: 问题描述 原因分析: 解决方案: 项目场景: 在使用Mac系统的时候,使用Apple无线鼠标,双击左键能够选取某个单词或词语,三击左键能够选取某一行,(百度、…

Go语言企业级项目使用dlv调试

使用dlv调试Go语言代码 打包Go代码(禁止优化和内联(便于调试更复杂的逻辑)): go build -gcflags"all-N -l" -o xxx_api_debug.exe启动一个dlb监听可运行程序的端口: dlv --listen:2345 --headlesstrue --api-version…

Kafka命令行的使用/Spark-Streaming核心编程(二)

Kafka命令行的使用 创建topic kafka-topics.sh --create --zookeeper node01:2181,node02:2181,node03:2181 --topic test1 --partitions 3 --replication-factor 3 分区数量,副本数量,都是必须的。 数据的形式: 主题名称-分区编号。 在…

Python3:Jupyterlab 安装和配置

Python3:Jupyterlab 安装和配置 Jupyter源于Ipython Notebook项目,是使用Python(也有R、Julia、Node等其他语言的内核)进行代码演示、数据分析、机器学习、可视化、教学的非常好的工具。 最新的基于web的交互式开发环境,适用于n…

快速排序及其在Unity游戏开发中的应用

一、快速排序(Quick Sort) 快速排序是一种**分治法(Divide and Conquer)**思想的排序算法,它的基本步骤是: 选一个基准元素(pivot):通常选第一个元素、最后一个元素,或者随机一个。分区(Partition):把数组分成两部分,小于等于 pivot 的放左边,大于 pivot 的放右…

【硬核干货】SonarQube安全功能

原文链接:【硬核干货】SonarQube安全功能 关于晓数神州 晓数神州坚持以“客户为中心”的宗旨,为客户提供专业的解决方案和技术服务,构建多引擎数字化体系。 核心业务1:聚焦DevOps全栈产品,打造需求管理、项目管理、开…

修改el-select背景颜色

修改el-select背景颜色 /* 修改el-select样式--直接覆盖默认样式(推荐) */ ::v-deep .el-select .el-input__inner {background-color: #1d2b72 !important; /* 修改输入框背景色 */color: #fff; } ::v-deep .el-select .el-input__wrapper {background-…

Unity-粒子系统:萤火虫粒子特效效果及参数

萤火虫特效由两部分组成。萤火虫粒子底色粒子面片。萤火虫的旋转飞动主要由 Noise参数和Color over Lifetime模块控制。 贴图:中间实周边虚的圆,可随意自行制作 Shader:Universal Render Pipeline/2D/Sprite-Lit-Default 以下是粒子详细参…

K8S Service 原理、图例——深度好文

一、理论介绍 1.1、3W 法则 1、是什么? Service 是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当 Service 存在时,它的IP地址和端口不会改变。客户端通过IP地址和端口号与 Service 建立连接,这些连接会被路由到提供该 Service 的…

Alibaba Cloud Linux 3.2104 LTS 64位 容器优化版安装docker docker compose记录

整个安装过程耗时4小时。(包含以下检查内容:) 检查该linux版本信息(并通过监控指标检查运行状态/cpu占用/内存占用/磁盘读取写入IOPS /同时连接数) 1:根据当前的系统进行yum与dnf的升级,保持稳定修复的版本…