操作系统实验:Lab1

清华大学操作系统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生成执行文件的过程

操作系统镜像文件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

根据实际命令,回到Makefile文件中,可以看到对应的生成ucore.img的过程及相应语句如下,

# 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))
      

      执行的实际命令为

      + 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)
      

      执行的实际命令为

      + 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。
      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。
      # 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文件。
      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
      
  • $(V)dd if=/dev/zero of=$@ count=10000:生成一个有10000个块的文件,每个块默认512字节,用0填充。执行的实际命令为
    dd if=/dev/zero of=bin/ucore.img count=10000
    
  • $(V)dd if=$(bootblock) of=$@ conv=notrunc:把bootblock中的内容写到第一个块。执行的实际命令为
    dd if=bin/bootblock of=bin/ucore.img conv=notrunc
    
  • $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc:从第二个块开始写kernel中的内容。执行的实际命令为
    dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
    

实际执行的命令分为三类:

  1. gcc命令:将.c或.S命令编译生成.o目标文件。
  2. ld命令:链接.o文件生成新的.o文件或可执行文件。
  3. dd命令:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。

gcc命令的参数和含义如下表:

参数 含义
-Idir 把dir 加入到搜索头文件的路径列表中
-fno-builtin 不接受没有 _builtin 前缀的函数作为内建函数
-Wall 开启警告
-ggdb 生成gdb专 用的调试信息,使用最适合的格式(DWARF 2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行
-m32 生成32位机器上的代码
-gstabs 使用 stabs格式,不包含gdb扩展,stabs常用于BSD系统的DBX调试器
-nostdinc 不使用标准库
-fno-stack-protector
-O2 编译时开启O2优化

ld命令的参数和含义如下表:

参数 含义
-m <emulation> 模拟为i386上的连接器
-nostdlib 不使用标准库
-N 设置代码段和数据段均可读写
-e <entry> 指定入口
-Ttext 制定代码段开始位置
-T <scriptfile> 让连接器使用指定的脚本
一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

在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;

可以看到,符合规范的硬盘主引导扇区必须含有512个字节,并且最后两个字节分别是0x55和0xAA。

练习2:使用qemu执行并调试lab1中的软件

从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行

将tools/gitinit文件改为如下命令:

file bin/kernel
target remote :1234
set architecture i8086

随后执行make debug将弹出gdb窗口,在gdb窗口中使用si命令即可单步追踪。截图如下:

从CPU加电后第一条指令开始执行,执行三步后输出当前指令

在初始化位置0x7c00设置实地址断点,测试断点正常

将tools/gitinit文件改为如下命令:

file bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
c
x/10i $pc

执行make debug后,测试结果如图:

在0x7c00处设断点

从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或内核中的代码位置,设置断点并进行测试

将端点设在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

效果如图:


断点为0x7de0

练习3:分析bootloader进入保护模式的过程

见注释Step1-6.

start:
# Step1:清理环境,射中重要段寄存器的初值。
.code16
    cli     
    cld  

    xorw %ax, %ax  
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %ss

# Step2:开启A20。通过将键盘控制器上的A20线置于高电位,全部32条地址线可用, 可以访问4G的内存空间。
seta20.1:
    inb $0x64, %al  # 等待8042键盘控制器不忙
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al  # 向8042端口发送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:将处理器转至32位模式,跳转
    ljmp $PROT_MODE_CSEG, $protcseg

.code32                                             # Assemble for 32-bit mode
protcseg:

# Step5:设置段寄存器,并建立堆栈
    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函数中,包含了加载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文件中的数据
    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:根据ELF头部储存的入口信息,找到内核的入口。
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);

    /* do nothing */
    while (1);
}

其中,readseg可以从磁盘读取任意长度的内容。

练习5:实现函数调用堆栈跟踪函数

补充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后结果见“练习5、6的结果”一图。
最后一行的内容是bootmain.c中的bootmain函数,也即第一个使用该堆栈的函数。bootloader设置的堆栈从0x7c00开始,使用“call bootmain”转入bootmain函数。 call指令压栈,所以bootmain中ebp为0x7bf8。

练习5、6的结果

练习6:完善中断初始化和处理

中断描述符表( 也可简称为保护模式下的中断向量表) 中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

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

请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数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中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
    case IRQ_OFFSET + IRQ_TIMER:
        ticks++;
        if (ticks== TICK_NUM) {
            ticks= 0;
            print_ticks();
        }
        break;

Challenge1

  • Note:极大的参考了答案,还没有完全理解。正试图将syscall相关的部分写进去。
  • lab1_switch_to_userlab1_switch_to_kernel中嵌入汇编,调用T_SWITCH_TOUT_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)
      );
    }
    
  • 发生中断后,在中断处理程序中需要修改段选择子,并在栈中保存原来的段选择子。
      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;
    

与参考答案的区别

  • 练习五:最开始使用的是内嵌汇编的形式获得对应地址的值,但是由于发现答案中的方法更好,因此改为了使用指针来获取值。
  • 练习六:与答案类似,但是自己写的。没有理解为什么前32个idt的is_trap也设为0。
  • Challenge:参考了答案。

所覆盖知识点

  • BIOS启动过程
  • bootloader启动过程
  • 保护模式和分段机制
  • 硬盘访问
  • 操作系统启动过程
  • 函数堆栈
  • 中断和异常

思考

在实验过程中,我发现我对OS的理解不够透彻。最重要的是,我没有完全分清哪些由软件操作,哪些由硬件操作,这给我的实验造成了一些困难。所幸最后在阅读汇编代码的时候大致理清了思路。在接下来的学习中,我需要更深入的理解理论,才能更顺利地进行接下来的实验。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335