NOTICE
- 有"w2l2"标记的题是助教要提交到学堂在线上的。
- 有"w2l2"和"spoc"标记的题是要求拿清华学分的同学要在实体课上完成,并按时提交到学生对应的git repo上。
- 有"hard"标记的题有一定难度,鼓励实现。
- 有"easy"标记的题很容易实现,鼓励实现。
- 有"midd"标记的题是一般水平,鼓励实现。
请描述ucore OS配置和驱动外设时钟的准备工作包括哪些步骤? (w2l2)
+ 采分点:说明了ucore OS在让外设时钟正常工作的主要准备工作
- 答案没有涉及如下3点;(0分)
- 描述了对IDT的初始化,包了针时钟中断的中断描述符的设置(1分)
- 除第二点外,进一步描述了对8259中断控制器的初始过程(2分)
- 除上述两点外,进一步描述了对8253时钟外设的初始化,或描述了对EFLAG操作使能中断(3分)
- 在
idt_init()
函数中,用SETGATE宏初始化中断向量表,然后利用汇编命令lidt
加载IDT pic_init()
函数实现了8259中断器的初始化.clock_init()
函数实现了对时钟的初始化,主要是通过汇编命令out
对I/O端口的写入完成配置.- 最后通过汇编命令
sti
指令开启中断.
lab1中完成了对哪些外设的访问? (w2l2)
+ 采分点:说明了ucore OS访问的外设
- 答案没有涉及如下3点;(0分)
- 说明了时钟(1分)
- 除第二点外,进一步说明了串口(2分)
- 除上述两点外,进一步说明了并口,或说明了CGA,或说明了键盘(3分)
在
clock.c
、driver.c
和console.c
的文件中有对以下外设的访问:
- IRQ_TIMER 时钟外设
- IRQ_KBD 键盘
- IRQ_COM1 串口
- IRQ_IDE1 IDE口1
- IRQ_IDE2 IDE口2
lab1中的cprintf函数最终通过哪些外设完成了对字符串的输出? (w2l2)
+ 采分点:说明了cprintf函数用到的3个外设
- 答案没有涉及如下3点;(0分)
- 说明了串口(1分)
- 除第二点外,进一步说明了并口(2分)
- 除上述两点外,进一步说明了CGA(3分)
通过分析
cprintf
函数的调用链,发现其关键函数为cons_putc()
函数,其实现如下:
void cons_putc(int c) {
lpt_putc(c);
cga_putc(c);
serial_putc(c);
}
这些函数分别是利用并口、命令行和串口进行输出。并口和串口是将待输出的字符串写到相应的端口,cga_putc()
则维护了一个buffer,然后对屏幕进行刷新。
lab1中printfmt函数用到了可变参,请参考写一个小的linux应用程序,完成实现定义和调用一个可变参数的函数。(spoc)
以下代码实现了一个可变参数的加法(由同组的梁盾同学完成代码的编写)
#include "stdio.h"
#include "stddef.h"
#define my_add(args...) va_add(args, -1)
int va_add(int a, ...) {
va_list ap;
va_start(ap, a);
while (1) {
int k = va_arg(ap, int);
if (k==-1) break;
a += k;
}
va_end(ap);
return a;
}
int main(int argc, char const *argv[]) {
printf("1+2 = %d\n", my_add(1,2));
printf("1+2+3 = %d\n", my_add(1,2,3));
return 0;
}
如果让你来一个阶段一个阶段地从零开始完整实现lab1(不是现在的填空考方式),你的实现步骤是什么?(比如先实现一个可显示字符串的bootloader(描述一下要实现的关键步骤和需要注意的事项),再实现一个可加载ELF格式文件的bootloader(再描述一下进一步要实现的关键步骤和需要注意的事项)...) (spoc)
首先是一个可以输出
hello world
的bootloader
, 为了做到这一点, 首先要配置 GDT, 进入保护模式, 然后是 学习 CGA 的使用方法.
在能够拥有一个可以运行的bootloader以后, 接下来需要能够加载 kernel 到内存里, 因此需要学习 kernel 镜像的访问 以及加载到内存的方法, 通过学习 ELF 的结构和调用方法可以做到这一点.
接下来是在 kernel 中处理各种中断, 首先是配置在中断控制器, 然后是配置 IDT, 一起将各种外设配置成功, 最后打开中断, 让所有中断能够成功调用对应的中断处理程序.
如何能获取一个系统调用的调用次数信息?如何可以获取所有系统调用的调用次数信息?请简要说明可能的思路。(spoc)
一个是在编译的时候实现这种功能, 通过添加编译选项, 当程序在调用 对应的系统调用的代码的时候, 添加代码进行统计, 这个方法的优点是不需要动 kernel 的代码, 但是缺点 是为了测试系统调用的次数, 需要重新编译对应的代码, 而且因为在用户态执行的代码有可能会因为用户态 其他的错误代码干扰导致测试结果错误.
第二种实现方式是kernel自行统计, 通过观察 ucore 的代码, 我们可以发现所有系统调用都是 经过
syscall
函数的, 并且被 中断分派程序 调用, 我们可以通过在这些地方插入统计代码进行统计, 这个方法的优点是不需要修改已经编译好的程序, 但是缺点是需要修改 kernel, 并且可能会降低系统 调用的速度
如何修改lab1, 实现一个可显示字符串"THU LAB1"且依然能够正确加载ucore OS的bootloader?如果不能完成实现,请说明理由。
- [x]
对于ucore_lab中的labcodes/lab1,我们知道如果在qemu中执行,可能会出现各种稀奇古怪的问题,比如reboot,死机,黑屏等等。请通过qemu的分析功能来动态分析并回答lab1是如何执行并最终为什么会出现这种情况?
- [x]
对于ucore_lab中的labcodes/lab1,如果出现了reboot,死机,黑屏等现象,请思考设计有效的调试方法来分析常在现背后的原因。
- [x]
如何修改lab1, 实现在出现除零错误异常时显示一个字符串的异常服务例程的lab1?
- [x]
在lab1/bin目录下,通过objcopy -O binary kernel kernel.bin
可以把elf格式的ucore kernel转变成体积更小巧的binary格式的ucore kernel。为此,需要如何修改lab1的bootloader, 能够实现正确加载binary格式的ucore OS? (hard)
- [x]
GRUB是一个通用的bootloader,被用于加载多种操作系统。如果放弃lab1的bootloader,采用GRUB来加载ucore OS,请问需要如何修改lab1, 能够实现此需求? (hard)
- [x]
如果没有中断,操作系统设计会有哪些问题或困难?在这种情况下,能否完成对外设驱动和对进程的切换等操作系统核心功能?
- [x]
读入ucore内核的代码?
- [x]
跳转到ucore内核的代码?
- [x]
全局描述符表的初始化代码?
- [x]
GDT内容的设置格式?初始映射的基址和长度?特权级的设置位置?
- [x]
可执行文件格式elf的各个段的数据结构?
- [x]
如果ucore内核的elf是否要求连续存放?为什么?
- [x]
函数调用的stackframe结构?函数调用的参数传递方法有哪几种?
- [x]
系统调用的stackframe结构?系统调用的参数传递方法有哪几种?
- [x]
使用内联汇编的原因?
- [x]
特权指令、性能优化
对ucore中的一段内联汇编进行完整的解释?
- [x]
4.4 x86中断处理过程
中断描述符表IDT的结构?
- [x]
中断描述表到中断服务例程的地址计算过程?
- [x]
中断处理中硬件压栈内容?用户态中断和内核态中断的硬件压栈有什么不同?
- [x]
中断处理中硬件保存了哪些寄存器?
- [x]
gcc编译、ld链接和dd生成两个映像对应的makefile脚本行?
- [x]
qemu的命令行参数含义解释?
- [x]
gdb命令格式?反汇编、运行、断点设置
- [x]
A20的使能代码分析?
- [x]
生成主引导扇区的过程分析?
- [x]
保护模式的切换代码?
- [x]
如何识别elf格式?对应代码分析?
- [x]
跳转到elf的代码?
- [x]
函数调用栈获取?
- [x]
如何识别elf格式?对应代码分析?
- [x]
跳转到elf的代码?
- [x]
函数调用栈获取?
- [x]
各种设备的中断初始化?
- [x]
中断描述符表IDT的排列顺序?
- [x]
中断号 CPU加电初始化后中断是使能的吗?为什么?
- [x]
中断服务例程的入口地址在什么地方设置的?
- [x]
alltrap的中断号是在哪写入到trapframe结构中的?
- [x]
trapframe结构?
- [x]