目录

1. 内存寻址

1.1 X86寻址技术演变

1.1.1 8086引入段机制

1.1.2 80286引入保护模式

1.1.3 80386在段寄存器上构建保护模式

1.2 80x86寄存器简介

1.2.1 通用寄存器

1.2.2 段寄存器

1.2.3 指令指针寄存器

1.2.4 标志寄存器

1.2.5 控制寄存器

1.3 物理地址、虚拟地址和线性地址

1.3.1 不同视角下的内存

1.3.2 三种内存空间与地址

2. 分段机制

2.1 段描述符

2.1.1 段描述符结构

2.1.2 段描述符结构实现

2.1.3 段选择符构成

2.2 地址转换与保护

2.3 Linux中的分段机制

2.3.1 目标

2.3.2 原因

2.3.3 解决方案

2.3.4 实现方法

2.4 基于段机制构建保护模式的讨论

2.4.1 CPL、RPL与DPL

2.4.2 基于特权级的保护模式

3. 分页机制

3.1 概述

3.2 页与页表

3.2.1 页与物理页面

3.2.2 页表

3.2.3 两级页表

3.2.4 页表项结构

3.2.5 线性地址到物理地址的转换

3.2.6 页面高速缓存TLB

3.3 Linux中的分页机制

3.3.1 Linux三级分页机制

3.3.2 页表初始化模拟示例


1. 内存寻址

1.1 X86寻址技术演变

1.1.1 8086引入段机制

① 16位数据总线(ALU宽度),即可以直接加以运算的指针长度为16位

② 20位地址总线,即可以寻址1MB空间

此处就需要解决如何通过16位的数据总线寻址20位地址的问题,为此8086引入了段机制,具体如下,

① 设置4个16位的段寄存器(CD、DS、SS、ES)

② 最终的访问地址由段基址(存储在段寄存器中) + 偏移地址组成(存储在通用寄存器中)

③ 由CPU内部的地址加法器生成实际送上地址总线的地址值,其中,

物理地址 = 段基址左移4位 + 偏移地址

说明:引入段机制的优势

① 程序中的地址不再需要硬编码

② 调试错误更容易定位

③ 支持更大的内存地址

1.1.2 80286引入保护模式

① 16位数据总线,24位地址总线

② 引入保护模式,在保护模式下内存段的访问受到了限制,访问内存时不能直接从段寄存器中获得段的起始地址,而是需要经过额外的转换和检查

③ 向下兼容的实模式

系统启动时处理器处于实模式,只能访问1MB空间。经过设置可进入保护模式,访问空间扩大到16MB。但是从保护模式返回实模式,只能重启

1.1.3 80386在段寄存器上构建保护模式

① 32位数据总线,32位地址总线

② 由于需要向下兼容,80386必须维持原有的段寄存器设计

③ 最终80386选择在段寄存器的基础上构建保护模式,在保护模式下段的范围可达到4GB(详见后文分析)

1.2 80x86寄存器简介

注:此处面向32位的80x86体系结构

1.2.1 通用寄存器

① 将16位的通用寄存器扩充为32位

② 通用寄存器的低位可单独使用(用于兼容16位以及8位的操作),以AX寄存器为例,

AL:8 bit

AX:16 bit

EAX:32 bit

1.2.2 段寄存器

① 4个段寄存器仍然为16位

② 在保护模式下,段寄存器中不再存放段基址,而是某个段的选择符(Selector)

说明:引入段选择符的原因

段寄存器只有16位,但是数据 & 地址总线为32位,所以段寄存器本身已无法容纳32位的段基址

所以将段基址存放在描述符表(Descriptor)中,段寄存器中存放的选择符则是描述符表的索引

1.2.3 指令指针寄存器

① 指令指针寄存器扩充为32位,称为EIP

② EIP中仍然存放下一条将要执行指令的偏移地址(offset)

③ EIP的低16位可以单独访问,即IP

1.2.4 标志寄存器

① 标志寄存器扩充为32位,称为EFLAGS

② EFLAGS存放有关处理器的控制标志,很多标志与16位FLAGS中的标志含义相同

1.2.5 控制寄存器

增加4个32位控制寄存器,主要用于操作系统的分页机制,结构如下,

1.2.5.1 CR0

① 内核中主要使用bit0 & bit 31,其中,

bit 0:保护允许位PE(Protectable Enable),用于启动保护模式

bit 31:分页允许位PG(Paging Enable),用于使能分页组件

二者结合,控制处理器的工作模式,

② 使能分页的汇编代码

# 将CR0的bit 31置1
movl %cr0, %eax
orl $0x80000000, %eax
movl %eax, %cr0

1.2.5.2 CR1

未定义的控制寄存器,供将来的处理器使用

1.2.5.3 CR2

① 缺页线性地址寄存器,保存最后一次出现缺页的32位线性地址

② 如果CPU访问到一个有效的线性地址,但是这个地址对应的页没有在内存中,则CPU产生一个缺页异常,同时将这个线性地址存入CR2寄存器

1.2.5.4 CR3

① 页目录基址寄存器,保存页目录的物理地址

② 由于页目录基址寄存器只记录地址的高20位,所以页目录需要放在以4KB位单位的存储器边界上

1.3 物理地址、虚拟地址和线性地址

1.3.1 不同视角下的内存

① 硬件视角

内存是插在或固化在主板上的内存条,他们有一定的容量,比如128MB

② 应用程序视角

可以被应用程序使用的内存空间,比如开发一个占用1GB内存的程序,并让其在操作系统下运行,即使这台机器只有128MB物理内存

③ 操作系统视角

介于上述二者之间,既要知道物理内存的地址,也要提供一套机制,为应用程序提供另一个内存空间,这个内存空间的大小可以和实际的物理内存大小之间没有关系

1.3.2 三种内存空间与地址

① 物理内存空间 & 地址

物理内存空间:物理内存条提供的内存空间

物理地址:每个内存单元的实际地址

② 虚拟内存空间 & 地址

虚拟内存空间:应用程序看到的内存空间

虚拟地址:虚拟内存空间的地址,也称为逻辑地址,一般用"段:偏移量"形式描述

③ 线性内存空间 & 地址

线性内存空间:一段连续的,不分段的,范围是0~4GB的地址空间(以32位为例)

线性地址:线性内存空间的一个绝对地址

说明1:保护模式下,CPU产生的虚拟地址(段 + 偏移)被送到MMU,由MMU将虚拟地址映射为物理地址

说明2:分段部件与分页部件

MMU是一个硬件电路,其中包含两个部件,即分段部件和分页部件。在80x86体系结构中,

① 分段部件(机制):将虚拟地址转换为线性地址

② 分页部件(机制):将线性地址转换为物理地址

特别注意:在80x86体系结构中,分段机制是必选项,分页机制是可选项(从CR0寄存器的介绍中可知)。在设计OS时,必须兼顾其他不支持分段机制的体系结构(e.g. ARM),所以OS的策略是使用但绕过80x86的分段机制

说明3:Linux中编译链接后产生的ELF可执行文件中,代码段的线性地址均从0x8000000开始(只编译不链接是从0x0开始)

2. 分段机制

2.1 段描述符

2.1.1 段描述符结构

段是虚拟地址空间的基本单位,段描述符的目的是描述虚拟空间中的一个段与线性地址的对应关系,需要包含如下内容,

① 段的基地址(Base Address):在线性地址空间中段的起始地址

② 段的界限(Limit):在虚拟地址空间中,段内可以使用的最大偏移量

③ 段的保护属性(Attribute):表示段的特性,e.g. 该段是否可读可写,是否可作为一个程序来执行,以及段的特权级等

说明1:如上图中的表就是段描述符表(或称作段表),其中的表项称作段描述符(Segment Descriptor)

说明2:在保护模式下,段寄存器中存储的就是段描述符表的索引

2.1.2 段描述符结构实现

段描述符共8B,组成方式如下,

① 32位段基址

② 20位段界限,即段长,粒度由G位控制

③ 粒度位G位:

当G = 0时,段长以字节为单位,则最大段长为2^20B = 1MB

当G = 1时,段长以页(4KB)为单位,则最大段长为2^20 * 4KB = 4GB

④ 缺省操作数大小位D位:

当D = 0,操作数为16位

当D = 1,操作数为32位

⑤ 存取权限字节

a. 存在位P位(Present):表示该段是否在内存中,P = 1在内存中,P = 0不在内存中

b. 描述符特权级DPL(Descriptor Privilege Level):值为0 ~ 3,用来确定这个段的特权级,即保护等级

c. S位(System):表示这个段是系统段还是用户段,S = 0为系统段,S = 1为用户程序的代码段、数据段或堆栈段

d. 类型位,共由3位组成,不同的类型组合可用于标识不同类型的段

e. A位(accessed):表示一个段最近是否被访问过,准确地说是指明从上次操作系统复位后,该段是否被访问过

当创建段描述符时,应该把这位清零,之后每当该段被访问时(即将该段的段选择符加载进段寄存器),将A位置1。对该位的清零由操作系统负责,通过定期监视该位的状态,可以统计出该段的使用频率,当内存紧张时,可以把不常用的段swap到磁盘上,从而实现虚拟内存管理

说明1:关于代码段的一致性

非一致性代码段:可以被同级代码段调用,或者通过门调用

一致性代码段:可以从低特权级的程序转移到该段执行,但是低特权级的程序仍然保持自身的特权级

tips:所有数据段都是非一致性的,即他们不能被低特权级的程序或过程访问。与代码段不同的是,数据段可以被更高特权级的程序或过程访问,而无需使用特殊的访问门

参考资料:

https://blog.csdn.net/longintchar/article/details/50489889

说明2:在保护模式下,有三种类型的描述符表,

① 全局描述符表(Global Descriptor Table,GDT),启动阶段使用

② 中断描述符表(Interrupt Descriptor Table,IDT),全程使用,动态分配与回收

③ 局部描述符表(Local Descriptor Table,LDT),目前基本不使用

为加快对这些表的访问,Intel设计了专门的寄存器GDTR、IDTR和LDTR,用于存放这些表的基地址及表的长度界限

2.1.3 段选择符构成

在保护模式下加载到段寄存器中的,就是段选择符,具体构成如下,

① 索引

a. 段描述表的索引,用以指明当前使用的是哪个段描述符

b. 索引字段共13位,也就是最多可索引8K个段,实际的段远没有这么多

② TI(图中有误)

TI(Table Indicator)为选择域,

当TI = 0,从GDT表选择段描述符

当TI = 1,从LDT表选择段描述符

③ RPL

RPL(Request Privilege Level)为请求者的特权级,保护模式会比较RPL与要访问段的DPL,只有高特权级可以访问地特权级,低特权级无法访问高特权级

这里就实现了保护模式中的保护

2.2 地址转换与保护

① 在段寄存器中装入段选择符,同时把32位地址偏移量装入某个寄存器

② 根据选择符中的索引值、TI及RPL值,再根据相应描述符表中的段地址、段界限和特权级等进行合法性检查(e.g. 界限检查、特权级检查、操作权限检查[比如是否请求在只读段写入])

③ 如果检查通过,则将相应的段描述符加载到段描述符高速缓冲器中

④ 将段描述符中的基地址和另一寄存器中的偏移量相加,就形成了32位线性地址

2.3 Linux中的分段机制

2.3.1 目标

使用且绕过80x86的段机制

2.3.2 原因

① 在80x86保护模式下,分段机制是必选项

② 大多数体系架构均不支持分段机制,只支持分页机制,OS在设计时要考虑如何兼容大多数不支持分段机制的体系结构

2.3.3 解决方案

① 设置段基地址为0,段界限为4GB的段描述符

② 在上述段描述符下,有如下关系,

段基址 + 偏移量 = 线性地址

段基址 = 0

所以,

偏移量 = 线性地址

这样就直接实现了虚拟地址到线性地址的映射,寄存器中的虚拟地址和最终的线性地址就是同一地址了

说明:类似ARM的RISC处理器,不支持分段模式,所以没有虚拟地址到线性地址的转换,所以在类似场景下,术语虚拟地址和线性地址是混用的,表示同一个意思

2.3.4 实现方法

① Linux内核运行在特权级0,用户程序运行在特权级3

② 80x86要求代码和数据要创建不同的段

所以共需要创建4个段描述符,即

特权级为0的代码段和数据段

特权级为1的代码段和数据段

书中给出的设置范例如下,

此处可见GDT表是从第3项开始使用的,其中第1项不使用是防止上电后段寄存器未经初始化就进入保护模式

在2.6.35版本内核中只设置了如下几项,

说明:GDT_ENTRY宏

内核代码中使用GDT_ENTRY宏构建段描述符,可见构建方式符合80x86的段描述符格式,这属于体系结构相关代码,所以是在arch/x86/include/asm/segment.h文件中

这里需要注意的是2处位运算,按如下方式就很好理解了

((base) & 0xff000000ULL) << (56 - 24) // 相当于先右移24,再左移56
((limit) & 0x000f0000ULL) << (48 - 16) // 相当于先右移16,再左移48

2.4 基于段机制构建保护模式的讨论

2.4.1 CPL、RPL与DPL

① CPL(Current Privilege Level,当前特权级)

a. 表示当前正在执行的代码所处的特权级

b. CPL存储在CS中的最低2位,当段选择子成功装入CS寄存器后,相应的段选择子中的RPL就变成了CPL

② RPL(Request Privilege Level,请求特权级)

a. 表示进程对段访问的请求权限,即进程想要以怎样的权限来访问目标段,RPL由程序员自己设置

b. RPL存储在段选择子的最后2位

c. RPL可以大于CPL,也就是进程请求的访问特权级可以比进程当前的特权级低

③ DPL(Descriptor Privilege Level,描述符特权级)

a. 表示内存段的特权级

b. DPL存储在段描述符中,当程序的运行环境建立之后,段描述符一般不需要修改,因此每个段的DPL是固定的

2.4.2 基于特权级的保护模式

对访问的特权级检查要求为,

EPL = max(RPL, CPL) <= DPL

因此低特权级的进程无法访问高特权级内存,Linux借此实现了对内核态数据的保护。此时内核数据段与代码段的DPL为0,用户态进程的CPL为3,所以用户态进程无法访问内核态数据

由此可见,操作系统是一个软硬件高度结合的系统软件,操作系统的实现需要依赖硬件提供的功能(从另一个方面,硬件设计时也需要考虑操作系统的需求)

说明:在X86架构中,无法通过mov指令直接修改CS & IP,只能通过call或jmp指令间接修改。也就是说,程序不能直接修改CS,进而修改自身的CPL,因此用户态程序无法直接跳转到内核态运行,这也就引出了系统调用的概念

ARMv7架构也类似,用户态程序不能修改CPSR,进而修改当前进程的运行状态,也是需要通过触发系统调用,进入内核态

3. 分页机制

3.1 概述

① 分页机制的目的是实现线性地址到物理地址的转换

② 80x86的分页机制是可选的,如果CR0的bit 31为0,则不允许分页,此时分段机制产生的线性地址就是物理地址

3.2 页与页表

3.2.1 页与物理页面

① 将虚拟地址空间划分为若干大小相等的片,称为页(Page),并将各页加以编号

② 将物理地址空间与页大小相等的若干个存储块,称为页面(Page Frame),也同样加以编号

在使用时,将页与页面关联起来,将线性地址空间中的若干页分别装入多个可以不连续的物理页面中

说明1:图中存在多个页指向同一个页面的情况,在多进程环境中,不同进程可以先后使用相同的物理页面,只是某一时刻只有一个进程使用

说明2:页大小

页过大或过小都会影响内存的使用效率,而且可用的页大小也需要硬件的支持(此处也体现出操作系统设计中软硬件结合的特性)

80x86支持的标准页大小为4KB(也支持4MB的页),在32位处理器中,使用4KB页既高效又巧妙

目前X86架构支持的页大小如下,

IA-32:4KB / 2MB / 4MB

IA-64:4KB / 8KB / 64KB / 256KB / 1MB / 4MB / 16MB / 256MB

剧透:巧妙是因为页表正好可以装在一个页中,后文有分析

3.2.2 页表

3.2.2.1 什么是页表

① 页表是把线性地址映射到物理地址的一种数据结构

② 就像段表是把虚拟地址映射到线性地址的数据结构

说明:地址转换的优化

将线性地址转换成物理地址在时间和空间上都要付出代价,因此必须进行优化,

时间的优化:因为访问内存很频繁,所以映射关系一定要简单,所以实现为简单的查表算法,这就是页表引入的原因

空间的优化:内存空间按字节编址,但是按字节进行映射效率很低,于是需要按照一定粒度(也就是页)进行映射。这样粒度内的相对地址(也就是业内偏移量)在映射时保持不变

3.2.2.2 页表项构成

① 物理页面基地址:线性地址空间中的一个页装入内存后所对应的物理页面的起始地址

② 页的属性:描述页的特性

说明1:因为页的大小是固定的,所以无需界限特性

说明2:如何存储页的属性

由于页面的大小是4KB,所以物理页面的基地址(32位)一定是4KB的倍数,因此页面基地址的低12位为0,可以使用这12位记录页面属性,这样一个4B的结构就可以描述一个页表项

3.2.2.3 一级存储空间需求

4GB的线性空间可以划分为1M个4KB大小的页,每个页表项占4B,如果只使用一级页表就需要物理连续的4MB空间存放

说明:为什么一级页表要填满

严格意义上说,对页表项的填充只要满足线性地址使用的最大值即可。考虑一个极端的情况,整个进程只使用0xFFFFFFFC这一个4B单元,也需要填充整个一级页表。因为一旦开启分页模式,线性地址中相应的字段就会被作为页表索引使用,虽然此处只需要最后一个页表项有效,但是之前的1MB - 1个页表项空间依然需要被占用,只是其中没有有效的页表项而已

类似只使用一个巨大数组的最后一个成员,虽然只是通过索引访问这一个成员,也分配了整个数组

那么问题就来了,为什么我们不可以只集中使用前面的地址空间呢 ?这就要涉及Linux中进程的地址空间分布,在实际使用中,往往是用两头中间空,这样就会导致中间无需填充的页表项浪费空间

而且在支持多进程的Linux中,每个进程都会有自己的页表,如果每个页表占用4MB内存,显然也是不现实的

3.2.3 两级页表

两级页表就是将对页表再进行分页,第1级称为页目录,其中存放页表的信息;第2级是页表,其中存放物理页面的信息

80386中按上图方式分配两级页表,页目录为2^10项,每个页目录指向的页表包含2^10个页表项。相当于每个页目录项最终映射2^10 * 4KB = 4MB的空间,整个页目录1K项,则正好映射4GB

其中页目录项和页表项均为4B,则页目录和每个页表均为4KB,正好可以放在一个页面内(这就是前文所述的巧妙之处)

说明1:为什么使用两级页表可以节省内存

根据上文的两级页表分配方案,共由1个页目录和1K个页表,如果完全填满需要(1 + 1K)* 4KB内存,也就是说比一级页表还多用了1个页面

但是!!!在实际的进程使用中,考虑到用两头中间空的进程内存布局,假设只使用线性空间最前和最后的4MB,计算下页表所需内存

此时需要占用1个页目录,2个页表项目,共12KB空间,如果使用一级页表则需要4MB内存!!!

所以多级页表节省内存是和实际的进程空间高度相关的

说明2:线性地址结构

使用两级页表的线性地址结构如上图所示,前20 bit分别为页目录和页表的索引,可使用伪代码描述如下,

typedef struct {// 用作页目录中的下标// 对应的目录项指向一个页表unsigned int dir:10;// 用作页表的下标// 对应的页表项指向一个物理页面unsigned int page:10;// 在4KB物理页面内的偏移量unsigned int offset:12;
} LinearAddr;

说明3:两级页表如何划分

首先需要注意,两级页表的划分是80x86硬件支持的,其他可用的页表划分均需要硬件支持

其次在硬件设计页表划分时可用的组合当然很多,假设使用a位索引页目录,b位索引页表,只要确保a + b = 20即可。但是合理的划分可以提高内存使用效率,比如此处划分为2个10 bit,则页目录和页表正好是4KB,可以放入一个页面

3.2.4 页表项结构

无论是页目录项还是页表项,都使用上图的表项结构,每个表项4B

下面简要说明下页表项的页面属性,

① P位(Present)

P = 1表示页装入到内存中;如果P = 0表示不在内存中

说明:当要访问的页目录项或页表项的P位为0时,说明此页没有装入内存中,此时分页机制在转换线性地址的同时会产生一个缺页异常(这就是内存管理的核心机制)

② R/W(Read / Write)和U/S(User / Supervisor)

这2位为页表或页提供硬件保护,当U/S = 0时,只有处于内核态的操作系统才能对此页或页表进行寻址

③ PWT位(Page Write-Through)

表示是否采用写透方式,当PWT = 1时,写透方式就是既写内存也写高速缓存

④ PCD位(Page Cache Disable)

表示是否启用高速缓存,当PCD = 1时,表示不启用高速缓存

⑤ Page Size

只适用于页目录项,如果设置为1,页目录项指的是4MB的页

tips:可理解为大页模式,即只有一级页表

3.2.5 线性地址到物理地址的转换

① 用32位线性地址的bit [31:22]作为页目录项的索引,将他乘以4,与CR3中页目录的起始地址相加,得到相应目录项在内存的地址

② 从这个地址读取32位目录项,取出其中的高20位,将低20位补0,得到页表在内存的起始地址

③ 用32位线性地址的bit [21:12]作为页表中页表项的索引,将他乘以4,与页表的起始地址相加,得到相应页表项在内存的地址

④ 从这个地址读取32位页表项,取出其中的高20位,再将线性地址的bit [11:0]放在低12位,形成最终32位的页面物理地址

说明:使能分段机制前,需要设置好段表;使能分页机制前,需要设置好页表

因为相关机制一旦使能,相应的地址转换机制就会开始工作

3.2.6 页面高速缓存TLB

① 在使用分页机制时,页表存储在内存中

② 因此CPU每次存取一个数据时,都要至少访问内存2次(访问页表 + 访问实际数据)

③ 增加访问内存次数,将大大降低访问的速度

④ 为了提高速度,80x86中设置了一个最近存取页的高速缓存硬件机制,他自动保存32项处理器最近使用的页表项,可以覆盖128KB的内存地址,该硬件也称为旁路转换缓冲器(TLB,Translation Lookaside Buffer)

⑤ 当访问线性地址空间的某个地址时,先检查对应的页表项是否在高速缓存中。平均来说,TLB约有90%的命中率

3.3 Linux中的分页机制

3.3.1 Linux三级分页机制

3.3.1.1 为何采用三级分页

① 虽然32位的80386物理上采用两级页表,但是许多处理器都采用64位结构,物理上支持更多层次的页表。而且随着处理器位数的上升,也需要更多级的页表才能起到更好地划分地址空间节省页表内存的目的

② Linux的设计目标之一就是能够把自己移植到大多数流行的处理器平台上,所以采用了三级页表机制

说明:在最新的Linux内核版本中,已经使用四级页表机制

3.3.1.2 三级分页机制

Linux中的三级页表构成如下,

① 页总目录PGD(Page General Directory)

② 页中间目录PMD(Page Middle Directory)

③ 页表PT(Page Table)

说明1:处理软件架构与硬件实现的差异

当三级分页模式应用到只有两级页表的80386处理器时,Linux使用一系列宏来掩盖各种平台的细节

在arch/x86/include/asm/pgtable-2level_types.h中有如下宏定义,

从上述宏定义可见,80386中只使用两级页表,物理上并没有PMD

作为对比,我们可以看下arch/x86/include/asm/pgtable-3level_types.h中的宏定义,从文件名可知,在这种模式下使用了三级页表。X86的PAE(Physical Address Extension)模式会使用三级页表

从上述宏定义中,可以得到线性地址的划分方式如下,

分析结果与源码中的宏定义也是匹配的

附件:

说明2:进程切换

每个进程都有自己的页目录和自己的页表集,当进程切换时,Linux把CR3控制寄存器中的内容保存在前一个进程的PCB中,然后从下一个进程的PCB中装载到CR3中。因此,当进程恢复在CPU上执行时,分页单元会指向一组正确的页表

说明3:Linux分页模式的更新

Linux 2.6之后Linux内核使用四级分页模式,线性地址划分如下图所示,

在Linux 4.15中又引入了五级分页模式

说明4:在使能分段和分页机制后,X86架构的地址转换流程如下图所示

3.3.2 页表初始化模拟示例

使用以下代码模拟页表初始化的过程,并加以分析

此处实际使用两级页表,没有中间目录项。模拟填充4个页目录项(最开始的4个),对应需要4个页表,共映射16MB内存(从0x0开始的16MB内存空间)

#define NR_PGT 0x4 // 共初始化4个页目录项
#define PGD_BASE (unsigned int *)0x1000  // 页目录起始地址,页目录为4KB
#define PAGE_OFFSET (unsigned int)0x2000 // 页目录项指向的页表起始地址// 页目录项 & 页表项属性
#define PTE_PRE 0x1 // Page present
#define PTE_RW  0X2 // Page Readable / Writable
#define PTE_USR 0x4 // User Privilege Levelvoid page_init(void)
{int pages = NR_PGT;unsigned int page_offset = PAGE_OFFSET;unsigned int *pgd = PGD_BASE;   // 页目录从物理内存的第2个页框开始unsigned int phy_addr = 0x0000; // 映射的物理内存地址unsigned int *pgt_entry = (unsigned int *)0x2000; // 页表起始地址// 初始化4个页目录项while (pages--){*pgd++ = page_offset | PTE_USR | PTE_RW | PTE_PRE;page_offset += 0x1000; // 每个页目录项对应一个页表,每个页表4KB}pgd = PGD_BASE; // 后续写入CR3寄存器的值,即页目录的起始地址// 初始化4个页表,共映射16MB内存while (phy_addr < 0x1000000){*pgt_entry++ = phy_addr | PTE_USR | PTE_RW | PTE_PRE;phy_addr += 0x1000; // 每个页表项对应一个页面,每个页面4KB}__asm__ __volatile__ ("movl %0, %%cr3;""movl %%cr0, %%eax;""orl $0x80000000, %%eax;""movl %%eax, %%cr0;"::"r"(pgd):"memory", "eax")
}

说明:内嵌汇编中对CR3的操作是将页目录的起始物理地址写入;对CR0的操作是使能分页机制

Linux操作系统原理与应用02:内存寻址相关推荐

  1. linux的原理和运用,Linux操作系统原理与应用_内存寻址

    原标题:Linux操作系统原理与应用_内存寻址 第五讲今天上线啦. 在本次课程中,陈老师详细的讲解了有关于内存寻址的演变的相关知识. 第一部分中,介绍了关于内存寻址的相关背景知识.内存寻址-操作系统设 ...

  2. Linux 操作系统原理 — 内存 — 页式管理、段式管理与段页式管理

    目录 文章目录 目录 前文列表 页式管理 快表 多级页表 基于页表的虚实地址转换原理 应用 TLB 快表提升虚实地址转换速度 页式虚拟存储器工作的全过程 缺页中断 为什么 Linux 默认页大小是 4 ...

  3. Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术

    目录 文章目录 目录 前文列表 物理地址与虚拟地址 内存空间的组织方式 虚拟地址空间的编址 内核态地址空间 用户态地址空间 内-外存空间的交换与虚拟存储空间之间的映射关系 缺页异常 前文列表 < ...

  4. Linux 操作系统原理 — 内存 — 内存分配算法

    目录 文章目录 目录 前文列表 内存碎片 伙伴(Buddy)分配算法 Slab 算法 虚拟内存的分配 内核态内存分配 vmalloc 函数 kmalloc 用户态内存分配 malloc 申请内存 用户 ...

  5. Linux 操作系统原理 — 内存 — 基于局部性原理实现的内/外存交换技术

    目录 文章目录 目录 前文列表 基于局部性原理实现的内-外存交换技术 局部性原理 Swap 交换分区 前文列表 <Linux 操作系统原理 - 内存 - 物理存储器与虚拟存储器> < ...

  6. Linux 操作系统原理 — 系统结构

    目录 文章目录 目录 Linux 系统架构 Linux 内核 内存管理 进程管理 文件系统 设备驱动程序 网络接口 Shell Linux 系统架构 Linux 系统一般有 4 个主要部分:内核.Sh ...

  7. Linux操作系统原理与应用04:内存管理

    目录 1. Linux内存管理概述 1.1 内存的层次结构 1.2 虚拟内存概述 1.2.1 虚拟内存基本思想 1.2.2 进程虚拟地址空间 1.3 内核空间到物理空间的映射 1.3.1 内核空间的线 ...

  8. Linux 操作系统原理 — 进程与线程管理

    目录 文章目录 目录 前言 进程与线程 内核线程,用户线程与轻量级进程 内核线程 轻量级进程 用户线程 轻量级进程与用户线程的区别 用户线程与轻量级进程的混合模式 用户线程和内核线程的区别 线程的实现 ...

  9. linux操作系统原理_Linux内核分析-操作系统是如何工作的(二)

    linux操作系统的主要构架如图1所示,我们知道,操作系统是通过管理CPU进程.存储器.文件系统.设备驱动.以及网络接口等相关部分来工作的,我们这里主要是通过分析关于CPU的操作即进程的管理执行来分析 ...

最新文章

  1. 【校招面试 之 网络】第3题 HTTP请求行、请求头、请求体详解
  2. filter 灰度处理:公祭日,一行代码让页面变成黑白色调
  3. zookeeper下载安装过程
  4. python输入list_python学习(list增删改查、及常用方法)
  5. android 模仿uc标签页,android模仿UC首页天气效果
  6. C#调用系统蜂鸣(需要发出警告时挺好用的 即使没有声卡)
  7. iPad不完美?盖茨的酸葡萄心理
  8. 微服务框架和工具大全
  9. 收音机磁棒天线4根接法_五六十年代不需要电的收音机,你见过吗?
  10. Kafka分区与消费者的关系
  11. 校园网锐捷Ubuntu12.04LTS
  12. js 中实现 汉字按拼音排序
  13. [OpenCV+VS2015]火焰检测算法(HSI判据)
  14. 关于ISI、SCI、EI、IEEE、Elsevier、Springer的区别
  15. 要重复多少次变成潜意识_从骨子里的改变-潜意识的力量!
  16. 学校计算机硬件管理制度,学校规章制度之计算机硬件管理制度
  17. 第13届蓝桥杯赛后感想
  18. sobol灵敏度分析matlab_灵敏度分析_使用MATLAB编写.doc
  19. GlusterFS(上)
  20. python3.6.6安装插件_linux python3.6安装uwsgi报错

热门文章

  1. Spring @Scheduled定时任务调度配置的详解
  2. MyEclipse中流程定义文件保存时自动生成流程图
  3. Linux中的提权操作
  4. java 套接字 访问tcp_Java 网络编程(五) 使用TCP/IP的套接字(Socket)进行通信
  5. linux7怎么配yum,centos7怎么配置yum
  6. Java队列Queue的使用
  7. mysql批量插入数据,一次插入多少行数据效率最高
  8. windows下mysql解压包安装
  9. java 获取属性值和设置属性值
  10. C# DataGridView 如何选中整行