ucore lab1实验报告
实验步骤
练习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.ok
+ 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
一个被系统认为是符合规范的硬盘主引导扇区的特征有以下几点:
1、磁盘主引导扇区只有512字节
2、磁盘最后两个字节为0x55AA
3、由不超过466字节的启动代码和不超过64字节的硬盘分区表加上两个字节的结束符组成
练习二:使用qemu执行并调试lab1中的软件
内容:
1、从 CPU加电后执行的第一条指令开始,单步跟踪 BIOS的执行
2、在初始化位置 0x7c00 设置实地址断点,测试断点正常
3、从 0x7c00 开始跟踪代码运行,将单步跟踪反汇编得到的代码与 bootasm.S和 bootblock.asm进行比较
4、自己找一个 bootloader或内核中的代码位置,设置断点并进行测试
首先通过make qemu指令运行出等待调试的qemu虚拟机,然后再打开一个终端,通过下述命令连接到qemu虚拟机:
首先通过make lab1-mon指令运行出等待调试的qemu虚拟机,并同时打开一个gdb,执行代码为:
+ cc kern/init/init.c
+ cc kern/libs/readline.c
+ cc kern/libs/stdio.c
+ cc kern/debug/kdebug.c
+ cc kern/debug/kmonitor.c
+ cc kern/debug/panic.c
+ cc kern/driver/clock.c
+ cc kern/driver/console.c
+ cc kern/driver/intr.c
+ cc kern/driver/picirq.c
+ cc kern/trap/trap.c
+ cc kern/trap/trapentry.S
+ cc kern/trap/vectors.S
+ cc kern/mm/pmm.c
+ cc libs/printfmt.c
+ cc libs/string.c
+ ld bin/kernel
+ cc boot/bootasm.S
+ cc boot/bootmain.c
+ cc tools/sign.c
+ ld bin/bootblock
'obj/bootblock.out' size: 488 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0586492 s, 87.3 MB/s
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000103106 s, 5.0 MB/s
146+1 records in
146+1 records out
74923 bytes (75 kB) copied, 0.0004648 s, 161 MB/s
可以看到qemu的运行状态:
以及gdb中运行停在了0x7c00处:
0x0000fff0 in ?? ()
warning: A handler for the OS ABI "GNU/Linux" is not built into this configuration
of GDB. Attempting to continue with the default i8086 settings.
The target architecture is assumed to be i8086
Breakpoint 1 at 0x7c00
Breakpoint 1, 0x00007c00 in ?? ()
=> 0x7c00: cli
0x7c01: cld
(gdb)
在gdb中显示当前的十条指令,并让qemu继续运行,命令是:
x /10i $pc
continue
可以看到qemu己经开始运行了,并且已经到了ucore。
之后待qemu运行完毕,退出:
quit
对比此时bootasm.S中的起始代码,发现确实是一样的。
练习3:分析bootloader进入保护模式的过程
1、关中断和清除数据段寄存器
.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
2、为何开启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
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
3、如何初始化GDT表
(1)载入GDT表
lgdt gdtdesc # 载入GDT表
(2)进入保护模式,cro的第0位为1表示处于保护模式。
movl %cr0, %eax # 加载cro到eax
orl $CR0_PE_ON, %eax # 将eax的第0位置为1
movl %eax, %cr0 # 将cr0的第0位置为1
(3)通过长跳转更新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
练习4:分析bootloader加载ELF格式的OS的过程
bootloader如何读取硬盘扇区
1、等待磁盘准备好;
2、发出读取扇区的命令;
3、等待磁盘准备好;
4、把磁盘扇区数据读到指定内存。
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
1、从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用。
2、校验e_magic字段。
3、根据偏移量分别把程序段的数据读取到内存中。
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:实现函数调用堆栈跟踪函数(需要编程)
可以通过read_ebp()和read_eip()函数来获取当前ebp寄存器和eip 寄存器的信息。
然后通过ebp+12,ebp+16,ebp+20,ebp+24来输出4个参数的值,最后更新ebp:ebp=ebp[0],更新eip:eip=ebp[1]。直到ebp 对应地址的值为0(表示当前函数为bootmain)。
read_eip() 函数定义在kdebug.c中:
read_eip(void) {
uint32_t eip;
asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); //内联汇编,读取(ebp-4)的值到变量eip
return eip;
}
read_ebp() 函数定义在x86.h中:
static inline uint32_t
read_ebp(void) {
uint32_t ebp;
asm volatile ("movl %%ebp, %0" : "=r" (ebp)); //内联汇编,读取edp寄存器的值到变量ebp
return ebp;
}
实现函数如下:
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];
}
}
执行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.c中的bootmain。(因为此时ebp对应地址的值为0)
bootloader设置的堆栈从0x7c00开始,使用”call bootmain”转入bootmain函数。
call指令压栈,所以bootmain中ebp为0x7bf8。
练习6:完善中断初始化和处理(需要编程)
中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
向量表结构:
/* Gate descriptors for interrupts and traps */
struct gatedesc {
unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment
unsigned gd_ss : 16; // segment selector
unsigned gd_args : 5; // # args, 0 for interrupt/trap gates
unsigned gd_rsv1 : 3; // reserved(should be zero I guess)
unsigned gd_type : 4; // type(STS_{TG,IG32,TG32})
unsigned gd_s : 1; // must be 0 (system)
unsigned gd_dpl : 2; // descriptor(meaning new) privilege level
unsigned gd_p : 1; // Present
unsigned gd_off_31_16 : 16; // high bits of offset in segment
};
由各项相加可知,一个表项的占64bit,即8字节,其中0 ~ 15位和48 ~ 63位分别为偏移量的低16位和高16位,两者拼接为偏移量,16~31位为段选择器。
由gd_ss与gd_off构成中断处理代码入口。
2、请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
extern uintptr_t __vectors[];
int i;
for(i=0;i<sizeof(idt)/sizeof(struct gatedesc);i++){
SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL);
}
lidt(&idt_pd);
#define SETGATE(gate, istrap, sel, off, dpl)
使用SETGATE设置每一个中段描述符表项。
3、请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
在kern/driver/clock.h中声明了ticks为extern。
ticks++;
if((ticks%TICK_NUM)==0){
print_ticks();
}