操作系统 ucore lab1
实验目的
操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,我们的目的在于了解到:
计算机原理
- CPU的编址与寻址: 基于分段机制的内存管理
- CPU的中断机制
- 外设:串口/并口/CGA,时钟,硬盘
Bootloader软件
- 编译运行bootloader的过程
- 调试bootloader的方法
- PC启动bootloader的过程
- ELF执行文件的格式和加载
- 外设访问:读硬盘,在CGA上显示字符串
ucore OS软件
- 编译运行ucore OS的过程
- ucore OS的启动过程
- 调试ucore OS的方法
- 函数调用关系:在汇编级了解函数调用栈的结构和处理过程
- 中断管理:与软件相关的中断处理
- 外设管理:时钟
实验步骤
练习1 理解通过make生成执行文件的过程
问题1:
- 操作系统镜像文件ucore.img是如何一步一步生成的?
实验过程:
- 先进入文件路径:
home/moocos/ucore_lab/labcodes_answer/lab1_result/
- 执行make:
make “V=”
- 观察过程
(1)通过GCC编译器将Kernel目录下的.c文件编译成OBJ目录下的.o文件。
+ cc kern/init/init.c //编译init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
+ cc kern/libs/readline.c //编译readline.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
+ cc kern/libs/stdio.c //编译stdlio.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
+ cc kern/debug/kdebug.c //编译kdebug.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
+ cc kern/debug/kmonitor.c //编译komnitor.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
+ cc kern/debug/panic.c //编译panic.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
+ cc kern/driver/clock.c //编译clock.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c //编译console.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/intr.c //编译intr.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/driver/picirq.c //编译prcirq.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/trap/trap.c //编译trap.c
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/trapentry.S //编译trapentry.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/trap/vectors.S //编译vectors.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/mm/pmm.c //编译pmm.c
gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
+ cc libs/printfmt.c //编译printfmt.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/printfmt.c -o obj/libs/printfmt.o
+ cc libs/string.c //编译string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.o
(2)ld命令根据链接脚本文件kernel.ld将生成的*.o文件,链接成BIN目录下的kernel文件。
+ ld bin/kernel //链接成kernel
ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o obj/libs/printfmt.o obj/libs/string.o
(3)通过GCC编译器将boot目录下的.c,.S文件以及tools目录下的sign.c文件编译成OBJ目录下的*.o文件。
+ cc boot/bootasm.S //编译bootasm.S
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c //编译bootmain.c
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c //编译sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
(4)ld命令将生成的*.o文件,链接成BIN目录下的bootblock文件。
+ ld bin/bootblock //根据sign规范生成bootblock
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 488 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
(5)dd命令将dev/zero, bin/bootblock,bin/kernel 写入到bin/ucore.img
//创建大小为10000个块的ucore.img,初始化为0,每个块为512字节
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0242234 s, 211 MB/s
//把bootblock中的内容写到第一个块
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.0001916 s, 2.7 MB/s
//从第二个块开始写kernel中的内容
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74923 bytes (75 kB) copied, 0.000380543 s, 197 MB/s
问题2:
- 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
实验过程:
- 大小为512字节
- 多余的空间填0
- 第510个(倒数第二个)字节是0x55,
- 第511个(倒数第一个)字节是0xAA。
练习2
练习2.1 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行
(1)进入~/moocos/ucore_lab/labcodes/lab1/bin,即ucore.img所在文件夹。
(2)输入指令:
qemu -S -s -hda ucore.img -monitor stdio
(3)打开gdb。
(4)输入命令,使gdb与qemu通过1234端口进行通信,qemu会停止状态听从gbd命令。
target remote 127.0.0.1:1234
(5)输入命令,单步跟踪。
si
(6)查看BIOS代码
x/2i $pc
练习2.2 在初始化位置0x7c00 设置实地址断点,测试断点正常
(1)输入命令,设置断点。
b *0x7c00
(2)continue后,测试断点。
c
x/2i $pc
练习2.3 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较
(1)单步跟踪。
输入两次si。
(2)使用meld对比bootasm.S和bootlock.asm的代码
meld /home/moocos/moocos/ucore_lab/labcodes/lab1/boot/bootasm.S /home/moocos/moocos/ucore_lab/labcodes/lab1/obj/bootblock.asm
是相同的。
练习2.4 自己找一个bootloader或内核中的代码位置,设置断点并进行测试
(1)查看bootloader,选择0x7c06位置。
b *0x7c06
c
x/2i $pc
练习3:分析bootloader进入保护模式的过程
关中断和清除数据段寄存器
.globl start
start:
.code16 # 使用16位模式编译
cli # 禁用中断
cld # 清除方向标志
# 建立重要的数据段寄存器(DS,ES,SS)。
xorw %ax, %ax # ax清0
movw %ax, %ds # ds清0
movw %ax, %es # es清0
movw %ax, %ss # ss清0
为何开启A20,以及如何开启A20
seta20.1: # 等待8042输入缓冲区空
inb $0x64, %al # 从0x64端口中读入一个字节到al中
testb $0x2, %al # 测试al的第2位
jnz seta20.1 # al的第2位为0,则跳出循环
movb $0xd1, %al # 将0xd1写入al中
outb %al, $0x64 # 将0xd1写入到0x64端口中
seta20.2: # 等待8042输入缓冲区空
inb $0x64, %al # 从0x64端口中读入一个字节到al中
testb $0x2, %al # 测试al的第2位
jnz seta20.2 # al的第2位为0,则跳出循环
movb $0xdf, %al # 将0xdf入al中
outb %al, $0x60 # 将0xdf入到0x64端口中,打开A20
如何初始化GDT表
- 载入GDT表
lgdt gdtdesc # 载入GDT表
- 进入保护模式
cro的第0位为1表示处于保护模式
movl %cr0, %eax # 加载cro到eax
orl $CR0_PE_ON, %eax # 将eax的第0位置为1
movl %eax, %cr0 # 将cr0的第0位置为1
- 通过长跳转更新cs的基地址
ljmp $PROT_MODE_CSEG, $protcseg # $PROT_MODE_CSEG的值为0x80
- 设置段寄存器,并建立堆栈
.code32 # 使用32位模式编译
protcseg:
movw $PROT_MODE_DSEG, %ax # ax赋0x80
movw %ax, %ds # ds赋0x80
movw %ax, %es # es赋0x80
movw %ax, %fs # fs赋0x80
movw %ax, %gs # gs赋0x80
movw %ax, %ss # ss赋0x80
movl $0x0, %ebp # 设置帧指针
movl $start, %esp # 设置栈指针
- 转到保护模式完成,进入boot主方法。
call bootmain
如何使能和进入保护模式
- 将cr0寄存器置1,cro的第0位为1表示处于保护模式。
练习4:分析bootloader加载ELF格式的OS的过程
bootloader如何读取硬盘扇区
- 等待磁盘准备好;
- 发出读取扇区的命令;
- 等待磁盘准备好;
- 把磁盘扇区数据读到指定内存。
bootmain.c:
#include <defs.h>
#include <x86.h>
#include <elf.h>
/* *********************************************************************
* This a dirt simple boot loader, whose sole job is to boot
* an ELF kernel image from the first IDE hard disk.
*
* DISK LAYOUT
* * This program(bootasm.S and bootmain.c) is the bootloader.
* It should be stored in the first sector of the disk.
*
* * The 2nd sector onward holds the kernel image.
*
* * The kernel image must be in ELF format.
*
* BOOT UP STEPS
* * when the CPU boots it loads the BIOS into memory and executes it
*
* * the BIOS intializes devices, sets of the interrupt routines, and
* reads the first sector of the boot device(e.g., hard-drive)
* into memory and jumps to it.
*
* * Assuming this boot loader is stored in the first sector of the
* hard-drive, this code takes over...
*
* * control starts in bootasm.S -- which sets up protected mode,
* and a stack so C code then run, then calls bootmain()
*
* * bootmain() in this file takes over, reads in the kernel and jumps to it.
* */
/* waitdisk - wait for disk ready */
static void
waitdisk(void) { //如果0x1F7的最高2位是01,跳出循环
while ((inb(0x1F7) & 0xC0) != 0x40)
/* do nothing */;
}
/* 读节 - 将“secno”处的单个扇区读入“dst” */
static void
readsect(void *dst, uint32_t secno) {
// 等待磁盘准备就绪
waitdisk();
// 用LBA模式的PIO(Program IO)方式来访问硬盘
outb(0x1F2, 1); //读取一个扇区
outb(0x1F3, secno & 0xFF); //要读取的扇区编号
outb(0x1F4, (secno >> 8) & 0xFF); //用来存放读写柱面的低8位字节
outb(0x1F5, (secno >> 16) & 0xFF); //用来存放读写柱面的高2位字节
outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); // 用来存放要读/写的磁盘号及磁头号
outb(0x1F7, 0x20); // cmd 0x20-读为扇区
// 等待磁盘准备就绪
waitdisk();
// 读一个扇区
insl(0x1F0, dst, SECTSIZE / 4); //获取数据
}
/* *
* readseg - read @count bytes at @offset from kernel into virtual address @va,might copy more than asked.
* */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
uintptr_t end_va = va + count;
// 转至分区边界
va -= offset % SECTSIZE;
// 从字节转换到扇区;内核从扇区1开始
uint32_t secno = (offset / SECTSIZE) + 1; //加1因为0扇区被引导占用
// 如果速度太慢,我们一次就能读到很多扇区。
// 我们会写更多的记忆,而不是要求,但这并不重要--我们以增序加载。
for (; va < end_va; va += SECTSIZE, secno ++) {
readsect((void *)va, secno);
}
}
bootloader如何加载ELF格式的OS
- 从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用。
- 校验e_magic字段。
- 根据偏移量分别把程序段的数据读取到内存中。
ElF结构定义
struct elfhdr {
uint32_t e_magic; // 判断读出来的ELF格式的文件是否为正确的格式
uint8_t e_elf[12];
uint16_t e_type; // 1=可重定位,2=可执行,3=共享对象,4=核心映像
uint16_t e_machine; // 3=x86,4=68K等.
uint32_t e_version; // 文件版本,总是1
uint32_t e_entry; // 程序入口所对应的虚拟地址。
uint32_t e_phoff; // 程序头表的位置偏移
uint32_t e_shoff; // 区段标题或0的文件位置
uint32_t e_flags; // 特定于体系结构的标志,通常为0
uint16_t e_ehsize; // 这个elf头的大小
uint16_t e_phentsize; // 程序头中条目的大小
uint16_t e_phnum; // 程序头表中的入口数目
uint16_t e_shentsize; // 节标题中条目的大小
uint16_t e_shnum; // 节标题中的条目数或0
uint16_t e_shstrndx; // 包含节名称字符串的节号。
};
bootmain.c:
#define SECTSIZE 512
#define ELFHDR ((struct elfhdr *)0x10000)
/* 引导-引导加载器的条目 */
void
bootmain(void) {
// 从磁盘上读取第一页
readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
// 这是有效的ELF吗?
if (ELFHDR->e_magic != ELF_MAGIC) { // 通过储存在头部的幻数判断是否是合法的ELF文件
goto bad;
}
struct proghdr *ph, *eph;
// 加载每个程序段(忽略ph标志)
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); // 先将描述表的头地址存在ph
eph = ph + ELFHDR->e_phnum;
// 按照描述表将ELF文件中数据载入内存
for (; ph < eph; ph ++) {
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}// ELF文件0x1000位置后面的0xd1ec比特被载入内存0x00100000,ELF文件0xf000位置后面的0x1d20比特被载入内存0x0010e000
// 从ELF报头调用入口点
// 注:不返回
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
/* do nothing */
while (1);
}
练习5:实现函数调用堆栈跟踪函数
栈结构:
ebp表示上一层。
eip表示第一个局部变量。
实现函数:
print_stackframe(void) {
int i,j;
uint32_t ebp=read_ebp();
uint32_t eip=read_eip();
for(i=0;i<STACKFRAME_DEPTH&&ebp!=0;i++){
cprintf("ebp:0x%08x eip:0x%08x\n",ebp,eip);
uint32_t *args=(uint32_t *)ebp+2;
cprintf("参数:");
for(j=0;j<4;j++){
cprintf("0x%08x ", args[j]);
}
cprintf("\n");
print_debuginfo(eip-1);
eip=((uint32_t *)ebp)[1];
ebp=((uint32_t *)ebp)[0];
}
}
直接涉及函数:
- kdebug.c中read_eip():
read_eip(void) {
uint32_t eip;
asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); //内联汇编,读取(ebp-4)的值到变量eip
return eip;
}
- x86.h中read_ebp() :
static inline uint32_t
read_ebp(void) {
uint32_t ebp;
asm volatile ("movl %%ebp, %0" : "=r" (ebp)); //内联汇编,读取edp寄存器的值到变量ebp
return ebp;
}
- kdebug.c中print_debuginfo(uintptr_t eip):
void
print_debuginfo(uintptr_t eip) {
struct eipdebuginfo info;
if (debuginfo_eip(eip, &info) != 0) {
cprintf(" <unknow>: -- 0x%08x --\n", eip);
}
else {
char fnname[256];
int j;
for (j = 0; j < info.eip_fn_namelen; j ++) {
fnname[j] = info.eip_fn_name[j];
}
fnname[j] = '\0';
cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line,
fnname, eip - info.eip_fn_addr);
}
}
执行make qemu:
Special kernel symbols:
entry 0x00100000 (phys)
etext 0x001032cf (phys)
edata 0x0010ea16 (phys)
end 0x0010fd20 (phys)
Kernel executable memory footprint: 64KB
ebp:0x00007b08 eip:0x001009a6
参数:0x00010094 0x00000000 0x00007b38 0x00100092
kern/debug/kdebug.c:307: print_stackframe+21
ebp:0x00007b18 eip:0x00100ca1
参数:0x00000000 0x00000000 0x00000000 0x00007b88
kern/debug/kmonitor.c:125: mon_backtrace+10
ebp:0x00007b38 eip:0x00100092
参数:0x00000000 0x00007b60 0xffff0000 0x00007b64
kern/init/init.c:48: grade_backtrace2+33
ebp:0x00007b58 eip:0x001000bb
参数:0x00000000 0xffff0000 0x00007b84 0x00000029
kern/init/init.c:53: grade_backtrace1+38
ebp:0x00007b78 eip:0x001000d9
参数:0x00000000 0x00100000 0xffff0000 0x0000001d
kern/init/init.c:58: grade_backtrace0+23
ebp:0x00007b98 eip:0x001000fe
参数:0x001032fc 0x001032e0 0x0000130a 0x00000000
kern/init/init.c:63: grade_backtrace+34
ebp:0x00007bc8 eip:0x00100055
参数:0x00000000 0x00000000 0x00000000 0x00010094
kern/init/init.c:28: kern_init+84
ebp:0x00007bf8 eip:0x00007d68
参数:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8
<unknow>: -- 0x00007d67 --
++ setup timer interrupts
(THU.CST) os is loading ...
输出从顶向下,实际执行顺序相反,从bootmain开始一步步调用函数,info.eip_file、info.eip_line和fnname分别表示调用发生的文件、调用发生所在行和调用函数名。
练习6:完善中断初始化和处理 (需要编程)
中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
向量表结构:
由各项相加可知,一个表项的占64bit,即8字节,其中0 ~ 15位和48 ~ 63位分别为偏移量的低16位和高16位,两者拼接为偏移量,16~31位为段选择器。
- 使用段选择符中的偏移值在GDT(全局描述符表) 或 LDT(局部描述符表)中定位相应的段描述符。
- 利用段描述符校验段的访问权限和范围,以确保该段是可以访问的并且偏移量位于段界限内。
- 利用段描述符中取得的段基地址加上偏移量,形成一个线性地址。
请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
- mmu.h中的SETGATE(gate,istrap,sel,off,dpl):
- lidt指令用于把内存中的限长值和基地址操作数加载到IDTR寄存器中。
- idt_init()实现:
- make debug一下子:
请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数
- 结构体trapframe:
struct trapframe {
struct pushregs tf_regs;
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
- trap.c中的print_ticks():
static void print_ticks() {
cprintf("%d ticks\n",TICK_NUM);
#ifdef DEBUG_GRADEvolatile size_t ticks;
cprintf("End of Test.\n");
panic("EOT: kernel seems ok.");
#endif
}
- clock.h中的ticks:
volatile size_t ticks;
- trap.c中的TICK_NUM:
#define TICK_NUM 100
- 对trap_dispatch(struct trapframe *tf)的补充:
- make qemu一下子:
扩展练习 Challenge 1(需要编程)
扩展proj4,增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务(通过网络查询所需信息,可找老师咨询。如果完成,且有兴趣做代替考试的实验,可找老师商量)。需写出详细的设计和分析报告。完成出色的可获得适当加分。
提示: 规范一下 challenge 的流程。
kern_init 调用 switch_test,该函数如下:
static void
switch_test(void) {
print_cur_status(); // print 当前 cs/ss/ds 等寄存器状态
cprintf("+++ switch to user mode +++\n");
switch_to_user(); // switch to user mode
print_cur_status();
cprintf("+++ switch to kernel mode +++\n");
switch_to_kernel(); // switch to kernel mode
print_cur_status();
}
switchto* 函数建议通过 中断处理的方式实现。主要要完成的代码是在 trap 里面处理 T_SWITCH_TO* 中断,并设置好返回的状态。
在 lab1 里面完成代码以后,执行 make grade 应该能够评测结果是否正确。
扩展练习 Challenge 2(需要编程)
用键盘实现用户模式内核模式切换。具体目标是:“键盘输入3时切换到用户模式,键盘输入0时切换到内核模式”。 基本思路是借鉴软中断(syscall功能)的代码,并且把trap.c中软中断处理的设置语句拿过来。
注意:
1.关于调试工具,不建议用lab1_print_cur_status()来显示,要注意到寄存器的值要在中断完成后tranentry.S里面iret结束的时候才写回,所以再trap.c里面不好观察,建议用print_trapframe(tf)
2.关于内联汇编,最开始调试的时候,参数容易出现错误,可能的错误代码如下
asm volatile ( “sub $0x8, %%esp \n”
“int %0 \n”
“movl %%ebp, %%esp”
: )
要去掉参数int %0 \n这一行
3.软中断是利用了临时栈来处理的,所以有压栈和出栈的汇编语句。硬件中断本身就在内核态了,直接处理就可以了。
参考答案在mooc_os_lab中的mooc_os_2014 branch中的labcodes_answer/lab1_result目录下