【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
- 作用
- 利用段机制隔离用户数据和系统数据
- 保留段的等级保护机制
- 简化(避免)逻辑地址到线性地址转换
- 可以直接将虚拟地址当作线性地址,二者完全一致
- 利用段机制隔离用户数据和系统数据
最新文章
- JDBC查询数据库中的数据
- 《PHP开发APP接口》笔记
- ASCII值对照表
- poj 2823 Sliding Window (单调队列入门)
- 坑爹的VS2012
- .NET设计模式(15):结构型模式专题总结(转)
- JAVA多线程学习2--线程同步
- Hibernate逍遥游记-第7章 Hibernate的检索策略和检索方式(<;set lazy=";false"; fetch=";join";>;、left join fetch、FetchMode.JOIN、)
- 实验一:基于Winsock完成简单的网络程序开发
- UESTC_邱老师的脑残粉 2015 UESTC Training for Graph Theory<;Problem D>;
- Hive 伪分布式下安装
- OVS 总体架构、源码结构及数据流程全面解析
- C和C指针小记(十七)-使用结构和指针-链表
- pyculiarity 时间序列(异常流量)异常检测初探——感觉还可以,和Facebook的fbprophet本质上一样
- jQuery.cookie的使用指南
- 【译】GNU Radio How to write a block 【如何开发用户模块及编写功能块】
- js的函数作用域跟块级作用域
- Ubuntu安装新版本nodejs的5种姿势
- 解决TextBox Ctrl+A不能全选的问题
- Android逆向之旅---静态方式分析破解视频编辑应用「Vue」水印问题