实验课程: 操作系统
实验名称: Lab3 从实模式到保护模式
1. 实验要求
任务 1
1.1
复现Example 1,说说你是怎么做的并提供结果截图,也可以参考Ucore、Xv6等系统源码,实现自己的LBA方式的磁盘访问。
提示:部分需要的文件存放在src/example-1下,请根据需要将其放置于自己创建的lab3文件夹下。
1.2
在Example1中,我们使用了LBA28的方式来读取硬盘。此时,我们只要给出逻辑扇区号即可,但需要手动去读取I/O端口。然而,BIOS提供了实模式下读取硬盘的中断,其不需要关心具体的I/O端口,只需要给出逻辑扇区号对应的磁头(Heads)、扇区(Sectors)和柱面(Cylinder)即可,又被称为CHS模式。现在,同学们需要将LBA28读取硬盘的方式换成CHS读取,同时给出逻辑扇区号向CHS的转换公式。最后说说你是怎么做的并提供结果截图。
参数 | 数值 |
---|---|
驱动器号(DL寄存器) | 80h |
每磁道扇区数 | 63 |
每柱面磁头数(每柱面总的磁道数) | 18 |
任务 2
复现Example 2,使用gdb或其他debug工具在进入保护模式的4个重要步骤上设置断点,并结合代码、寄存器的内容等来分析这4个步骤,最后附上结果截图。gdb的使用可以参考appendix的“debug with gdb and qemu”部份。
提示:部分需要的文件存放在src/example-2下,请根据需要将其放置于自己创建的lab3文件夹下。
任务 3
改造“Lab2-Assignment 4”为32位代码,即在保护模式后执行自定义的汇编程序。
2. 实验过程
任务 1
1.1
编写bootloader.asm和mbr.asm,在 mbr.asm
处放入使用LBA模式读取硬盘的代码,并加载bootloader到地址0x7e00,输出Hello World部份的代码放入到bootloader.asm中,具体代码内容见附件
然后我们编译 bootloader.asm
,写入硬盘起始编号为1的扇区,共有5个扇区。
1 | nasm -f bin bootloader.asm -o bootloader.bin |
mbr.asm
也要重新编译和写入硬盘起始编号为0的扇区。
1 | nasm -f bin mbr.asm -o mbr.bin |
使用qemu运行即可,
1 | qemu-system-i386 -hda hd.img -serial null -parallel stdio |
1.2
CHS和LBA的转换
磁头数为硬盘磁头的总数,扇区数为每磁道的扇区数
1 | 逻辑编号(即LBA地址)=(柱面编号×磁头数+磁头编号)×扇区数+扇区编号-1 |
例如 LBA = 0 则 CHS = 0/0/1
经过计算,S = 2, C =0 , H = 0
在这里我们改写load_bootloader:和asm_read_hard_disk:
接下来的步骤与1.1相同
具体代码见关键代码展示部分
任务 2
创建img:
1 | qemu-img create hd.img 10m |
首先我们编译bootloader.asm,写入硬盘起始编号为1的扇区,共有5个扇区。
1 | nasm -f bin bootloader.asm -o bootloader.bin |
mbr.asm也要重新编译和写入硬盘起始编号为0的扇区。
1 | nasm -f bin mbr.asm -o mbr.bin |
使用qemu运行即可,观察结果
1 | qemu-system-i386 -hda hd.img -serial null -parallel stdio |
下面进行调试
生成符号表
我们首先删除mbr.asm和bootloader.asm的org语句,因为我们会在链接的过程中指定他们代码和数据的起始地址,其效果和org指令完全相同。
我们编译mbr.asm,生成可重定位文件mbr.o。其中,-g参数是为了加上debug信息。
1 | nasm -o mbr.o -g -f elf32 mbr.asm |
然后我们为可重定位文件mbr.o指定起始地址0x7c00,分别链接生成可执行文件mbr.symbol和mbr.bin
1 | ld -o mbr.symbol -melf_i386 -N mbr.o -Ttext 0x7c00 |
对于bootloader.asm,我们执行上述类似的操作。
1 | nasm -o bootloader.o -g -f elf32 bootloader.asm |
然后将mbr.bin和bootloader.bin分别写入hd.img,写入的位置是lab2-Example 2中指定的位置。
1 | dd if=mbr.bin of=hd.img bs=512 count=1 seek=0 conv=notrunc |
debug基本流程
写好makefile和gdbinit文件后
直接在命令行输入
1 | make build |
在下面的位置设置第一个断点:
我们在内存中使用一个48位的变量pgdt来表示GDTR的内容。
然后把GDT的信息写入变量pgdt,把pgdt的内容加载进GDTR。即这两句
1 | ;初始化描述符表寄存器GDTR |
设置断点 b bootloader.asm:35(即在mov word[pgdt], 39)一行
加载GDTR信息后查看
如x/3uh 0x54320从地址0x54320开始,读取3个双字节(h),以无符号十六进制方式显示(u)
第二个断点位置:
当我们想进入保护模式时,首先需要打开第 21 根地址线。第21根地址线的开关位于南桥芯片的端口A20,使用 in,out 指令可以对主板端口进行读、写操作。代码如下。
1 | in al, 0x92 ; 南桥芯片内的端口 |
在or语句设置断点,查看ax的第二位是否变为1
第三个断点位置
保护模式的真正开关——CR0。CR0 是 32 位的寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位,其第0位是保护模式的开关位,称为PE(protect mode enable)位。 PE置1,CPU 进入保护模式
1 | cli ; 保护模式下中断机制尚未建立,应禁止中断 |
在or语句处设置断点观察第0位变化
第四个断点位置
1 | jmp dword CODE_SELECTOR:protect_mode_begin |
解决“dbus-launch“ (No such file or directory)的问题
任务 3
对lab2assignment4中的代码进行修改。使之能在32位模式保护模式下运行
因为保护模式下禁止了中断,不能通过int 10h方式输出字符,我们需要按照lab2任务1的方式进行输出。
在bootloader进入保护模式部分后加入代码
3. 关键代码
任务1.2中读取磁盘代码:
1 | load_bootloader: |
任务2中gdbinit内容
1 | target remote:1234 |
makefile
1 | run: |
4. 实验结果
任务 1
1.1
1.2
使用CHS读取硬盘并加上了姓名学号
任务2
- 准备GDT,用lgdt指令加载GDTR信息 。(pgdt为39)
2. 打开第21根地址线 。(al第2位为1)
3. 开启cr0的保护模式标志位 。(ax第0位变为1)
4. 远跳转,进入保护模式
5.最后进入保护模式
任务3
5. 总结
遇到的问题及改进意见
在任务2中gdbinit文档中bootloader.symbol的地址应为0x7e00而不是0x7c00
写好makefile文件后在命令行make debug报错
“Failed to execute child process “dbus-launch”
“
Error constructing proxy for :1.288:/org/gnome/Terminal/Factory0:
连接已关闭Failed to use specified server:
连接已关闭Falling back to default server.Error constructing proxy for org.gnome.Terminal:/org/gnome/Terminal/Factory0: 连接已关闭
make: [makefile:6:debug] 错误 1
“
分别使用
sudo apt install dbus-x11
sudo make debug后解决了问题
实验总结
在这次实验中,我学习了LBA,CHS两种读写硬盘的方法,在实验中我还学习了实模式和保护模式的区别,从实模式跳转进入到保护模式、gdb的使用等内容。最后实现了移植大于512字节的程序。