实验一:系统软件启动过程
练习一
ucore.img的生成
运行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/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/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
......该部分将各个C文件编译为obj文件,主要编译选项有:
-m32生成32位环境下的目标-ggdb,-gsatbs提供调试信息-nostdinc不搜索当前环境下的标准头文件-Ikern/libs等 将文件夹加入头文件搜索路径
第二部分
ld bin/kernel
ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/libs/string.o obj/libs/printfmt.o该部分将上一步生成的obj文件链接到一起,产生bin/kernel文件
-m elf_i386模拟elf_i386连接器-nostdlib不使用标准库-T tools/kernel.ld使用kernel.ld中的配置链接文件
第三部分
- 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编译BootLoader,并生成将BootLoader补齐到512字节的工具bin/sign
-Os提示编译器尽量减小目标码的体积
第四部分
+ 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
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc将
bootasm.o和bootmain.o链接到一起-N设置text和data部分可读可写,并且不对数据段进行对齐-e start将entry point设为start-Ttext 0x7C00将代码段的起始地址设为0x7C00
执行
bin/sign obj/bootblock.out bin/bootblock生成512字节的bootblock不足510字节的部分补0
最后两个字节设为
0x55 0xAA作为signature
用dd生成一个空镜像,然后将bootblock和kernel按次序写入
if=FILE从FILE读入数据of=FILE输出到FILEconv=notrunc不截断输出文件seek=1跳过输出开头的512个字节
主引导扇区
标准的MBR分区表结构为前466字节为代码段,紧跟64字节的分区表,最后两个字节为Boot Signature
实际上第511和512个字节分别为0x55和0xAA即可被BIOS读入内存中,实验过程如下
dd if=/dev/zero of=bootloader count=1
echo -ne "\x55\xaa" | dd seek=510 bs=1 of=bootloader
qemu-system-i386 bootloader练习二
直接make debug或手动在terminal中输入如下
qemu-system-i386 -S -s -parallel stdio -hda ucore.img
gdb -q -tui -x /tools/gdbinit即可进行单步调试
练习三
保护模式的进入
从BootLoader进入保护模式可分为三个部分
第一部分
进行基本设置
cli(clear interrupt flag) 使CPU不再接受外部中断cld(clear direction flag) 使CPU按从低地址到高地址处理字符串将若干寄存器置0
打开A20 Gate
等待直到8042芯片的输入缓存为空
向0x64端口发送0xD1,意为对P2端口写数据
等待直到8042芯片的输入缓存为空
向0x64端口发送0xDF,打开A20 Gate
第二部分
lgdt gdtdesc将gdtdesc所指向的6个字节内容读入GDTR低2位的0x17表明表的大小为24
高4位为表的起始地址
将控制寄存器CR0的PE (Protection Enable, bit 0)设为1
ljmp $PROT_MODE_CSEG, $protcseg将cs寄存器设为$PROT_MODE_CSEG,将eip设为protcseg所指地址
其中GDT由asm.h中提供的宏展开生成,一般描述符的结构如下
31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------+
| | | | |A| | | | | | | |
| BASE 31..24 |G|X|O|V| LIMIT |P| DPL |1| TYPE|A| BASE 23..16 | 4
| | | | |L| 19..16 | | | | | | |
|-----------------+-+-+-+-+---------+-+-----+-+-----+-+-----------------|
| | |
| SEGMENT BASE 15..0 | SEGMENT LIMIT 15..0 | 0
| | |
+-----------------+-----------------+-----------------+-----------------+SEG_NULLASM会产生一个空描述符,SEG_ASM(type,base,lim)会根据参数生成所需的描述符,注意这里取了lim的高20位作为LIMIT。
此处描述符定义的代码段和数据段都是从0x0开始,容量4GB的段。
第三部分
除了cs外的段寄存器都指向数据段,将epb置0,将栈顶指向BootLoader起始处(0x7c00),调用bootmain
练习四
读硬盘扇区
BootLoader中读取硬盘的功能是基于static void readsect(void *dst, uint32_t secno)函数实现的,该函数可以实现读一个扇区,反复调用可以将kernel完整读入内存。大致过程如下
等待磁盘准备好
从0x1F7读入状态码,检查BSY和RDY两个bit
向0x1F2端口发送读取的扇区数
传送LBA的各个位,发送读取命令
等待磁盘准备好
读入数据
加载ELF格式OS
首先读入8个扇区,将ELF文件头读入;
上一步从硬盘读入内存的起始地址为0x10000,因此该地址也是ELF文件头的地址;
根据文件头提供的信息将kernel读入正确的内存地址
调用kernel的入口函数
练习五
输出如下
epb:0x00007b38 eip:0x00100967 arg: 0x00010074 0x00010074 0x00007b68 0x0010007f
kern/debug/kdebug.c:306: print_stackframe+21
epb:0x00007b48 eip:0x00100c41 arg: 0x00000000 0x00000000 0x00000000 0x00007bb8
kern/debug/kmonitor.c:125: mon_backtrace+10
epb:0x00007b68 eip:0x0010007f arg: 0x00000000 0x00007b90 0xffff0000 0x00007b94
kern/init/init.c:48: grade_backtrace2+19
epb:0x00007b88 eip:0x001000a1 arg: 0x00000000 0xffff0000 0x00007bb4 0x00000029
kern/init/init.c:53: grade_backtrace1+27
epb:0x00007ba8 eip:0x001000be arg: 0x00000000 0x00100000 0xffff0000 0x00100043
kern/init/init.c:58: grade_backtrace0+19
epb:0x00007bc8 eip:0x001000df arg: 0x00000000 0x00000000 0x0010fd20 0x00103300
kern/init/init.c:63: grade_backtrace+26
epb:0x00007be8 eip:0x00100050 arg: 0x00000000 0x00000000 0x00000000 0x00007c4f
kern/init/init.c:28: kern_init+79
epb:0x00007bf8 eip:0x00007d6e arg: 0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8
<unknow>: -- 0x00007d6d --最后一行的0x7d6d = 0x7d6c + 1,而打开bootblock.asm可以看到0x7d6c为调用kernel入口的最后一句指令,所以0x7d6d实际上是调用call指令后压栈的返回地址
练习六
中断描述符
每个终端描述符占8个字节空间,其中16-31位的bits为段选择子,0-15和48-63位的bits分别为偏移地址的两个字,大致结构如下
31 23 15 7 0
+-----------------+-----------------+---+---+---------+-----+-----------+
| OFFSET 31..16 | P |DPL|0 1 1 1 0|0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
| SELECTOR | OFFSET 15..0 |0
+-----------------+-----------------+-----------------+-----------------+初始化中断描述符表
对idt数组中的每一个终端描述符调用SETGATE进行设置,要注意段描述符应该与voidpmm_init(void)中设置的内核代码段描述符地址(0x8)一致。
由于要支持应用程序发出软中断int 0x80,需特别将T_SYSCALL的DPL设为3。
时钟中断处理
在static void trap_dispatch(struct trapframe *tf)中处理时钟中断部分添加如下代码
if (++ticks % TICK_NUM == 0) {
print_ticks();
}每次收到中断即将全局变量ticks加1,当为100整数倍时调用print_ticks。
总结
实现与参考答案的区别
参考答案限制了print_stackframe的最大深度
知识点
BIOS启动过程:加电后,CPU执行物理地址0xFFFFFFF0处的跳转指令,开始执行BIOS程序
保护模式的进入:打开A20地址线->设置全局描述符表->打开保护模式
分段机制:逻辑地址可分为段选择子和偏移量,通过段选择子可以找到段描述符,从中取出段基址加上偏移量可得到线性地址,未启用分页机制时即为物理地址
ELF文件格式:ucore中的
struct elfhdr和struct proghdr表示了ELF文件头的结构
最后更新于
这有帮助吗?