“操作系统实验:基于 uCore OS”实验报告

启动操作系统

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

查看Makefile中的相关代码如下:
-生成kernel的相关代码:


KINCLUDE    += kern/debug/ \
               kern/driver/ \
               kern/trap/ \
               kern/mm/

KSRCDIR        += kern/init \
               kern/libs \
               kern/debug \
               kern/driver \
               kern/trap \
               kern/mm

KCFLAGS        += $(addprefix -I,$(KINCLUDE))

$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) //首先生成相关文件,如init.o等

KOBJS    = $(call read_packet,kernel libs)

# create kernel target 
kernel = $(call totarget,kernel)

$(kernel): tools/kernel.ld //相关文件备齐,开始生成kernel

$(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)
copy

-生成bootblock的相关代码:

bootfiles = $(call listf_cc,boot) //生成bootasm.o,bootmain.o等
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))

bootblock = $(call totarget,bootblock)

//根据bootasm.o,bootmain.o以及sign生成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)

# -------------------------------------------------------------------

# create 'sign' tools
$(call add_files_host,tools/sign.c,sign,sign)  //生成sign
$(call create_target_host,sign,sign)
copy

-生成ucore.img的相关代码:

UCOREIMG    := $(call totarget,ucore.img)

$(UCOREIMG): $(kernel) $(bootblock)
    $(V)dd if=/dev/zero of=$@ count=10000 //生成10000个块的文件,每块512字节,用0填充
    $(V)dd if=$(bootblock) of=$@ conv=notrunc //向第一个块中写入bootblock的内容
    $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc //从第二个块开始写入kernel内容

$(call create_target,ucore.img)
copy

通过make "V=",可以看到各部分代码对应执行的实际内容如下:
1.1生成kernel相关文件对应的命令为(以init.o为例):

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
copy

1.2生成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
copy

2.1生成bootasm.o的实际命令为:

gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
copy

2.2生成bootmain.o的实际命令为:

gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
copy

2.3生成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
copy

2.4生成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: 472 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
copy

3.生成ucore.img的相关部分为:

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.0311037 s, 165 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, 7.0769e-05 s, 7.2 MB/s
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74871 bytes (75 kB) copied, 0.00390632 s, 19.2 MB/s
copy

[练习1.2] 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

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;
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]);
copy

从代码可以看到,主引导扇区大小固定为512字节,且最后两个字节分别为0x55和0xAA。

[练习2.1] 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。

将tools/gdbinit中内容修改为:

set architecture i8086
target remote :1234
copy

运行"make debug"后,可在gdb界面看到加电后第一条指令位置为0xffff0,相应可通过x /i 0xffff0查看到对应命令为:

0xffff0:     ljmp   $0xf000,$0xe05b
copy

后续可通过si命令单步跟踪BIOS执行,并通过上述方法查看对应命令。

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

在原Makefile文件中增加如下部分:

lab1-mon: $(UCOREIMG)
    $(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -monitor stdio -hda $< -serial null"
    $(V)sleep 2
    $(V)$(TERMINAL) -e "gdb -q -x tools/lab1init"
copy

对应的lab1init内容为:

target remote:1234  //连接qemu
set architecture i8086  //设置当前CPU为8086,对应16位实模式
b *0x7c00  //在0x7c00处设置断点。
continue          //继续执行
x /2i $pc  //显示当前eip处的汇编指令
copy

运行"make lab1-mon"便可看到断点正常,输出如下:

Breakpoint 1, 0x00007c00 in ?? ()
=> 0x7c00:    cli    
   0x7c01:    cld
copy

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

在[练习2.2]中已在lab1-mon中添加相关参数,打开q.log可查到从0x00007c00开始的汇编指令如下:

IN: 
0x00007c00:  cli    

----------------
IN: 
0x00007c00:  cli    
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   0x7cd1
copy

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

[练习3] 分析bootloader 进入保护模式的过程。

bootasm.S中相关代码如下:

    cli        // 关中断
    cld        // Flag寄存器方向标志位DF置零

    // 段寄存器置零
    xorw %ax, %ax         
    movw %ax, %ds        
    movw %ax, %es         
    movw %ax, %ss         

    // 开启A20
seta20.1:
    inb $0x64, %al       // 等待8042 input buffer为空
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al      // 发送0xd1至8042的0x64端口,意为写入8042的P2 port的命令
    outb %al, $0x64      

seta20.2:
    inb $0x64, %al       // 等待8042 input buffer为空
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al      // 发送0xdf=11011111至8042的0x60端口,意为将8042的P2 port的A20设置位置1,即开启A20
    outb %al, $0x60      

    
    lgdt gdtdesc        // 载入以初始化GDT表

    //将cr0寄存器PE位置1,开启保护模式
    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

    // 长跳转进入32位模式
    ljmp $PROT_MODE_CSEG, $protcseg
copy

[练习4] :分析bootloader加载ELF格式的OS的过程。

bootmain.c中相关代码如下:

// 读取单扇区函数
static void
readsect(void *dst, uint32_t secno) {
    // wait for disk to be ready
    waitdisk();

    outb(0x1F2, 1);                         // count = 1
    outb(0x1F3, secno & 0xFF);
    outb(0x1F4, (secno >> 8) & 0xFF);
    outb(0x1F5, (secno >> 16) & 0xFF);
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
    outb(0x1F7, 0x20);                      // cmd 0x20 - read sectors

    // wait for disk to be ready
    waitdisk();

    // read a sector
    insl(0x1F0, dst, SECTSIZE / 4);
}

// 调用读取单扇区函数,实现读取段数据函数
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;

    // round down to sector boundary
    va -= offset % SECTSIZE;

    // translate from bytes to sectors; kernel starts at sector 1
    uint32_t secno = (offset / SECTSIZE) + 1;

    // If this is too slow, we could read lots of sectors at a time.
    // We'd write more to memory than asked, but it doesn't matter --
    // we load in increasing order.
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno);
    }
}


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头设置相关程序头指针,依次读取段数据至内存
    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);
    }

    // ELF格式的OS加载完成,根据ELF头的相关信息,转入kernel入口
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

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

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

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

修改kdebug.c中的print_stackframe函数代码如下:

void
print_stackframe(void) {
     
    uint32_t ebp = read_ebp(),eip = read_eip();
    int i,j;
    for(i = 0;(ebp!=0) && (i < STACKFRAME_DEPTH);i ++){
        cprintf("ebp : 0x%08x,eip : 0x%08x args:",ebp,eip);
        uint32_t *args = (uint32_t *)ebp + 2;
        for(j = 0;j < 4;j ++){
            cprintf("0x%08x ",j,*(args + j));
        }
        cprintf("\n");
        print_debuginfo(eip-1);
        eip = *((uint32_t *)ebp + 1);
        ebp = *((uint32_t *) ebp);
    }
copy

执行"make qemu"命令后对应输出如下:

ebp : 0x00007b08,eip : 0x001009a6 args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/debug/kdebug.c:294: print_stackframe+21
ebp : 0x00007b18,eip : 0x00100c9c args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/debug/kmonitor.c:125: mon_backtrace+10
ebp : 0x00007b38,eip : 0x00100092 args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/init/init.c:48: grade_backtrace2+33
ebp : 0x00007b58,eip : 0x001000bb args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/init/init.c:53: grade_backtrace1+38
ebp : 0x00007b78,eip : 0x001000d9 args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/init/init.c:58: grade_backtrace0+23
ebp : 0x00007b98,eip : 0x001000fe args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/init/init.c:63: grade_backtrace+34
ebp : 0x00007bc8,eip : 0x00100055 args:0x00000000 0x00000001 0x00000002 0x00000003 
    kern/init/init.c:28: kern_init+84
ebp : 0x00007bf8,eip : 0x00007d68 args:0x00000000 0x00000001 0x00000002 0x00000003 
    <unknow>: -- 0x00007d67 --
copy

与实验要求基本一致。其中最后一行为:

ebp : 0x00007bf8,eip : 0x00007d68 args:0x00000000 0x00000001 0x00000002 0x00000003 
    <unknow>: -- 0x00007d67 --
copy

bootblock中对应的汇编命令如下:

0x00007c40:  mov    $0x0,%ebp
0x00007c45:  mov    $0x7c00,%esp
0x00007c4a:  call   0x7cd1
copy

初始esp设为0x7c00,下一条call指令压栈后,对应的ebp即变为0x7bf8,与实验输出的最后一行相同。因此该位置即对应bootsam.S中的call bootmain,即bootmain函数。

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

中断向量表一个表项占用8个字节,其中第0至15位表示offset的低16位,第48至63位表示offset的高16位。第16至31位表示段选择子。两者结合用于表示中断处理代码的入口地址。

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

idt_init函数代码如下:

void
idt_init(void) {
    extern uintptr_t __vectors[];
    int i;
    for(i=0;i<sizeof(idt)/sizeof(struct gatedesc);i++){
        SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL);  //调用SETGATE宏和__vectors[],依序设置IDT各项
    }
    SETGATE(idt[T_SWITCH_TOK],0,GD_KTEXT,__vectors[T_SWITCH_TOK],DPL_USER);  //设置用户态切换到内核态对应表项
    lidt(&idt_pd);
}
copy

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

trap函数中时钟中断部分代码如下:

case IRQ_OFFSET + IRQ_TIMER:
        ticks++; //每次全局变量ticks+1
        if(ticks%TICK_NUM==0)  //每TICK_NUM=100个ticks,执行print_ticks()函数输出显示
            print_ticks();
        break;
copy
最新评论
暂无评论~