【UCB操作系统CS162项目】Pintos Lab0:项目上手 (Getting Real)

前言

Stanford 的 CS144 计网完成后让我们继续挑战一项更难的课程项目:UCB 操作系统 CS162 的 Pintos,这个也是多个 CS 顶校都在用的项目。老规矩讲课部分因为本科基本都学过就略过了。

继续安利 CS自学指南,和博主同届甚至就住在楼下的大佬的自学网站,汇总了很多国内外高校 CS 相关的高质量公开课。

项目内容为理解一个微型操作系统 Pintos 的原理并为其添加几方面的重要功能,有自动化测试样例。可以跟着北大操作系统实验班整理的文档做:PintosBook

我的实现(更新至Lab 2):Altair-Alpha/pintos

准备工作

环境配置

跟着实验手册的 Environment Setup 做即可,博主用的是 Docker 部署,未出现问题。开发环境 VSCode + 开两个 PoweShell 窗口一个运行一个调试就很舒服了。

预备知识

  • C 语言基础。
  • 大体过一遍实验手册的 Getting Started 和 Appendix 部分,如果有整块没接触过的知识可以补一补。每个 Lab 中会提示需要详细阅读的部分。
  • 如果没有学过汇编,可以读 PCASM 这本书,不需要记住所有汇编指令的具体细节,大概能看懂程序意思即可。
  • GDB 调试。本课程是一次非常好的学习和锻炼调试程序的机会,本节 Lab 0 主要就是一个利用调试观察系统启动流程的热身,请仔细阅读手册中的 Debugging 部分,也可以参考 C 语言中文网上的 GDB 调试教程

Task 1: Booting Pintos

成功启动 Pintos 即可。下面游戏正式开始。

Task 2: Debugging

Exercise 2.1

  • What is the first instruction that gets executed?
  • At which physical address is this instruction located?

按照 Debugging 部分说明运行 GDB 绑定 Pintos 后得到以下输出,即机器启动运行的第一条指令:

[f000:fff0]    0xffff0: ljmp   $0x3630,$0xf000e05b

该指令为 ljmp(长转移),位于 0xffff0,属于 BIOS 区内,是硬编码的第一条指令位置。

Exercise 2.2

0x7c00 处设置断点并运行到该位置,此时控制权已由 BIOS 移交给 Bootloader,运行的指令与 loader.S 文件相对应。

  • How does the bootloader read disk sectors? In particular, what BIOS interrupt is used?

第 55 行,指令为 call read_sector,调用的函数位于 230 行:

在这里插入图片描述
读取硬盘扇区需要借助 BIOS 提供的功能,具体来说就如题面所述是触发一个 BIOS 中断,该指令位于 242 行(图中红框),维基百科 BIOS interrupt call 条目下有完整的中断表可供查询:

在这里插入图片描述

结合 240 行对 AH = 0x42 的设置可知使用的是 Extended Read Sectors 功能。

  • How does the bootloader decide whether it successfully finds the Pintos kernel?

继续向下看,目前我们已经读取了第一个磁盘扇区的内容,该扇区应该为主引导记录(Master Boot Record, MBR)扇区,包含了磁盘的分区信息,其特征是以 0x55AA 标志位结束(位于 0x01FE - 0x01FF,即 510,511 字节处)。67 行进行该检查,如果不相等,说明当前磁盘未正常分区,跳过并读取下一个磁盘。接下来跳转到 MBR 中第一个分区记录的位置(offset=446),如果读取结果为 0,说明当前分区未使用,使 si+=16 读取下一个分区记录,如果达到结束位置(510)仍未找到有效分区,则跳转至下一个磁盘。然后根据注释使用了值 0x20 来检查是否为 Pintos Kernel,这里我没有在文档中找到相应说明。最后,检查分区记录中第一个字节的值,该值如果为 0x80 则标识着当前分区是 Bootable 的。

在这里插入图片描述
在这里插入图片描述
至此,如果所有检查均通过,则可以确信已经找到了 Pintos Kernel。

  • What happens when the bootloader could not find the Pintos kernel?

接续上一个问题的分析,如果所有分区、所有磁盘均尝试读取后都没有找到 Pintos Kernel 并执行 86 行跳转到 load_kernel 函数,则最终会落入:
在这里插入图片描述
输出 Not found 后,会触发另一个 BIOS 中断 0x18,该中断的作用即 Bootloader 向 BIOS 报告因未找到可引导磁盘导致启动失败。

在这里插入图片描述

  • At what point and how exactly does the bootloader transfer control to the Pintos kernel?

找到 Pintos Kernel 后,Bootloader 从磁盘逐个读取扇区内容,并放在从 0x20000 开始的内存空间。Kernel 以 ELF 格式存储,如文档所述,Kernel 的入口位置非固定编码,而是被保存在其 ELF Header 中的一个指针。该指针位于 0x18,于是第 165 行指令读取该指针放在寄存器 dx 中,又在 166 行将其转存在一个内存位置 start 中(注释中解释了这样做的原因),最终在 168 行执行 ljmp 实际跳转。

在这里插入图片描述
在这里插入图片描述

至此,Bootloader 的使命完成,控制权移交给 Pintos Kernel 的入口。该入口就是 start.S 文件中的汇编代码。这部分代码完成了内存识别、页表建立、GDT 处理以及从 16-bit real mode 到 32-bit protected mode 的转换等工作,对应文档中 Core Guide / Loading / Low-Level Kernel Initialization 部分,这里就不再展开了。最终在 180 行执行 call pintos_init,该函数位于 init.c 中,进入 C 语言代码。

Exercise 2.3

此部分主要练习 GDB 使用,为后续 Lab 打基础。追踪的目标为 init.c 中的一个函数 palloc_get_page() 和一个全局变量 uint32_t *init_page_dir

  • At the entry of pintos_init(), what is the value of the expression init_page_dir[pd_no(ptov(0))] in hexadecimal format?

pintos_init 起始处设置断点,并计算该表达式,结果为 0。

在这里插入图片描述

  • When palloc_get_page() is called for the first time,
    • what does the call stack look like?
    • what is the return value in hexadecimal format?
    • what is the value of expression init_page_dir[pd_no(ptov(0))] in hexadecimal format?

bpalloc_get_page 处设置断点,c 运行至该位置,然后使用 bt 查看调用栈。使用 finish/fin 运行至函数结束并查看返回值是一个 void * 指针。使用 p/x 打印表达式 16 进制计算结果,仍为 0。

在这里插入图片描述

  • When palloc_get_page() is called for the third time,
    - what does the call stack look like?
    - what is the return value in hexadecimal format?
    - what is the value of expression init_page_dir[pd_no(ptov(0))] in hexadecimal format?

继续 c 两次,重复上一步操作得到:

在这里插入图片描述
可以观察到,本次调用与第一次不同,不是在 paging_init 中而是在 thread_start 中被调用。返回的 void * 指针与第一次相差 0x2000。表达式的值为 0x102027

Task 3: Kernel Monitor

Exercise 3.1

在 Lab 0 的最后,我们可以上手在 Pintos 中写入一些自己的代码了。目前,如果在 Pintos 启动的命令行中没有参数,则启动后会自动结束。本节的任务是在这种情况下添加一个可交互的终端,要求如下:

Enhance threads/init.c to implement a tiny kernel monitor in Pintos.
Requirments:

  • It starts with a prompt PKUOS> and waits for user input.
  • As the user types in a printable character, display the character.
  • When a newline is entered, it parses the input and checks if it is whoami. If it is whoami, print your student id.
  • Afterward, the monitor will print the command prompt PKUOS> again in the next line and repeat.
  • If the user input is exit, the monitor will quit to allow the kernel to finish. For the other input, print invalid command. Handling special input such as backspace is not required.
  • If you implement such an enhancement, mention this in your design document.

这里首先要注意,我们是在 Kernel 层级编写代码,所以标准 C 库函数是 不能使用 的,不过课程为我们预先写好了一些与 C 同名的库函数,位于 lib 文件夹下,并且配置好了头文件搜索路径。

代码实现(包含 Backspace 键处理):

int
pintos_init (void)
{
  ...
  if (*argv != NULL) {
    /* Run actions specified on kernel command line. */
    run_actions (argv);
  } else {
    size_t cmd_maxlen = 10;
    char *buf = (char *)malloc(cmd_maxlen); // command line input buffer
    while (true) {
      printf("PKUOS>");
      memset(buf, '\0', cmd_maxlen);
      size_t index = 0;
      while (1) {
        char c = input_getc();
        if (c == 13) { // newline
          printf("\n");
          break;
        } else if (c == 127) { // backspace
          if (index > 0) {
            buf[--index] = '\0';
            printf("\b \b");
          }
          continue;
        }
        if (index >= cmd_maxlen) {
          continue;
        }
        buf[index++] = c;
        if (c > 31) { // printable characters
          printf("%c", c);
        }
      }
      printf("cmd: %s\n", buf);
      if (!strcmp(buf, "whoami")) {
        printf("123456789\n");
      } else if (!strcmp(buf, "exit")) {
        break;
      } else {
        printf("invalid command\n");
      }
    }
    free(buf);
    printf("shell terminated.\n");
  }
  ...
}

Lab 0 至此结束。

  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值