美文网首页
操作系统实验:Lab1

操作系统实验:Lab1

作者: wenj1997 | 来源:发表于2018-03-18 15:00 被阅读0次

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

    相关文章

      网友评论

          本文标题:操作系统实验:Lab1

          本文链接:https://www.haomeiwen.com/subject/axolqftx.html