THU OS Lab1

操作系统实验1

练习1

1. 操作系统镜像文件ucore.img是如何一步一步生成的

  • 先生成kernel和bootblock,然后生成ucore.img(需要研究下MakeFile的写法,兴趣不大)

2. 一个被系统认为是符合规范的硬盘主引导扇区的特点

  • 只有512个字节
  • 最后两字节是0x55AA
  • 由不超过466字节的启动代码和不超过64字节的硬盘分区表加上结束符构成

练习2

先记录一下在eclipse中用gdb和qemu调试的方法:

  1. 配置External Tools Configurations,其中的arguments可以指定make的参数,然后参考MakeFile确定
  2. 配置Debug Configurations,其中的Commands栏可以指定gdb的指令序列
  3. 先启动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;