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 位:
0x10c0f300Base [31:24]:0x10(位 24-31)G:取决于具体的位,例如1D/B:取决于具体的位,例如1P:1(位 47),表示段存在。DPL:11(位 45-46),即 3。S:1(位 44),表示这是一个代码或数据段。Type:C0部分决定,例如1010(可写数据段)。
- 低 32 位:
0x00003fffBase [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 |
- 实验结果










