清華大學操作系統(tǒng)Lab1實驗報告
課程主頁:http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring
實驗指導書:https://chyyuu.gitbooks.io/ucore_os_docs/content/
github:https://github.com/chyyuu/ucore_os_lab
練習1:理解通過make生成執(zhí)行文件的過程
操作系統(tǒng)鏡像文件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
+ 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/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/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/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/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/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/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/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/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/string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.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
+ 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
+ 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
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
根據(jù)實際命令,回到Makefile文件中,可以看到對應(yīng)的生成ucore.img的過程及相應(yīng)語句如下,
# create ucore.img
UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(bootblock)
$(V)dd if=/dev/zero of=$@ count=10000
$(V)dd if=$(bootblock) of=$@ conv=notrunc
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img)
逐條分析:
-
$(kernel):生成kernel。需要以下兩步:-
編譯kern/目錄下的C程序,生成kernel需要的.o文件:
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))執(zhí)行的實際命令為
+ 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 + 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/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/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/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/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/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/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/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/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/string.c gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.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 -
鏈接這些.o文件,生成kernel。
# create kernel target kernel = $(call totarget,kernel) $(kernel): tools/kernel.ld $(kernel): $(KOBJS) @echo + ld $@ $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) @$(OBJDUMP) -S $@ > $(call asmfile,kernel) @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) $(call create_target,kernel)執(zhí)行的實際命令為
+ 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
-
-
$(bootblock):生成binblock。需要以下三步:- 生成bootmain.o和bootasm.o。
執(zhí)行的實際命令為bootfiles = $(call listf_cc,boot) $(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))+ 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 - 編譯tools/sign.c,生成sign.o。
執(zhí)行的實際命令為# create 'sign' tools $(call add_files_host,tools/sign.c,sign,sign) $(call create_target_host,sign,sign)+ 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 - 鏈接以上的.o文件。
執(zhí)行的實際命令為bootblock = $(call totarget,bootblock) $(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign) @echo + ld $@ $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock) @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) $(call create_target,bootblock)+ 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
- 生成bootmain.o和bootasm.o。
-
$(V)dd if=/dev/zero of=$@ count=10000:生成一個有10000個塊的文件,每個塊默認512字節(jié),用0填充。執(zhí)行的實際命令為dd if=/dev/zero of=bin/ucore.img count=10000 -
$(V)dd if=$(bootblock) of=$@ conv=notrunc:把bootblock中的內(nèi)容寫到第一個塊。執(zhí)行的實際命令為dd if=bin/bootblock of=bin/ucore.img conv=notrunc -
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc:從第二個塊開始寫kernel中的內(nèi)容。執(zhí)行的實際命令為dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
實際執(zhí)行的命令分為三類:
- gcc命令:將.c或.S命令編譯生成.o目標文件。
- ld命令:鏈接.o文件生成新的.o文件或可執(zhí)行文件。
- dd命令:用指定大小的塊拷貝一個文件,并在拷貝的同時進行指定的轉(zhuǎn)換。
gcc命令的參數(shù)和含義如下表:
| 參數(shù) | 含義 |
|---|---|
| -Idir | 把dir 加入到搜索頭文件的路徑列表中 |
| -fno-builtin | 不接受沒有 _builtin 前綴的函數(shù)作為內(nèi)建函數(shù) |
| -Wall | 開啟警告 |
| -ggdb | 生成gdb專 用的調(diào)試信息,使用最適合的格式(DWARF 2,stabs等)會有一些gdb專用的擴展,可能造成其他調(diào)試器無法運行 |
| -m32 | 生成32位機器上的代碼 |
| -gstabs | 使用 stabs格式,不包含gdb擴展,stabs常用于BSD系統(tǒng)的DBX調(diào)試器 |
| -nostdinc | 不使用標準庫 |
| -fno-stack-protector | |
| -O2 | 編譯時開啟O2優(yōu)化 |
ld命令的參數(shù)和含義如下表:
| 參數(shù) | 含義 |
|---|---|
| -m <emulation> | 模擬為i386上的連接器 |
| -nostdlib | 不使用標準庫 |
| -N | 設(shè)置代碼段和數(shù)據(jù)段均可讀寫 |
| -e <entry> | 指定入口 |
| -Ttext | 制定代碼段開始位置 |
| -T <scriptfile> | 讓連接器使用指定的腳本 |
一個被系統(tǒng)認為是符合規(guī)范的硬盤主引導扇區(qū)的特征是什么?
在tools/sign.c中,
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;
可以看到,符合規(guī)范的硬盤主引導扇區(qū)必須含有512個字節(jié),并且最后兩個字節(jié)分別是0x55和0xAA。
練習2:使用qemu執(zhí)行并調(diào)試lab1中的軟件
從CPU加電后執(zhí)行的第一條指令開始,單步跟蹤BIOS的執(zhí)行
將tools/gitinit文件改為如下命令:
file bin/kernel
target remote :1234
set architecture i8086
隨后執(zhí)行make debug將彈出gdb窗口,在gdb窗口中使用si命令即可單步追蹤。截圖如下:
在初始化位置0x7c00設(shè)置實地址斷點,測試斷點正常
將tools/gitinit文件改為如下命令:
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
c
x/10i $pc
執(zhí)行make debug后,測試結(jié)果如圖:
從0x7c00開始跟蹤代碼運行,將單步跟蹤反匯編得到的代碼與bootasm.S和bootblock.asm進行比較
為了方便比較,我將Makefile做如下更改以使得跟蹤代碼的匯編代碼輸出到文件中:
debug: $(UCOREIMG)
$(V)$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -parallel stdio -hda $< -serial null &
$(V)sleep 2
$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"
將tools/gitinit文件改為如下命令以使得每一步si都可以將匯編代碼輸出出來:
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
c
x/i $pc
set architecture i386
define hook-stop
x/i $pc
end
單步跟蹤的得到的部分代碼如下:
----------------
IN:
0x00007c00: cli
----------------
IN:
0x00007c00: cli
----------------
IN:
0x00007c01: cld
----------------
IN:
0x00007c02: xor %ax,%ax
----------------
IN:
0x00007c04: mov %ax,%ds
----------------
IN:
0x00007c06: mov %ax,%es
----------------
IN:
0x00007c08: mov %ax,%ss
----------------
IN:
0x00007c0a: in $0x64,%al
----------------
IN:
0x00007c0c: test $0x2,%al
----------------
IN:
0x00007c0e: jne 0x7c0a
----------------
IN:
0x00007c10: mov $0xd1,%al
----------------
IN:
0x00007c12: out %al,$0x64
----------------
IN:
0x00007c14: in $0x64,%al
----------------
IN:
0x00007c16: test $0x2,%al
----------------
IN:
0x00007c18: jne 0x7c14
----------------
IN:
0x00007c1a: mov $0xdf,%al
可以看出,和bootloader.S中0x7c00起始的匯編代碼相同。
自己找一個bootloader或內(nèi)核中的代碼位置,設(shè)置斷點并進行測試
將端點設(shè)在0x7d10,tools/gitinit文件改為如下命令:
file bin/kernel
target remote :1234
set architecture i8086
b *0x7d10
c
x/i $pc
set architecture i386
define hook-stop
x/i $pc
end
效果如圖:
練習3:分析bootloader進入保護模式的過程
見注釋Step1-6.
start:
# Step1:清理環(huán)境,射中重要段寄存器的初值。
.code16
cli
cld
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
# Step2:開啟A20。通過將鍵盤控制器上的A20線置于高電位,全部32條地址線可用, 可以訪問4G的內(nèi)存空間。
seta20.1:
inb $0x64, %al # 等待8042鍵盤控制器不忙
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 向8042端口發(fā)送0xd1
outb %al, $0x64
seta20.2:
inb $0x64, %al # 等待8042鍵盤控制器不忙input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 開啟A20
outb %al, $0x60
# Step3:初始化GDT,從實模式切換至保護模式
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Step4:將處理器轉(zhuǎn)至32位模式,跳轉(zhuǎn)
ljmp $PROT_MODE_CSEG, $protcseg
.code32 # Assemble for 32-bit mode
protcseg:
# Step5:設(shè)置段寄存器,并建立堆棧
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
movl $0x0, %ebp
movl $start, %esp
# Step6:進入bootmain
call bootmain
練習4:分析bootloader加載ELF格式的OS的過程
在bootmain函數(shù)中,包含了加載OS的過程,見Step1-4:
void
bootmain(void) {
// Step1:讀磁盤中的第一頁
readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
// Step2:檢查是否為ELF
if (ELFHDR->e_magic != ELF_MAGIC) {
goto bad;
}
struct proghdr *ph, *eph;
// Step3:加載描述表,并按照描述表讀入ELF文件中的數(shù)據(jù)
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph ++) {
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}
// Step4:根據(jù)ELF頭部儲存的入口信息,找到內(nèi)核的入口。
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
/* do nothing */
while (1);
}
其中,readseg可以從磁盤讀取任意長度的內(nèi)容。
練習5:實現(xiàn)函數(shù)調(diào)用堆棧跟蹤函數(shù)
補充kern/debug/kdebug.c:
void
print_stackframe(void) {
uint32_t ebp = read_ebp();
uint32_t eip = read_eip();
for (int i = 0; i < STACKFRAME_DEPTH && ebp != 0; ++i) {
cprintf("ebp:0x%08x eip:0x%08x ", ebp, eip);
uint32_t arg1, arg2, arg3, arg4;
arg1 = *((uint32_t *)ebp + 2);
arg2 = *((uint32_t *)ebp + 3);
arg3 = *((uint32_t *)ebp + 4);
arg4 = *((uint32_t *)ebp + 5);
cprintf("args:0x%08x 0x%08x 0x%08x 0x%08x\n", arg1, arg2, arg3, arg4);
print_debuginfo(eip - 1);
eip = *((uint32_t *)ebp + 1);
ebp = *((uint32_t *)ebp);
}
}
運行make qemu后結(jié)果見“練習5、6的結(jié)果”一圖。
最后一行的內(nèi)容是bootmain.c中的bootmain函數(shù),也即第一個使用該堆棧的函數(shù)。bootloader設(shè)置的堆棧從0x7c00開始,使用“call bootmain”轉(zhuǎn)入bootmain函數(shù)。 call指令壓棧,所以bootmain中ebp為0x7bf8。
練習6:完善中斷初始化和處理
中斷描述符表( 也可簡稱為保護模式下的中斷向量表) 中一個表項占多少字節(jié)?其中哪幾位代表中斷處理代碼的入口?
中斷向量表一個表項占用8字節(jié),2、3字節(jié)是段選擇子,0、1字節(jié)和6、7字節(jié)拼成位移, 兩者聯(lián)合便是中斷處理程序的入口地址。
請編程完善kern/trap/trap.c中對中斷向量表進行初始化的函數(shù)idt_init。
void
idt_init(void) {
extern uintptr_t __vectors[];
for (int i = 0; i < 256; ++i) {
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
}
SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);
lidt(&idt_pd);
}
請編程完善trap.c中的中斷處理函數(shù)trap,在對時鐘中斷進行處理的部分填寫trap函數(shù)中處理時鐘中斷的部分,使操作系統(tǒng)每遇到100次時鐘中斷后,調(diào)用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
case IRQ_OFFSET + IRQ_TIMER:
ticks++;
if (ticks== TICK_NUM) {
ticks= 0;
print_ticks();
}
break;
Challenge1
- Note:極大的參考了答案,還沒有完全理解。正試圖將syscall相關(guān)的部分寫進去。
- 在
lab1_switch_to_user和lab1_switch_to_kernel中嵌入?yún)R編,調(diào)用T_SWITCH_TOU和T_SWITCH_TOK兩種中斷。static void lab1_switch_to_user(void) { //LAB1 CHALLENGE 1 : TODO cprintf("1"); asm volatile ( "sub $0x8, %%esp \n" "int %0 \n" "movl %%ebp, %%esp":: "i"(T_SWITCH_TOU) ); cprintf("1"); } static void lab1_switch_to_kernel(void) { //LAB1 CHALLENGE 1 : TODO asm volatile ( "int %0 \n" "movl %%ebp, %%esp" :: "i"(T_SWITCH_TOK) ); } - 發(fā)生中斷后,在中斷處理程序中需要修改段選擇子,并在棧中保存原來的段選擇子。
case T_SWITCH_TOU: if (tf->tf_cs != USER_CS) { k2u = *tf; k2u.tf_cs = USER_CS; k2u.tf_ds = USER_DS; k2u.tf_es = USER_DS; k2u.tf_ss = USER_DS; k2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8; k2u.tf_eflags |= FL_IOPL_MASK; *((uint32_t *)tf - 1) = (uint32_t)&k2u; } break; case T_SWITCH_TOK: if (tf->tf_cs != KERNEL_CS) { tf->tf_cs = KERNEL_CS; tf->tf_ds = KERNEL_DS; tf->tf_es = KERNEL_DS; tf->tf_eflags &= ~FL_IOPL_MASK; u2k = (struct trapframe *)(tf->tf_esp - (sizeof(struct trapframe) - 8)); memmove(u2k, tf, sizeof(struct trapframe) - 8); *((uint32_t *)tf - 1) = (uint32_t)u2k; } break;
與參考答案的區(qū)別
- 練習五:最開始使用的是內(nèi)嵌匯編的形式獲得對應(yīng)地址的值,但是由于發(fā)現(xiàn)答案中的方法更好,因此改為了使用指針來獲取值。
- 練習六:與答案類似,但是自己寫的。沒有理解為什么前32個idt的is_trap也設(shè)為0。
- Challenge:參考了答案。
所覆蓋知識點
- BIOS啟動過程
- bootloader啟動過程
- 保護模式和分段機制
- 硬盤訪問
- 操作系統(tǒng)啟動過程
- 函數(shù)堆棧
- 中斷和異常
思考
在實驗過程中,我發(fā)現(xiàn)我對OS的理解不夠透徹。最重要的是,我沒有完全分清哪些由軟件操作,哪些由硬件操作,這給我的實驗造成了一些困難。所幸最后在閱讀匯編代碼的時候大致理清了思路。在接下來的學習中,我需要更深入的理解理論,才能更順利地進行接下來的實驗。