操作系统ucore的lab1

Lab1 report

[练习1]

[练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
查看Makefile中的命令

1.进入lab1_result目录,输入make clean来清除已编译的东西

moocos-> make clean
rm -f -r obj bin

2.查看

make V=
moocos-> make V=
+ cc kern/init/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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o
+ ld bin/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
+ cc boot/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
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
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
+ ld bin/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!
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.0374739 s, 137 MB/s
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000181796 s, 2.8 MB/s
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.00176758 s, 42.4 MB/s

bin/ucore.img
生成ucore.img的相关代码为
$(UCOREIMG): $(kernel) $(bootblock)
( V ) d d i f = / d e v / z e r o o f = (V)dd if=/dev/zero of= @ count=10000
( V ) d d i f = (V)dd if= (bootblock) of=$@ conv=notrunc
( V ) d d i f = (V)dd if= (kernel) of=$@ seek=1 conv=notrunc

为了生成ucore.img,首先需要生成bootblock、kernel

bin/bootblock
生成bootblock的相关代码为
$(bootblock): ( c a l l t o o b j , (call toobj, (bootfiles)) $(call totarget,sign)
@echo + ld $@
( V ) (V) (LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^
-o ( c a l l t o o b j , b o o t b l o c k ) @ (call toobj,bootblock) @ (OBJDUMP) -S $(call objfile,bootblock) >
( c a l l a s m f i l e , b o o t b l o c k ) @ (call asmfile,bootblock) @ (OBJCOPY) -S -O binary $(call objfile,bootblock)
( c a l l o u t f i l e , b o o t b l o c k ) @ (call outfile,bootblock) @ (call totarget,sign) $(call outfile,bootblock) $(bootblock)

为了生成bootblock,首先需要生成bootasm.o、bootmain.o、sign

obj/boot/bootasm.o, obj/boot/bootmain.o
生成bootasm.o,bootmain.o的相关makefile代码为
bootfiles = $(call listf_cc,boot)
( f o r e a c h f , (foreach f, (bootfiles), ( c a l l c c c o m p i l e , (call cc_compile, (f),$(CC),
$(CFLAGS) -Os -nostdinc))
实际代码由宏批量生成
生成bootasm.o需要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
其中关键的参数为
-ggdb 生成可供gdb使用的调试信息。这样才能用qemu+gdb来调试bootloader or ucore。
-m32 生成适用于32位环境的代码。我们用的模拟硬件是32bit的80386,所以ucore也要是32位的软件。
-gstabs 生成stabs格式的调试信息。这样要ucore的monitor可以显示出便于开发者阅读的函数调用栈信息
-nostdinc 不使用标准库。标准库是给应用程序用的,我们是编译ucore内核,OS内核是提供服务的,所以所有的服务要自给自足。
-fno-stack-protector 不生成用于检测缓冲区溢出的代码。这是for 应用程序的,我们是编译内核,ucore内核好像还用不到此功能。
-Os 为减小代码大小而进行优化。根据硬件spec,主引导扇区只有512字节,我们写的简单bootloader的最终大小不能大于510字节。
-I

添加搜索头文件的路径
生成bootmain.o需要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
新出现的关键参数有
-fno-builtin 除非用__builtin_前缀,
否则不进行builtin函数的优化
bin/sign
生成sign工具的makefile代码为
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)
实际命令为
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
首先生成bootblock.o
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00
obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
其中关键的参数为
-m 模拟为i386上的连接器
-nostdlib 不使用标准库
-N 设置代码段和数据段均可读写
-e 指定入口
-Ttext 制定代码段开始位置

 拷贝二进制代码bootblock.o到bootblock.out
 objcopy -S -O binary obj/bootblock.o obj/bootblock.out
 其中关键的参数为
	-S  移除所有符号和重定位信息
	-O <bfdname>  指定输出格式
 使用sign工具处理bootblock.out,生成bootblock
 bin/sign obj/bootblock.out bin/bootbloc

bin/kernel
生成kernel的相关代码为
$(kernel): tools/kernel.ld
$(kernel): $(KOBJS)
@echo + ld $@
( V ) (V) (LD) $(LDFLAGS) -T tools/kernel.ld -o $@ ( K O B J S ) @ (KOBJS) @ (OBJDUMP) -S $@ > ( c a l l a s m f i l e , k e r n e l ) @ (call asmfile,kernel) @ (OBJDUMP) -t $@ KaTeX parse error: Undefined control sequence: \ at position 38: …/d; s/ .* / /; \̲ ̲ /^$/d’ > $(call symfile,kernel)
为了生成kernel,首先需要 kernel.ld init.o readline.o stdio.o kdebug.o
kmonitor.o panic.o clock.o console.o intr.o picirq.o trap.o
trapentry.o vectors.o pmm.o printfmt.o string.o
kernel.ld已存在

>	obj/kern/*/*.o 
	 生成这些.o文件的相关makefile代码为
	 $(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,\
		$(KCFLAGS))
	 这些.o生成方式和参数均类似,仅举init.o为例,其余不赘述
>	obj/kern/init/init.o
	 编译需要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
 生成kernel时,makefile的几条指令中有@前缀的都不必需
 必需的命令只有
 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
 其中新出现的关键参数为
	-T <scriptfile>  让连接器使用指定的脚本
生成一个有10000个块的文件,每个块默认512字节,用0填充
dd if=/dev/zero of=bin/ucore.img count=10000
把bootblock中的内容写到第一个块
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
从第二个块开始写kernel中的内容
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc

查看sign.c文件内容

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[]) {
   struct stat st;
   if (argc != 3) {
       fprintf(stderr, "Usage: <input filename> <output filename>\n");
       return -1;
   }
   if (stat(argv[1], &st) != 0) {
       fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno));
       return -1;
   }
   printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size);
   if (st.st_size > 510) {
       fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size);
       return -1;
   }
   char buf[512];
   memset(buf, 0, sizeof(buf));
   FILE *ifp = fopen(argv[1], "rb");
   int size = fread(buf, 1, st.st_size, ifp);
   if (size != st.st_size) {
       fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size);
       return -1;
   }
   fclose(ifp);
   buf[510] = 0x55;
   buf[511] = 0xAA;
   FILE *ofp = fopen(argv[2], "wb+");
   size = fwrite(buf, 1, 512, ofp);
   if (size != 512) {
       fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size);
       return -1;
   }
   fclose(ofp);
   printf("build 512 bytes boot sector: '%s' success!\n", argv[2]);
   return 0;
}

[练习1.2] 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
从sign.c的代码来看,一个磁盘主引导扇区只有512字节。且
第510个(倒数第二个)字节是0x55,
第511个(倒数第一个)字节是0xAA。

[练习2]

[练习2.1] 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。
练习2可以单步跟踪,方法如下:
1 修改 lab1/tools/gdbinit,内容为:

set architecture i8086
target remote :1234

在 lab1目录下,执行

make debug

在看到gdb的调试界面(gdb)后,在gdb调试界面下执行如下命令

si

即可单步跟踪BIOS了。

在gdb界面下,可通过如下命令来看BIOS的代码

 x /2i $pc  //显示当前eip处的汇编指令

改写Makefile文件
debug: $(UCOREIMG)
( V ) (V) (TERMINAL) -e “$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -parallel stdio -hda $< -serial null”
$(V)sleep 2
( V ) (V) (TERMINAL) -e “gdb -q -tui -x tools/gdbinit”

在调用qemu时增加`-d in_asm -D q.log`参数,便可以将运行的汇编指令保存在q.log中。
为防止qemu在gdb连接后立即开始执行,删除了`tools/gdbinit`中的`continue`行。

[练习2.2] 在初始化位置0x7c00 设置实地址断点,测试断点正常。

在tools/gdbinit结尾加上

    set architecture i8086  //设置当前调试的CPU是8086
	b *0x7c00  //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
	c          //continue简称,表示继续执行
	x /2i $pc  //显示当前eip处的汇编指令
	set architecture i386  //设置当前调试的CPU是80386

运行"make debug"便可得到

	Breakpoint 2, 0x00007c00 in ?? ()
	=> 0x7c00:      cli    
	   0x7c01:      cld    
	   0x7c02:      xor    %eax,%eax
	   0x7c04:      mov    %eax,%ds
	   0x7c06:      mov    %eax,%es
	   0x7c08:      mov    %eax,%ss 
	   0x7c0a:      in     $0x64,%al
	   0x7c0c:      test   $0x2,%al
	   0x7c0e:      jne    0x7c0a
	   0x7c10:      mov    $0xd1,%al

[练习2.3] 在调用qemu 时增加-d in_asm -D q.log 参数,便可以将运行的汇编指令保存在q.log 中。
将执行的汇编代码与bootasm.S 和 bootblock.asm 进行比较,看看二者是否一致。

在tools/gdbinit结尾加上

	b *0x7c00
	c
	x /10i $pc

便可以在q.log中读到"call bootmain"前执行的命令

IN: 
0x00007c00:  cli    
IN: 
0x00007c01:  cld    
0x00007c02:  xor    %ax,%ax
0x00007c04:  mov    %ax,%ds
0x00007c06:  mov    %ax,%es
0x00007c08:  mov    %ax,%ss
IN: 
0x00007c0a:  in     $0x64,%al	
IN: 
0x00007c0c:  test   $0x2,%al
0x00007c0e:  jne    0x7c0a
	----------------
	IN: 
	0x00007c10:  mov    $0xd1,%al
	0x00007c12:  out    %al,$0x64
	0x00007c14:  in     $0x64,%al
	0x00007c16:  test   $0x2,%al
	0x00007c18:  jne    0x7c14
	
	----------------
	IN: 
	0x00007c1a:  mov    $0xdf,%al
	0x00007c1c:  out    %al,$0x60
	0x00007c1e:  lgdtw  0x7c6c
	0x00007c23:  mov    %cr0,%eax
	0x00007c26:  or     $0x1,%eax
	0x00007c2a:  mov    %eax,%cr0
	
	----------------
	IN: 
	0x00007c2d:  ljmp   $0x8,$0x7c32
	
	----------------
	IN: 
	0x00007c32:  mov    $0x10,%ax
	0x00007c36:  mov    %eax,%ds
	
	----------------
	IN: 
	0x00007c38:  mov    %eax,%es
	
	IN: 
	0x00007c3a:  mov    %eax,%fs
	0x00007c3c:  mov    %eax,%gs
	0x00007c3e:  mov    %eax,%ss
	IN: 
	0x00007c40:  mov    $0x0,%ebp
	IN: 
	0x00007c45:  mov    $0x7c00,%esp
	0x00007c4a:  call   0x7d0d
	IN: 
	0x00007d0d:  push   %ebp

其与bootasm.S和bootblock.asm中的代码相同。

[练习3]

分析bootloader 进入保护模式的过程。

%cs=0 $pc=0x7c00,进入后

首先清理环境:包括将flag置0和将段寄存器置0

	.code16
	    cli
	    cld
	    xorw %ax, %ax
	    movw %ax, %ds
	    movw %ax, %es
	    movw %ax, %ss

开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,
可以访问4G的内存空间。

	seta20.1:               # 等待8042键盘控制器不忙
	    inb $0x64, %al      # 
	    testb $0x2, %al     #
	    jnz seta20.1        #
	
	    movb $0xd1, %al     # 发送写8042输出端口的指令
	    outb %al, $0x64     #
	
	seta20.1:               # 等待8042键盘控制器不忙
	    inb $0x64, %al      # 
	    testb $0x2, %al     #
	    jnz seta20.1        #
	
	    movb $0xdf, %al     # 打开A20
	    outb %al, $0x60     # 

初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可

	    lgdt gdtdesc

进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式

	    movl %cr0, %eax
	    orl $CR0_PE_ON, %eax
	    movl %eax, %cr0

通过长跳转更新cs的基地址

	 ljmp $PROT_MODE_CSEG, $protcseg
	.code32
	protcseg:

设置段寄存器,并建立堆栈

	    movw $PROT_MODE_DSEG, %ax
	    movw %ax, %ds
	    movw %ax, %es
	    movw %ax, %fs
	    movw %ax, %gs
	    movw %ax, %ss
	    movl $0x0, %ebp
	    movl $start, %esp

转到保护模式完成,进入boot主方法

	    call bootmain

[练习4]

分析bootloader加载ELF格式的OS的过程。

首先看readsect函数,
readsect从设备的第secno扇区读取数据到dst位置

	static void
	readsect(void *dst, uint32_t secno) {
	    waitdisk();
	
	    outb(0x1F2, 1);                         // 设置读取扇区的数目为1
	    outb(0x1F3, secno & 0xFF);
	    outb(0x1F4, (secno >> 8) & 0xFF);
	    outb(0x1F5, (secno >> 16) & 0xFF);
	    outb(0x1F6, ((secno >> 24) & 0xF)  0xE0);
	        // 上面四条指令联合制定了扇区号
	        // 在这4个字节线联合构成的32位参数中
	        //   29-31位强制设为1
	        //   28位(=0)表示访问"Disk 0"
	        //   0-27位是28位的偏移量
	    outb(0x1F7, 0x20);                      // 0x20命令,读取扇区
	
	    waitdisk();

	    insl(0x1F0, dst, SECTSIZE / 4);         // 读取到dst位置,
	                                            // 幻数4因为这里以DW为单位
	}

readseg简单包装了readsect,可以从设备读取任意长度的内容。

	static void
	readseg(uintptr_t va, uint32_t count, uint32_t offset) {
	    uintptr_t end_va = va + count;
	
	    va -= offset % SECTSIZE;
	
	    uint32_t secno = (offset / SECTSIZE) + 1; 
	    // 加1因为0扇区被引导占用
	    // ELF文件从1扇区开始
	
	    for (; va < end_va; va += SECTSIZE, secno ++) {
	        readsect((void *)va, secno);
	    }
	}

在bootmain函数中,

	void
	bootmain(void) {
	    // 首先读取ELF的头部
	    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
	
	    // 通过储存在头部的幻数判断是否是合法的ELF文件
	    if (ELFHDR->e_magic != ELF_MAGIC) {
	        goto bad;
	    }
	
	    struct proghdr *ph, *eph;
	
	    // ELF头部有描述ELF文件应加载到内存什么位置的描述表,
	    // 先将描述表的头地址存在ph
	    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
	    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);
	    while (1);
	}

[练习5]

实现函数调用堆栈跟踪函数

ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。
ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。

输出中,堆栈最深一层为

	ebp:0x00007bf8 eip:0x00007d68 \
		args:0x00000000 0x00000000 0x00000000 0x00007c4f
	    <unknow>: -- 0x00007d67 --

其对应的是第一个使用堆栈的函数,bootmain.c中的bootmain。
bootloader设置的堆栈从0x7c00开始,使用"call bootmain"转入bootmain函数。
call指令压栈,所以bootmain中ebp为0x7bf8。

[练习6]

完善中断初始化和处理

[练习6.1] 中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,
两者联合便是中断处理程序的入口地址。

[练习6.2] 请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。

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

[练习6.3] 请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数

定义变量:

1
struct trapframe switchk2u, *switchu2k;
结构体 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));
宏定义:
#define IRQ_OFFSET                32    
#define IRQ_TIMER                 0
#define IRQ_KBD                   1
#define IRQ_COM1                  4
#define T_SWITCH_TOU              120
#define USER_CS        ((GD_UTEXT) | DPL_USER)
#define USER_DS        ((GD_UDATA) | DPL_USER)
#define KERNEL_DS    ((GD_KDATA) | DPL_KERNEL)
#define TICK_NUM 100
print_ticks函数

static void print_ticks() {
    cprintf("%d ticks\n",TICK_NUM);
#ifdef DEBUG_GRADE
    cprintf("End of Test.\n");
    panic("EOT: kernel seems ok.");
#endif
}
trap_dispatch函数的实现:

static void
trap_dispatch(struct trapframe *tf) {
    char c;

    switch (tf->tf_trapno) {
    case IRQ_OFFSET + IRQ_TIMER:
        ticks ++;
        if (ticks % TICK_NUM == 0) {
            print_ticks();
        }
        break;

 //下面的代码不用我们实现
    case IRQ_OFFSET + IRQ_COM1:
        c = cons_getc();
        cprintf("serial [%03d] %c\n", c, c);
        break;
    case IRQ_OFFSET + IRQ_KBD:
        c = cons_getc();
        cprintf("kbd [%03d] %c\n", c, c);
        break;
    case T_SWITCH_TOU:
        if (tf->tf_cs != USER_CS) {
            switchk2u = *tf;
            switchk2u.tf_cs = USER_CS;
            switchk2u.tf_ds = switchk2u.tf_es = switchk2u.tf_ss = USER_DS;
            switchk2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8;

            switchk2u.tf_eflags |= FL_IOPL_MASK;
            *((uint32_t *)tf - 1) = (uint32_t)&switchk2u;
        }
        break;
    case T_SWITCH_TOK:
        if (tf->tf_cs != KERNEL_CS) {
            tf->tf_cs = KERNEL_CS;
            tf->tf_ds = tf->tf_es = KERNEL_DS;
            tf->tf_eflags &= ~FL_IOPL_MASK;
            switchu2k = (struct trapframe *)(tf->tf_esp - (sizeof(struct trapframe) - 8));
            memmove(switchu2k, tf, sizeof(struct trapframe) - 8);
            *((uint32_t *)tf - 1) = (uint32_t)switchu2k;
        }
        break;
    case IRQ_OFFSET + IRQ_IDE1:
    case IRQ_OFFSET + IRQ_IDE2:
        break;
    default:
        if ((tf->tf_cs & 3) == 0) {
            print_trapframe(tf);
            panic("unexpected trap in kernel.\n");
        }
    }
}

[练习7]

增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),
当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务

在idt_init中,将用户态调用SWITCH_TOK中断的权限打开。
SETGATE(idt[T_SWITCH_TOK], 1, KERNEL_CS, __vectors[T_SWITCH_TOK], 3);

在trap_dispatch中,将iret时会从堆栈弹出的段寄存器进行修改
对TO User

	    tf->tf_cs = USER_CS;
	    tf->tf_ds = USER_DS;
	    tf->tf_es = USER_DS;
	    tf->tf_ss = USER_DS;
对TO Kernel
	    tf->tf_cs = KERNEL_CS;
	    tf->tf_ds = KERNEL_DS;
	    tf->tf_es = KERNEL_DS;

在lab1_switch_to_user中,调用T_SWITCH_TOU中断。
注意从中断返回时,会多pop两位,并用这两位的值更新ss,sp,损坏堆栈。
所以要先把栈压两位,并在从中断返回后修复esp。

	asm volatile (
	    "sub $0x8, %%esp \n"
	    "int %0 \n"
	    "movl %%ebp, %%esp"
	    : 
	    : "i"(T_SWITCH_TOU)
	);

在lab1_switch_to_kernel中,调用T_SWITCH_TOK中断。
注意从中断返回时,esp仍在TSS指示的堆栈中。所以要在从中断返回后修复esp。

	asm volatile (
	    "int %0 \n"
	    "movl %%ebp, %%esp \n"
	    : 
	    : "i"(T_SWITCH_TOK)
	);

但这样不能正常输出文本。根据提示,在trap_dispatch中转User态时,将调用io所需权限降低。

	tf->tf_eflags = 0x3000;
原创文章 2 获赞 0 访问量 237

猜你喜欢

转载自blog.csdn.net/liuxingyustar/article/details/102672459
今日推荐