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
2
int test;
int *ptr = &test;

指针变量ptr中存储的到底是什么

猜想

  • 是变量test的地址吗?

  • 除了变量test的地址, 再外加它的类型int吗?

    实证:

    注意: 只有在访存时才有效果

回顾”地址”

汇编语言中

常使用什么地址? 逻辑地址.

  • $逻辑地址 = 段基址:偏移量$
  • $物理地址 = 段基址 \times 10H + 偏移量$

高级计算机体系结构

x86的地址转换机制: 分段+分页

  • 虚拟地址包括逻辑地址和线性地址
  • 分页机制可以绕开, 但分段机制绕不开
  • 程序员能使用的只能是逻辑地址

推理

ptr存储的内容只能与test的逻辑地址有关, 那么段基址只能存放在寄存器, ptr只能存储偏移量

实证

实验1

实验设计

  1. 获取GDTR寄存器的值, 从而获取GDT的基地址
  2. 基于step1中的GDT基地址, 获得GDT表的内容, 进而找出各个段寄存器对应的表项, 即段描述符, 进一步获得段描述符的基地址、段界限等信息
  3. 编写用户程序, 获得其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
    43
    root@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

    说明

    1. MEM_PRINT宏打印的结果: 左边是低地址, 右边是高地址; 上面是低地址, 下面是高地址

    2. x86平台的字节序(小端序, 即数据的高字节保存在内存的高地址): 高高低低

    3. 人类阅读的习惯: 左边是高字节, 右边是低字节

    • GDTR寄存器的值: FF A0 10 00 00 FF, 可得:
      • GDT的基地址: 0xffa010000
      • GDT范围: $[0, \text{0xff}]$, 即$[0,256)$ (单位: Byte). 由于一个段描述符占8B, 从而共 $256 \div 8=32$ 项
  • 用户程序输出

    1
    2
    3
    cs: 0x73
    ds: 0x7b
    ss: 0x7b

    以CS寄存器为例, CS的值为0x73, 其对应的二进制为 0111 0011, 可得:

    • index: 01110b=14
    • 判断是LDT还是GDT表: 0b, 即GDT表
    • 运行级别: 11b = 3, 即用户态

根据上述的分析结果, 可得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
2
3
4
5
6
7
8
9
10
11
graph TB

view1[view 1] --> model1[model 1]
view2[view 2] --> model2[model 2]
view3[...] --> model3[...]
view_n[view n] --> model4[model n]

model1 --> ensemble[ensemble althorithm] --> output
model2 --> ensemble[ensemble althorithm]
model3 --> ensemble[ensemble althorithm]
model4 --> ensemble[ensemble althorithm]
1
2
3
4
5
6
7
8
9
10
11
12
13
graph TB
f:load_direct_gdt --> f:load_gdt --> f:pv_cpu_ops.load_gdt --> f:native_load_gdt --> s:lgdt

f:load_direct_gdt --> f:get_cpu_gdt_rw --> f:per_cpu
f:get_cpu_gdt_rw --> v:gdt_page --> a:gdt
v:gdt_page --> m:GDT_ENTRIES

st:gdt_page --> v:gdt_page
st:gdt_page --> st:desc_struct --> a:gdt

m:DEFINE_PER_CPU_PAGE_ALIGNED --> v:gdt_page
m:DEFINE_PER_CPU_PAGE_ALIGNED --> m:GDT_ENTRY_DEFAULT_USER_CS
m:DEFINE_PER_CPU_PAGE_ALIGNED --> m:GDT_ENTRY_INIT

说明
f: 函数
s: 汇编
a: 数组
p: 指针
v: 变量
st: 结构体
m: 宏