211. 分段管理机制
基础知识
数据类型大小
数据类型 | 16位操作系统(byte) | 32位操作系统(byte) | 64位操作系统(byte) | 取值范围 |
---|---|---|---|---|
char |
1 | 1 | 1 | -128 ~ 127 |
unsigned char |
1 | 1 | 1 | 0 ~ 255 |
short int / short |
2 | 2 | 2 | -32768~32767 |
unsigned short |
2 | 2 | 2 | 0 ~ 65535 |
int |
2 | 4 | 4 | -2147483648~2157483647 |
unsigned int |
2 | 4 | 4 | 0~4294967295 |
long int / long |
4 | 4 | 8 | -2147483648~2147483647 |
unsigned long |
4 | 4 | 8 | 0~42294967295 |
long long int / long long |
8 | 8 | 8 | -9223372036854775808 ~9223372036854775807 |
double |
8 | 8 | 8 | 1.7E+10的负308次⽅~1.7E+10的正308次⽅ |
float |
4 | 4 | 4 | 3.4E+10的负38次⽅~3.4E+10的38次⽅ |
long double |
10/12 | 10/16 | 有效位10字节。 32位为了对齐实际分配12字节;64位分配16字节 |
|
指针 |
2 | 4 | 8 | / |
问题1:指针变量存储的是什么
问题
1 | int test; |
指针变量ptr中存储的到底是什么
猜想
是变量test的地址吗?
除了变量test的地址, 再外加它的类型int吗?
实证:
注意: 只有在访存时才有效果
回顾”地址”
汇编语言中
常使用什么地址? 逻辑地址.
- $逻辑地址 = 段基址:偏移量$
- $物理地址 = 段基址 \times 10H + 偏移量$
高级计算机体系结构
x86的地址转换机制: 分段+分页

- 虚拟地址包括逻辑地址和线性地址
- 分页机制可以绕开, 但分段机制绕不开
- 程序员能使用的只能是逻辑地址
推理
ptr存储的内容只能与test的逻辑地址有关, 那么段基址只能存放在寄存器, ptr只能存储偏移量
实证
实验1
实验设计
- 获取GDTR寄存器的值, 从而获取GDT的基地址
- 基于step1中的GDT基地址, 获得GDT表的内容, 进而找出各个段寄存器对应的表项, 即段描述符, 进一步获得段描述符的基地址、段界限等信息
- 编写用户程序, 获得其CS、DS和SS, 从而弄清使用的是GDT还是LDT, 以及其对应的index
结果分析
内核模块输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43root@ubuntu16-32:/home/qeuroal/kernel_module_practice/3/1_read_gdtr/PrintingDriver# dmesg
[36345.598974] msg_printer Initialize
[36350.026692] msg_printer: open invoked, do nothing
[36350.026721] msg_printer: write invoked, do nothing
[36350.026724] msg_printer: cs=60
[36350.026727] msg_printer: ds=7b
[36350.026729] msg_printer: ss=68
[36350.026730] msg_printer gdtr:
[36350.026735] eecc9f12: FF 00 00 10 A0 FF
[36350.026736] msg_printer gdt:
[36350.026778] ffa01000: 00 00 00 00 00 00 00 00
[36350.026784] ffa01008: 00 00 00 00 00 00 00 00
[36350.026788] ffa01010: 00 00 00 00 00 00 00 00
[36350.026791] ffa01018: 00 00 00 00 00 00 00 00
[36350.026795] ffa01020: 00 00 00 00 00 00 00 00
[36350.026798] ffa01028: 00 00 00 00 00 00 00 00
[36350.026801] ffa01030: FF FF 00 B7 BB F3 DF B7
[36350.026805] ffa01038: 00 00 00 00 00 00 00 00
[36350.026808] ffa01040: 00 00 00 00 00 00 00 00
[36350.026811] ffa01048: 00 00 00 00 00 00 00 00
[36350.026815] ffa01050: 00 00 00 00 00 00 00 00
[36350.026818] ffa01058: 00 00 00 00 00 00 00 00
[36350.026821] ffa01060: FF FF 00 00 00 9B CF 00
[36350.026824] ffa01068: FF FF 00 00 00 93 CF 00
[36350.026827] ffa01070: FF FF 00 00 00 FB CF 00
[36350.026830] ffa01078: FF FF 00 00 00 F3 CF 00
[36350.026833] ffa01080: 6B 20 00 30 A0 8B 00 FF
[36350.026837] ffa01088: 00 00 00 00 00 00 00 00
[36350.026840] ffa01090: FF FF 00 00 00 9A 40 00
[36350.026843] ffa01098: FF FF 00 00 00 9A 00 00
[36350.026846] ffa010a0: FF FF 00 00 00 92 00 00
[36350.026849] ffa010a8: 00 00 00 00 00 92 00 00
[36350.026852] ffa010b0: 00 00 00 00 00 92 00 00
[36350.026855] ffa010b8: FF FF 00 00 00 9A 40 00
[36350.026858] ffa010c0: FF FF 00 00 00 9A 00 00
[36350.026861] ffa010c8: FF FF 00 00 00 92 40 00
[36350.026864] ffa010d0: FF FF 00 00 00 92 CF 00
[36350.026867] ffa010d8: FF FF 00 B0 F6 93 8F 26
[36350.026870] ffa010e0: 18 00 00 DB D7 91 40 F1
[36350.026873] ffa010e8: 00 00 00 00 00 00 00 00
[36350.026877] ffa010f0: 00 00 00 00 00 00 00 00
[36350.026880] ffa010f8: 6B 20 00 60 C1 89 00 CA
[36350.026886] msg_printer: close invoked, do nothing说明
MEM_PRINT
宏打印的结果: 左边是低地址, 右边是高地址; 上面是低地址, 下面是高地址x86平台的字节序(小端序, 即数据的高字节保存在内存的高地址): 高高低低
人类阅读的习惯: 左边是高字节, 右边是低字节
- GDTR寄存器的值:
FF A0 10 00 00 FF
, 可得:- GDT的基地址:
0xffa010000
- GDT范围: $[0, \text{0xff}]$, 即$[0,256)$ (单位: Byte). 由于一个段描述符占8B, 从而共 $256 \div 8=32$ 项
- GDT的基地址:
用户程序输出
1
2
3cs: 0x73
ds: 0x7b
ss: 0x7b以CS寄存器为例, CS的值为0x73, 其对应的二进制为
0111 0011
, 可得:- index:
01110b=14
- 判断是LDT还是GDT表:
0b
, 即GDT表 - 运行级别:
11b = 3
, 即用户态
- index:
根据上述的分析结果, 可得CS段寄存器对应的段描述符信息如下:
- 段描述符所在地址: $base_{GDT} + index \times 8 = \text{0xffa01000} + 14 \times 8 = \text{0xffa01070}$ ==>
ffa01070: FF FF 00 00 00 FB CF 00
- 段描述符的值:
00 CF FB 00 00 00 FF FF
段描述符的字段信息如下:

对段描述符进行解析:(值为16进制)
- 段limit[0, 16): FF FF
- 段基址[0, 16): 00 00
- 段基址[16, 24): 00
- 段limit[16, 20): F
- 段粒度(G位, 位于高4字节的第23位): 1, 可知段粒度为4KB
- 段基址[24, 32): 00
整理结果:
段基址: 00 00 00 00
段界限(段limit): F FF FF
由于段粒度为4KB, 而段界限为0xFFFFF, 段最大为 $\text{0xFFFFF} \times 4KB = 2^{20} \times 2 ^ 2 KB = 2^{22}KB = 4GB$, 故段界限为4GB. 因此, 正好可以寻址完32位的地址空间(4GB).
分析结果:
32位机中, Linux将应用程序的代码段范围设为了 $[0, \text{0xff ff ff ff}]$(单位为字节)
注意: 用户态DS和SS, 内核态CS、DS和SS同样也是如此
32位机寻址范围通常也是 $[0, \text{0xff ff ff ff}]$(单位为字节), 因此全局只有一个段, 等同于没有分段
由于 $线性地址=段基址+偏移量$; $段基址=0$, 从而 $偏移量=线性地址$
偏移量等于线性地址
- 只是数值上相等
- 物理意义(语义)上是不一样的: 偏移量归属于逻辑地址, 而线性地址归属于线性地址
- 由于$段基址=0$, 相当与直接使用线性地址, 但是在概念上仍使用逻辑地址(线性地址是用不了的), 只是数值上等同于线性地址
int *ptr = &test;
中, ptr在数值上既存储了偏移量, 也存储了线性地址
内核管理gdt表
1 | graph TB |
1 | graph TB |
说明
f: 函数
s: 汇编
a: 数组
p: 指针
v: 变量
st: 结构体
m: 宏