【av68676164(p55-p58)】 Intel CPU和Linux内存管理
7.4.1 Intel CPU物理结构
x86实模式
实模式
- 20位:1M内存空间
- 地址表示方式:段地址(16位):偏移地址(16位)
- 段地址4位对齐
保护模式(Protect Mode)
- 32位地址空间:4G内存
- 支持多任务、任务切换、上下文保护
- 进程隔离:代码和数据的安全
- 支持分段机制和分页机制
- 新的寄存器
- EAX~EDX:扩充到32位
- CR0~CR4
- GDTR
- LDTR
- IDTR
- ……
保护模式的寄存器模型

控制寄存器CR0
CR0的低5位组成机器状态字(MSW)
- PE:0—实模式;1—保护模式
- MP:1(系统有数字协处理器时)
- EM:0(仿真协处理器)
- TS:任务切换,切换任务时自动设置
- PG:允许分页

控制寄存器CR2
如果发生缺页,引发缺页的线性地址保存在CR2中

控制寄存器CR3
CR3包含页目录基址:高20位

x86 CPU架构下的三种地址
逻辑地址:汇编语言(段:偏移)

若在保护模式下,DS不能理解为段基址
线性地址:由逻辑地址转换得到
物理地址
未分页 线性地址==物理地址
分页 线性地址!=物理地址
x86 CPU架构下的三种地址
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
第一级:段机制(逻辑地址到线性地址)
第二级:分页机制(线性地址到物理地址)

逻辑地址、物理地址、线性地址
Intel架构下
凡是在代码中书写的内存地址都是逻辑地址,也就是采用基地址+偏移量,我们能够直接看到的也是逻辑地址,通过左移等操作可以计算出对应的物理地址(在实模式下逻辑地址与物理地址没有中间层所以是可以的,但是在保护模式则是行不通的,再说)
实模式下
逻辑地址通过左移等操作可以计算出对应的物理地址
保护模式
- 在逻辑地址和物理地址之间多了一个中间层线性地址
- 逻辑地址左移等操作计算出来的是线性地址而不再是物理地址了, 线性地址就是32位的整数
- 通过分页机制将线性地址转为32位的物理地址
7.4.2 Intel CPU段机制
段与段描述符
段
一段连续内存
段描述符
描述段的属性,8字节(保护模式)
- 段基址
- 段界限
- 段属性
- 段类型
- 访问该段所需最小特权级
- 是否在内存
- ……
描述符(Descriptor)
段基址:32位(段基址1+段基址2)
段界限:20位(段界限1+段界限2)

| TYPE值 | 数据段和代码段描述符 S=1 | 系统段和门描述符 S=0 |
|---|---|---|
| 0 | 只读 | <未定义> |
| 1 | 只读,已访问 | 可用286TSS |
| 2 | 读/写 | LDT |
| 3 | 读/写,已访问 | 忙的286TSS |
| 4 | 只读,向下扩展 | 286调用门 |
| 5 | 只读,向下扩展,已访问 | 任务门 |
| 6 | 读/写、向下扩展 | 286中断门 |
| 7 | 读/写,向下扩展,已访问 | 286陷阱门 |
| 8 | 只执行 | <未定义> |
| 9 | 只执行、已访问 | 可用386TSS |
| A | 执行/读 | <未定义> |
| B | 执行/读、已访问 | 386TSS |
| C | 只执行、一致码段 | 386调用门 |
| D | 只执行、一致码段、已访间 | <未定义> |
| E | 执行/读、一致码段 | 386中断门 |
| F | 执行/渎、一致码段、已访问 | 386陷阱门 |
描述符的数据结构
typedef struct Descriptor {
unsigned int base24_31; //:8 基地址的高8位
unsigned int g; //:1 段长单位,0:字节
unsigned int d_b; //:1
unsigned int unused; //:1
unsigned int avl; //:1
unsigned int seg_limit_16_19; //:4 段界限高4位
unsigned int p; //:1
unsigned int dpl; //:1
unsigned int s; //:1
unsigned int type; //:4
unsigned int base_0_23; //:24 基地址的低24位
unsigned int seg_limit_O_15; //:16 段界限低16位
}
描述符表(Descriptor Table)
描述符表
存放描述符的数组
长度:8字节的整数倍
描述符表类型
- 全局描述符表GDT:Global Descriptor Table
- 局部描述符表LDT:Local Descriptor Table
- 中断描述符表IDT:Interrupt Descriptor Table
全局描述符表GDT:Global Descriptor Table
包含所有进程可用的段描述符,系统进1个GDT表
局部描述符表LDT:Local Descriptor Table
包含与特定进程有关的描述符,每个进程由自己的LDT
中断描述符表IDT:Interrupt Descriptor Table
包含终端服务程序段的描述符(中断门描述符)
类似中断向量表
选择子(Selector)
选择子用于选择GDT/LDT中的某个描述符
- 存放在段寄存器中:高13位是整数索引。
构成
- 索引域(INDEX):13位,给出段描述符在GDT或者IDT中的位置
- TI域(Table Indicator):1位,GDT(0)或IDT(1)
- 特权级别域(Request Privilege Level):2位

例:LDT基址0012 0000H,GDT基址00100000H,CS=1007H
- 请求的特权级是多少?
- 目标段的描述符位于GDT中还是LDT中?
- 目标段的描述符的基地址是多少?
解:(CS)=1007H=0001 0000 0000 0111B
RPL=3,申请的特权级位3
TI=1,描述符位于LDT内
描述符相对于LDT基址的偏移量位
\(0001 0000 0000 0B \times 8=512 \times 8 = 4096 = 1000H\)
段描述符的地址为
\(0012 0000H + 1000H = 0012 1000H\)
把逻辑地址转换到线性地址(32位,4G)

7.4.3 Linux页面机制
硬件分页
分页
- Intel CPU的页
- 通过设置CR0的PG位开启分页功能
- 分页:线性地址→物理地址(线性地址是分段功能获取的)
- 在MMU中进行分页
Linux的三级页表结构
普通页表实现时的问题
32位OS(4G空间),每页4K,页表每个记录占4字节
- 进程的页数:4G/4K=1M个页
- 页表的记录应有1M条记录
- 页表所占内存:1M*4字节=4M
- 页表占页框数:4M/4K=1K页框(连续)
- 页表所占内存:1M*4字节=4M
- 页表的记录应有1M条记录
问题:
- 难以找到连续1K个页框存放页表
- 页表全部放入过度消耗内存(4M)
解决办法
- 将4M的超大页表存储到离散的1K个页框中
- 仅将页表的部分内容调入内存
| 页号 | 页框号 | 中断位 | 外存地址 | 访问位 | 修改位 |
|---|---|---|---|---|---|
| 0 | 8 | 0 | 4000 | 1 | 0 |
| 1 | 24 | 0 | 8000 | 1 | 1 |
| … | … | … | … | … | … |
二级页表
把超大的页表(4M)以页为单位分成若干个小页表,存入离散的若干个页框中
为了对小页表进行管理和查找,另设置一个叫页目录的表,记录每个小页表的存放位置(页框号)
- 页目录实际上是一个特殊页表:每个记录存放的是小页表的标号和其所在的页框号之间的对应关系
页目录:一级页表或外部页表;小页表:二级页表
Windows NT二级页表的结构

页目录号:小页表页号(页目录的索引)31-22
页号:页面的编号(页表的索引)21-12
页偏移:页偏移 11-0
二级页表地址的映射特点
- 访问数据需要三次访问内存
- 页目录调入内存
- 页表按需要调入内存
- 页面、页表、页目录的大小都刚好4K(占一个页框)
7.4.4 Linux对段的支持
Linux段机制
进程建立时,段机制对寄存器初始化:start_thread()
#define start_thread(regs, new_eip, new_esp) do {
__asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));
set_fs(USER_DS);
//对段寄存器初始化
//__USER_DS数据段
regs->xds = __USER_DS;
regs->xes = __USER_DS;
regs->xss = __USER_DS;
//__USER_CS代码段
regs->xcs = __USER_CS;
regs->eip = new_eip;
regs->esp = new_esp;
} while (0)
#define __KERNEL_CS 0X10
#define __KERNEL_DS 0x18
#define __USER_CS 0x23
#define __USER_DS 0x2B
| 宏 | 值 | INDEX | TI | DPL |
|---|---|---|---|---|
| __KERNEL_CS | 0X10 | 0000 0000 0001 0 | 0 | 0 0 |
| __KERNEL_DS | 0x18 | 0000 0000 0001 1 | 0 | 0 0 |
| __USER_CS | 0x23 | 0000 0000 0010 0 | 0 | 1 1 |
| __USER_DS | 0x2B | 0000 0000 0010 1 | 0 | 1 1 |
INDEX:2,3,4,5;TI=0;DPL:0、3
GDT定义
ENTRY(gdt_table)
.quad 0x0000000000000000 /*NULL descriptor*/
.quad 0x0000000000000000 /*not used*/
.quad 0x00cf9a000000ffff /*0x10 kernel 4GB code at 0x00000000*/
.quad 0x00cf92000000ffff /*0x18 kernel 4GB data at 0x00000000*/
.quad 0x00cffa000000ffff /*0x23 user 4GB code at 0x00000000*/
.quad 0x00cff2000000ffff /*0x2b user 4GB data at 0x00000000*/
.quad 0x0000000000000000 /*not used*/
.quad 0x0000000000000000 /*not used*/
xxxx xxxx Gl00 hhhhPDP0 1010 xxxx xxxx xxxx xxxx xxxx xxxx hhhh hhhh hhhh hhhh
K_CS:0000 0000 1100 111110011010 0000 0000 0000 0000 0000 0000 1111111111111111
K_DS:0000 0000 1100 11111001 0010 0000 0000 0000 0000 0000 0000 1111111111111111
U_CS:0000 0000 1100 111111111010 0000 0000 0000 0000 0000 0000 1111111111111111
U_DS:0000 0000 1100 11111111 0010 0000 0000 0000 0000 0000 0000 1111111111111111 .
- XXXX:基地址;hhhh:段界限
- G位倒是1(段长单位4KB);P位都是1(段在内存)
Linux的段机制
- Linux四个范围一样的段:0~0xFFFFFFFF(4G)
- 内核数据段|内核代码段|用户数据段|用户代码段
- 各段属性不同
- 内核段特权级为0
- 用户段特权级位3
- 作用
- 利用段机制隔离用户数据和系统数据
- 保留段的等级保护机制
- 简化(避免)逻辑地址到线性地址转换
- 可以直接将虚拟地址当作线性地址,二者完全一致
- 利用段机制隔离用户数据和系统数据
【av68676164(p55-p58)】 Intel CPU和Linux内存管理的更多相关文章
- linux内存管理
一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分: 1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...
- Linux内存管理(二)
Linux内存管理之二:Linux在X86上的虚拟内存管理 本文档来自网络,并稍有改动. 前言 Linux支持很多硬件运行平台,常用的有:Intel X86,Alpha,Sparc等.对于不能够通用的 ...
- Linux内存管理(一)
Linux内存管理之一:基本概念篇 物理地址.线性地址(虚拟地址)和逻辑地址:阐述段式管理和页式管理基本概念:Linux操作系统内存管理和虚拟内存概念:为内核开发做一个基础铺垫. 内存是linux内核 ...
- [转帖]Linux分页机制之概述--Linux内存管理(六)
Linux分页机制之概述--Linux内存管理(六) 2016年09月01日 19:46:08 JeanCheng 阅读数:5491 标签: linuxkernel内存管理分页架构更多 个人分类: ┈ ...
- linux内存管理---物理地址、线性地址、虚拟地址、逻辑地址之间的转换
linux内存管理---虚拟地址.逻辑地址.线性地址.物理地址的区别(一) 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步 ...
- linux内存管理---虚拟地址、逻辑地址、线性地址、物理地址的区别(一)
分析linux内存管理机制,离不了上述几个概念,在介绍上述几个概念之前,先从<深入理解linux内核>这本书中摘抄几段关于上述名词的解释: 一.<深入理解linux内核>的解释 ...
- 转 Linux内存管理原理
Linux内存管理原理 在用户态,内核态逻辑地址专指下文说的线性偏移前的地址Linux内核虚拟3.伙伴算法和slab分配器 16个页面RAM因为最大连续内存大小为16个页面 页面最多16个页面,所以1 ...
- 【转帖】linux内存管理原理深入理解段式页式
linux内存管理原理深入理解段式页式 https://blog.csdn.net/h674174380/article/details/75453750 其实一直没弄明白 linux 到底是 段页式 ...
- Linux内存管理和寻址详解
1.概念 内存管理模式 段式:内存分为了多段,每段都是连续的内存,不同的段对应不用的用途.每个段的大小都不是统一的,会导致内存碎片和内存交换效率低的问题. 页式:内存划分为多个内存页进行管理,如在 L ...
随机推荐
- Python之函数、递归、内置函数
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- Thymeleaf模板引擎学习
开发传统Java WEB项目时,我们可以使用JSP页面模板语言,但是在SpringBoot中已经不推荐使用JSP页面进行页面渲染了.从而Thymeleaf提供了一个用于整合Spring MVC的可选模 ...
- 我们通常这样使用Linux弱口令检测!
在Internet环境中,过于简单的口令是服务器面临的最大风险,对于管理员来说,即使找出这些弱口令账号是非常必要的,这样便于采取进一步的安全措施. 这里的话,弱口令检测需要用到一款密码破译软件--Jo ...
- OFDM通信系统的MATLAB仿真(2)
关于OFDM系统的MATLAB仿真实现的第二篇随笔,在第一篇中,我们讨论的是信号经过AWGN信道的情况,只用添加固定噪声功率的高斯白噪声就好了.但在实际无线信道中,信道干扰常常是加性噪声.多径衰落的结 ...
- Django Models随机获取指定数量数据方法
方法一:新增models的Manager方法 下面就直接发代码了 class RandomManager(models.Manager): def get_queryset(self): return ...
- I 2 C、 SPI、 USB驱动架构
根据图12.4, Linux倾向于将主机端的驱动与外设端的驱动分离, 而通过一个核心层将某种总线的协议进行抽象, 外设端的驱动调用核心层API间接过渡到对主机驱动传输函数的调用. 对于I 2 C. S ...
- 【Vue组件通信】props、$ref、$emit,组件传值
1.什么是组件通信 组件间如何通信,也就成为了vue中重点知识,组件通信,涉及到组件之间数据的传递.类似NET POST/GET参数传递. Vue基本的三种传递方式** (props.\(ref.\) ...
- 谁能告诉我如何通过Jenkins完成分布式环境搭建并执行自动化脚本
今天我们接着昨天的内容,看一看如何完成Jenkins分布式环境的搭建和使用,因为我之前也是自己一个人摸索的,如果有不对的地方,请各位看官私信指出. 新增分布式部署节点 在系统管理/节点管理中点击新建 ...
- xctf-pwn hello_pwn
走流程,看看文件类型 64位,开了NX 直接丢IDA分析 查看sub_400686() 是个给flag的函数,可以看到,只要满足if语句的条件使dword_60106C == 1853186401就可 ...
- 常用核心数据库查询sql
一.查询账户信息 -- 查询数据量 /*{"xdb_comment":"1","table":"mb_tran_hist" ...