os-homework-7-地址映射与共享

os-homework-7-地址映射与共享
风铃夜行该实验报告基本按照参考文献,但改正了一些笔误/错误并按照我实际运行结果修改了部分值。
实验目的
- 深入理解操作系统的段、页式内存管理,深入理解段表、页表、逻辑地址、线性地址、物理地址等概念;
- 实践段、页式内存管理的地址映射过程;
- 编程实现段、页式内存管理上的内存共享,从而深入理解操作系统的内存管理。
实验任务
- 用 Bochs 调试工具跟踪 Linux 0.11 的地址翻译(地址映射)过程,了解 IA-32 和 Linux 0.11 的内存管理机制;
- 在 Ubuntu 上编写多进程的生产者—消费者程序,用共享内存做缓冲区;
- 在信号量实验的基础上,为 Linux 0.11 增加共享内存功能,并将生产者—消费者程序移植到 Linux 0.11。
文件名 | 介绍 |
---|---|
hit-操作系统实验指导书.pdf | 哈工大OS实验指导书 |
Linux内核完全注释(修正版v3.0).pdf | 赵博士对Linux v0.11 OS进行了详细全面的注释和说明 |
file1615.pdf | BIOS 涉及的中断数据手册 |
hit-oslab-linux-20110823.tar.gz | hit-oslab 实验环境 |
gcc-3.4-ubuntu.tar.gz | Linux v0.11 所使用的编译器 |
Bochs 汇编级调试指令 | bochs 基本调试指令大全 |
最全ASCII码对照表0-255 | 屏幕输出字符对照的 ASCII 码 |
x86_64 常用寄存器大全 | x86_64 常用寄存器大全 |
实验内容
IA-32 的地址翻译过程
- 启动调试器
- 将
test.c
测试程序拷贝到 linux0.11 系统下
1 | cd oslab_Lab6 |
- 编译并运行
test.c
linux-0.11
系统下编译运行test.c
1 | gcc -o test test.c |
- 暂停
- 在命令行窗口按
Ctrl+c
,Bochs
会暂停运行态。绝大多数情况下都会停在test
内。 - 其中的
000f
如果是0008
,则说明中断在了内核里。那么就要 c,然后再Ctrl+c
,直到变为000f
为止。 - 如果显示的下一条指令不是
cmp
…(这里指语句以cmp
开头),就用n
命令单步运行几步,直到停在cmp
。
- 使用命令
u /8
,显示从当前位置开始8
条指令的反汇编代码。 - 这就是
test.c
中从while
开始一直到return
的汇编代码。变量i
保存在ds:0x3004
这个地址,并不停地和0
进行比较,直到它为0
,才会跳出循环。
- 使用
sreg
指令查段表,计算 DS 线性地址
ds:0x3004
是虚拟地址,应用程序都有一个段表,叫 LDT。ldtr
寄存器表示 LDT表存放在 GDT 表的位置。
0x0068 = 0000000001101000
,则存放在 GDT 表的 1101(二进制)=13(十进制)号位置
逻辑地址由段选择子(Segment Selector)和段内偏移(Offset)组成。段选择子是一个 16 位的值,它并不直接是内存地址,而是用于在描述符表中查找对应的段描述符。
位数范围 | 字段名称 | 功能说明 |
---|---|---|
15-3 | 索引 (Index) | 在 GDT 或 LDT 中查找段描述符的索引号。由于每个描述符占 8 字节,所以实际地址是 索引 * 8 。 |
2 | TI (Table Indicator) | 表指示器。TI = 0 表示在全局描述符表 (GDT) 中查找;TI = 1 表示在局部描述符表 (LDT) 中查找。 |
1-0 | RPL (Requester Privilege Level) | 请求特权级。表示当前段的请求者特权级,用于特权级检查。数值 00(最高特权级)到 11(最低特权级)。 |
- 在 GDT 表中查找 LDT 表
- GDT 表中的每一项占 64 位(8 个字节),所以我们要查找的项的地址是
0x00005cb8+13*8
。xp 是用于查看内存内容的调试指令,w表示显示两个字(b-BYTE, h-WORD, w-DWORD, g-DWORD64) xp /2w 0x00005cb8+13*8
此时可以发现,和前面sreg指令中ldtr后面的dl和dh值一样(这是Bochs 调试器自动计算出的)
- 利用 GDT 表中描述符计算 LDT 表线性地址
- 描述符为“0x52d00068 0x000082fd”,-> 0x 00 fd 52d0 (前面32位对应下面行,后面32位对应上面行)
段描述符结构:
段描述符是一个 64 位(8 字节)的数据结构,存储着段的详细信息。它决定了段的基地址(Base Address)、限长(Limit)以及各种属性(Attributes)。
位数范围 | 字段名称 | 功能说明 |
---|---|---|
63-56 | Base | 段基地址的最高 8 位。 |
55 | G (Granularity) | 粒度位。G = 0 表示限长以字节为单位;G = 1 表示限长以 4KB 为单位。 |
54-52 | D/B / L (Default Operation Size / Long Mode) | 操作数大小/长模式位。对于代码段和数据段,D = 1 表示 32 位操作,D = 0 表示 16 位操作。在 64 位模式下,L=1 表示 64 位代码段。 |
51-48 | AVL (Available for System Software) | 系统软件可用位。 |
47 | P (Present) | 存在位。P = 1 表示段在内存中;P = 0 表示段不在内存中,会引发缺页中断。 |
46-45 | DPL (Descriptor Privilege Level) | 描述符特权级。段的特权级,数值 00(最高)到 11(最低)。 |
44 | S (Descriptor Type) | 描述符类型。S = 1 表示代码或数据段描述符;S = 0 表示系统段描述符(如 LDT、TSS)。 |
43-40 | Type | 段类型。详细描述段的类型和权限(如可读、可写、可执行、向上/向下扩展等)。 |
39-16 | Base [23:0] | 段基地址的低 24 位。 |
15-0 | Limit | 段限长的低 16 位。 |
- 根据该地址,找到了 LDT 表位置。
- xp /8w 0x00fd52d0
- 段寄存器(段选择子) DS 从 LDT 表获取段描述符
sreg
指令获取 DS 寄存器内容
1 | <bochs:8> sreg |
逻辑地址由段选择子(Segment Selector)和段内偏移(Offset)组成。段选择子是一个 16 位的值,它并不直接是内存地址,而是用于在描述符表中查找对应的段描述符。
位数范围 | 字段名称 | 功能说明 |
---|---|---|
15-3 | 索引 (Index) | 在 GDT 或 LDT 中查找段描述符的索引号。由于每个描述符占 8 字节,所以实际地址是 索引 * 8 。 |
2 | TI (Table Indicator) | 表指示器。TI = 0 表示在全局描述符表 (GDT) 中查找;TI = 1 表示在局部描述符表 (LDT) 中查找。 |
1-0 | RPL (Requester Privilege Level) | 请求特权级。表示当前段的请求者特权级,用于特权级检查。数值 00(最高特权级)到 11(最低特权级)。 |
例如,在实验中,DS 寄存器的值为 0x0017
。0x0017
的二进制表示为 0000 0000 0001 0111
。
- 索引:
0000 0000 00010
(位 3-15) =00010
(二进制) =2
(十进制)。这表示 DS 段描述符在 LDT 中是第 2 项(从 0 开始计数,即第三个描述符)。 - TI:
1
(位 2)。这表示 DS 段描述符在 LDT 中查找。 - RPL:
11
(位 0-1) =3
(十进制)。表示请求特权级为 3(用户态)。
RPL:即如果 RPL 的数值大于 CPL(数值越大,权限越小),则用 RPL 的值覆盖 CPL 的值,即 CPR, RPL ≤ DPL
TI:TI=0,在 GDT 表中查; TI = 1,在 LDT 表中查。
- ds = 0x0017 = 0001 0111,即RPL = 11, TI = 1,索引 = 10,即在 LDT 表中索引为 2 的描述符(即第三个),即
0x00003fff 0x10c0f300
为搜寻很久的 DS 段描述符了。此时可以发现,和前面
sreg
指令中ds
后面的dl
和dh
值一样(这是Bochs 调试器自动计算出的)
根据段选择子ldtr在GDT表中找到了LDT表中的段描述符,计算线性地址,该线性地址其实bochs调试器在ldtr寄存器后面的dh、dl中已经计算好了,可以供我们验证;
根据段选择子DS在LDT表中找到DS的段描述符,计算线性地址,该线性地址其实bochs调试器在DS寄存器后面的dh、dl中已经计算好了,可以供我们验证。
- 利用 LDT 表中段描述符计算 DS 线性地址(段基址)
- 由段描述符
0x00003fff 0x10c0f300
可以计算得到段基址为0x 10 00 0000
,所以ds:0x3004
的线性地址为0x10003004
。
段描述符是一个 64 位(8 字节)的数据结构,存储着段的详细信息。它决定了段的基地址(Base Address)、限长(Limit)以及各种属性(Attributes)。
位数范围 | 字段名称 | 功能说明 |
---|---|---|
63-56 | Base | 段基地址的最高 8 位。 |
55 | G (Granularity) | 粒度位。G = 0 表示限长以字节为单位;G = 1 表示限长以 4KB 为单位。 |
54-52 | D/B / L (Default Operation Size / Long Mode) | 操作数大小/长模式位。对于代码段和数据段,D = 1 表示 32 位操作,D = 0 表示 16 位操作。在 64 位模式下,L=1 表示 64 位代码段。 |
51-48 | AVL (Available for System Software) | 系统软件可用位。 |
47 | P (Present) | 存在位。P = 1 表示段在内存中;P = 0 表示段不在内存中,会引发缺页中断。 |
46-45 | DPL (Descriptor Privilege Level) | 描述符特权级。段的特权级,数值 00(最高)到 11(最低)。 |
44 | S (Descriptor Type) | 描述符类型。S = 1 表示代码或数据段描述符;S = 0 表示系统段描述符(如 LDT、TSS)。 |
43-40 | Type | 段类型。详细描述段的类型和权限(如可读、可写、可执行、向上/向下扩展等)。 |
39-16 | Base [23:0] | 段基地址的低 24 位。 |
15-0 | Limit | 段限长的低 16 位。 |
在实验中,DS 的段描述符为 0x00003fff 0x10c0f300
。我们将其拆分为高 32 位和低 32 位进行分析:
- 高 32 位:
0x10c0f300
Base [31:24]
:0x10
(位 24-31)G
:取决于具体的位,例如1
D/B
:取决于具体的位,例如1
P
:1
(位 47),表示段存在。DPL
:11
(位 45-46),即 3。S
:1
(位 44),表示这是一个代码或数据段。Type
:C0
部分决定,例如1010
(可写数据段)。
- 低 32 位:
0x00003fff
Base [15:0]
:0x0000
(位 0-15)Limit [15:0]
:0x3fff
(位 0-15)
将段基地址的三个部分 (Base [31:24]
, Base [23:16]
, Base [15:0]
) 组合起来,可以得到完整的 32 位段基地址。
例如,0x00003fff 0x10c0f300
经过组合,其段基地址为 0x10000000
。
加上段内偏移 0x3004
,最终计算得到的线性地址为 0x10000000 + 0x3004 = 0x10003004
。
- 查页表,由线性地址计算物理地址
页目录号(10位)、页表号(10位)和页内偏移(12位)
- 线性地址
0x10003004
:页目录号 =64
, 页号 =3
, 页内偏移 =4
线性地址结构:
位数范围 | 字段名称 | 功能说明 |
---|---|---|
31-22 | 页目录号 (Page Directory Index) | 用于在页目录表中查找对应的页目录项 (PDE)。 |
21-12 | 页表号 (Page Table Index) | 用于在页目录项指向的页表中查找对应的页表项 (PTE)。 |
11-0 | 页内偏移 (Offset within Page) | 在最终物理页内部的偏移量,表示数据在该页中的具体位置。 |
例如,线性地址 0x10003004
的二进制表示为 0001 0000 0000 0000 0011 0000 0000 0100
。
- 页目录号:
0001 0000 00
(位 22-31) =01000000
(二进制) =64
(十进制)。 - 页表号:
00 0000 0011
(位 12-21) =0000000011
(二进制) =3
(十进制)。 - 页内偏移:
0000 0000 0100
(位 0-11) =0x004
(十六进制)。
- 页目录表的位置由 CR3 寄存器指引,
creg
命令可以查看:
- 说明页目录表的基址为 0
- 页目录表和页表中的内容很简单,是 1024 (2^10)个 32 位数,表大小刚好为4K。用以下指令可以在页目录表中查找到页目录项
1 | xp /w 0+64*4 |
页目录项 (PDE) 和 页表项 (PTE) 结构:
页目录项和页表项都是 32 位(4 字节)的数据结构,它们指向下一级页表或最终的物理页。
位数范围 | 字段名称 | 功能说明 |
---|---|---|
31-12 | 页框地址 (Page Frame Address) | 指向 4KB 对齐的页表或物理页的起始物理地址。 |
11 | <br> (Reserved) | 保留位,Linux 0.11 中通常为 0。 |
10-9 | Avail (Available for System Software) | 系统软件可用位。 |
8 | G (Global) | 全局位。G = 1 表示该页不进行 TLB 冲刷,用于内核等全局页。 |
7 | PS (Page Size) | 页大小位。PS = 1 表示 4MB 大页;PS = 0 表示 4KB 小页。在 Linux 0.11 中通常为 0 (4KB 页)。 |
6 | D (Dirty) | 脏位。D = 1 表示页被写入过;D = 0 表示页未被写入。 |
5 | A (Accessed) | 访问位。A = 1 表示页被访问过(读或写)。 |
4 | PCD (Page Cache Disable) | 页缓存禁止。PCD = 1 禁止该页的缓存。 |
3 | PWT (Page Write-Through) | 页写穿。PWT = 1 启用写穿缓存策略。 |
2 | U/S (User/Supervisor) | 用户/管理特权级。U/S = 1 允许所有特权级访问;U/S = 0 只允许特权级 0-2 访问。 |
1 | R/W (Read/Write) | 读/写权限。R/W = 1 允许读写;R/W = 0 只允许读。 |
0 | P (Present) | 存在位。P = 1 表示页在内存中;P = 0 表示页不在内存中,会引发缺页中断。 |
查找页目录表:
CR3
寄存器存储页目录表的物理基地址,在实验中显示为0x00000000
。- 通过
xp /w 0 + 64 * 4
,我们查找页目录表中索引为 64 的页目录项。结果是0x00fa5027
。 - 从这个页目录项中,提取页表物理地址:
0x00fa5000
(高 20 位)。0x27
是属性位(P=1
,R/W=1
,U/S=0
等)。
查找页表:
- 页表物理地址是
0x00fa5000
。 - 通过
xp /w 0x00fa5000 + 3 * 4
,我们查找页表中索引为 3 的页表项。结果是0x00fa3067
。 - 从这个页表项中,提取物理页框号:
0x00fa3000
(高 20 位)。同样,0x67
是属性位。
- 页表物理地址是
计算物理地址:
- 将物理页框号
0x00fa3000
与页内偏移0x004
相加,最终得到变量i
的物理地址:0x00fa3000 + 0x004 = 0x00fa3004
。
- 将物理页框号
可以通过两种方式验证:
基于线性地址 0x10003004
,使用 page
指令:
1 | page 0x10003004 |
基于物理地址 0x00fa3004
,使用 xp
指令查看地址中存放的一个字节:
1 | xp /w 0x00fa3004 |
- 修改内存来改变 i 的值,使程序退出循环
- 将从
0x00fa3004
地址开始的 4 个字节都设为 0
1 | setpmem 0x00fa3004 4 0 |
- 然后再用
c
命令继续 Bochs 的运行,可以看到 test 退出了,说明 i 的修改成功了,此项实验结束。
在 Linux 0.11 中实现共享内存
本次实验将在 Lab6 信号量的实现和应用
实验的基础上,使用共享内存替换文件缓冲区,来完成生产者和消费者两个程序。
Linux 中,将不同进程的虚拟地址空间通过页表映射到物理内存的同一区域,实现共享内存。
- 新建文件kernel/shm.c,实现共享内存
1 |
|
brk
为代码段和数据段的总长度,brk + start_code
即为代码段+数据段结束的位置;start_stack
为栈的起始地址。- 因此,将物理内存映射到虚拟内存处,
brk
和start_stack
之间的空间为栈准备,栈底是闲置的(栈是往低地址方向扩展的),可将共享内存映射到这块空间。
- 修改文件
include/unistd.h
,新增全局函数
1 |
- 修改
/kernel/system_call.s
,需要修改总的系统调用数
1 | nr_system_calls = 78 |
- 修改
/include/linux/sys.h
,声明全局新增函数
1 | /* 实验6 */ |
- 修改
linux-0.11/kernel/Makefile
,添加sem.c
编译规则
1 | OBJS = sched.o system_call.o traps.o asm.o fork.o \ |
- 实现生产者程序
producer.c
和 消费者程序consumer.c
为了确保对共享内存操作的互斥,仍需要使用一个信号量在每次读写的时候进行限制;同理,还需要两个信号量来保证共享内存中缓冲区大小为 10
producer.c
1 |
|
consumer.c
1 |
|
在linux0.11环境下编译运行
- 将已经修改的
consumer.c
、producer.c
和unistd.h
、sem.h
文件拷贝到linux-0.11
系统中
1 | cd oslab_Lab6 |
- 编译及运行Bochs
1 | cd oslab_Lab6/linux-0.11 |
- 在linux0.11的Bochs中编译运行生产者-消费者程序
1 | gcc -o producer producer.c |
- 在Ubuntu中挂载
hdc
,将linux0.11输出的文件移动到Ubuntu中
1 | sudo ./mount-hdc |
- 实验结果