操作系统实验1
练习1
1. 操作系统镜像文件ucore.img是如何一步一步生成的
- 先生成kernel和bootblock,然后生成ucore.img(需要研究下MakeFile的写法,兴趣不大)
2. 一个被系统认为是符合规范的硬盘主引导扇区的特点
- 只有512个字节
- 最后两字节是
0x55AA
- 由不超过466字节的启动代码和不超过64字节的硬盘分区表加上结束符构成
练习2
先记录一下在eclipse中用gdb和qemu调试的方法:
- 配置External Tools Configurations,其中的arguments可以指定make的参数,然后参考MakeFile确定
- 配置Debug Configurations,其中的Commands栏可以指定gdb的指令序列
- 先启动qemu,让其进入等待状态(通过MakeFile),然后启动gdb,让其跟qemu建立连接,比如使用如下的参数:
set architecture i8086 target remote :1234
如果不用eclipse,可以直接写一个文件保存gdb的运行指令,然后在MakeFile中加一个选项指向这个文件(具体参考其他已有的选项)
1. 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行
- 使用上述的两条指令启动gdb就可以达到效果,根据讲课的视频知道第一条指令在
0xffff0
处,可以使用如下指令x /2i 0xffff0
查看相应的bios代码
2. 在初始化位置0x7c00设置实地址断点,测试断点正常
- 加上
b 0x7c00
即可
3. 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较
- 在eclipse调试器中可以看到相应的汇编代码
4. 自己找一个bootloader或内核中的代码位置,设置断点并进行调试
- 自由发挥
练习 3
分析bootloader进入保护模式的过程
- 查看bootasm.S源码可知,首先要使能A20总线,然后初始化GDT表(在bootasm.S中),最后把cr0寄存器置1从而使能保护模式
练习4
1. bootloader如何读取硬盘扇区
- readsect函数
2. bootloader如何加载ELF格式的OS
- bootmain函数,遍历所有的program header段依次读取,然后调用entry point
练习5
完成print_stackframe
的实现
- 先看实验指导书了解调用栈的结构,然后根据注释可以很轻松的写出实现。这里主要是对C语言不熟所以不会将32位整数转成地址去读数据,参考了答案写出来的
void print_stackframe(void) { uint32_t ebp = read_ebp(); uint32_t eip = read_eip(); uint32_t i; for (i = 0; i < STACKFRAME_DEPTH; ++i) { cprintf("ebp:0x%08x eip:0x%08x ", ebp, eip); uint32_t* args = (uint32_t*)(ebp + 8); // 通过强转成指针来把整数当作地址 cprintf("args:0x%08x 0x%08x 0x%08x 0x%08x", args[0], args[1], args[2], args[3]); // 直接通过索引实现对地址的顺序访问 cprintf("\n"); print_debuginfo(eip-1); eip = ((uint32_t*)(ebp + 4))[0]; // 同理,转成指针并用索引来读地址中的值。记住返回地址存放在ebp的上面4位的地址中 ebp = ((uint32_t*)ebp)[0]; // 上一个ebp存放在ebp对应的地址中 } }
练习6
1. 中断描述符表中一个表项占多少字节?哪几位代表中断处理代码的入口?
- 8字节。可以在
mmu.h
中看到gatedesc
的定义,其中第0~15位是偏移量的低16位,第16~31位是段选择子,第48~63位是偏移量的高16位。通过段选择子从全局描述符表找到相应的段基址,然后通过偏移量找到中断例程的入口
2. 完成idt_init
函数
- 有些存疑的地方,主要是对第三个参数GD_KTEXT不太理解,目前先理解为因为
vectors
中的中断服务例程都放在了内核的代码段,所以所有的段选择子都填这个void idt_init(void) { extern uintptr_t __vectors[]; int i; for (int i = 0; i < 256; ++i) { SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL); // idt项,是否为系统调用,段选择子,偏移量,特权级别 } SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER); // 从用户切到内核是用户特权级 lidt(&idt_pd); }
3. 完成trap
函数
- 只完成了时钟中断
case IRQ_OFFSET + IRQ_TIMER: ticks++; if (ticks % TICK_NUM == 0) { print_ticks(); } break;