ucore实验_lab1

ucore_lab1

练习一

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

cd home/moocos/ucore_lab/labcodes_answer/lab1_result/

(2)执行make命令

make clean

然后输入

make "V="

得到了以下信息:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
其中生成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


生成bootblock的代码为:

ld -m    elf_i386 -nostdlib -N -e start -Ttext 0x7C00 
obj/boot/bootasm.o 
obj/boot/bootmain.o 
-o obj/bootblock.o

2 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
(1) 大小为512字节
(2) 多余的空间填0
(3 )第510个(倒数第二个)字节是0x55,
(4) 第511个(倒数第一个)字节是0xAA。

练习二

1 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行
(1) 首先通过make qemu指令运行出等待调试的qemu虚拟机,然后再打开一个终端,通过下述命令连接到qemu虚拟机:

gdb
target remote 127.0.0.1:1234

(2)输入si命令,单步跟踪。
(3)在初始化位置0x7c00设置实地址断点,测试断点正常。
(4) 查看反汇编代码

x/2i $pc

(5)使用meld对比bootasm.S和bootlock.asm的代码对bootasm.S和 bootblock.asm进行比较

meld /home/moocos/moocos/ucore_lab/labcodes/lab1/boot/bootasm.S /home/moocos/moocos/ucore_lab/labcodes/lab1/obj/bootblock.asm

练习三

1、BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。
(1)关闭中断,将各个段寄存器重置

.globl start
start:
.code16                                             # 使用16位模式编译
    cli                                             # 禁用中断
    cld                                             # 清除方向标志
    # 建立重要的数据段寄存器(DS,ES,SS)。
    xorw %ax, %ax                                   # ax清0
    movw %ax, %ds                                   # ds清0
    movw %ax, %es                                   # es清0
    movw %ax, %ss                                   # ss清0


(2)开启A20

seta20.1:
    inb $0x64, %al    # 读取状态寄存器,等待8042键盘控制器闲置
    testb $0x2, %al   # 判断输入缓存是否为空
    jnz seta20.1

    movb $0xd1, %al    # 0xd1表示写输出端口命令,参数随后通过0x60端口写入
    outb %al, $0x64   

seta20.2:
    inb $0x64, %al    
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al   # 通过0x60写入数据11011111 即将A20置1
    outb %al, $0x60   

(3)加载GDT表,将CR0的第0位置1,长跳转到32位代码段,重装CS和EIP,转到保护模式完成,进入boot主方法。

lgdt gdtdesc
movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0
    ljmp $PROT_MODE_CSEG, $protcseg
      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
    call bootmain

练习四

通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,

  • bootloader如何读取硬盘扇区的?
  • bootloader是如何加载ELF格式的OS?

1 bootloader如何读取硬盘扇区

等待磁盘准备好;
发出读取扇区的命令;
等待磁盘准备好;
把磁盘扇区数据读到指定内存。
/* file header */
struct elfhdr {
    uint32_t e_magic;     // must equal ELF_MAGIC elf的魔数
    uint8_t e_elf[12];
    uint16_t e_type;      // 1=relocatable, 2=executable, 3=shared object, 4=core image
    uint16_t e_machine;   // 3=x86, 4=68K, etc.
    uint32_t e_version;   // file version, always 1
    uint32_t e_entry;     // entry point if executable 入口地址
    uint32_t e_phoff;     // file position of program header or 0第一个programheader的位置,
                          //这是个结构体,通过这个指针可以找到结构体数组的位置结合e_phnum可以取得所有ph结构体
    uint32_t e_shoff;     // file position of section header or 0
    uint32_t e_flags;     // architecture-specific flags, usually 0
    uint16_t e_ehsize;    // size of this elf header
    uint16_t e_phentsize; // size of an entry in program header
    uint16_t e_phnum;     // number of entries in program header or 0
    uint16_t e_shentsize; // size of an entry in section header
    uint16_t e_shnum;     // number of entries in section header or 0
    uint16_t e_shstrndx;  // section number that contains section name strings
};

/* program section header */
struct proghdr {
    uint32_t p_type;   // loadable code or data, dynamic linking info,etc.
    uint32_t p_offset; // file offset of segment
    uint32_t p_va;     // virtual address to map segment
    uint32_t p_pa;     // physical address, not used
    uint32_t p_filesz; // size of segment in file
    uint32_t p_memsz;  // size of segment in memory (bigger if contains bss)
    uint32_t p_flags;  // read/write/execute bits
    uint32_t p_align;  // required alignment, invariably hardware page size
};

2 bootloader是如何加载ELF格式的OS
从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用。
校验e_magic字段。
根据偏移量分别把程序段的数据读取到内存中。

bootmain(void) {
    ..........
    //首先判断是不是ELF
    if (ELFHDR->e_magic != ELF_MAGIC) {
        goto bad;                 
    }
    struct proghdr *ph, *eph;

    //获得第一个程序头结构体的地址
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
    eph = ph + ELFHDR->e_phnum;     //最后一个程序头结构体的地址

    //按照程序头表的描述,将ELF文件中的数据载入内存
    for (; ph < eph; ph ++) {
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }
    //根据ELF头表中的入口信息,找到内核的入口并开始运行 
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
    ..........
}

练习五

我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。
1、函数堆栈
(1)函数调用栈结构如下

+|  栈底方向        | 高位地址
 |    ...        |
 |    ...        |
 |  参数3        |
 |  参数2        |
 |  参数1        |
 |  返回地址        |
 |  上一层[ebp]    | <-------- [ebp]
 |  局部变量        |  低位地址

(2)函数调用可以描述为以下几个步骤:
参数入栈:将参数从右向左依次压入栈中。
返回地址入栈:call指令内部隐含的动作,将call的下一条指令入栈 、代码区跳转:跳转到被调
用函数入口处 、函数入口处前两条指令,为本地编译器自动插入的指令,执行这两条指令
将ebp的值入栈,即调用之前的那个栈帧的底部
将当前的esp值赋给ebp,当前的esp即为新的函数的栈帧的底部,保存到ebp中
给新栈帧分配空间(把ESP减去所需空间的大小)。
(3)函数返回的大概步骤:
保存返回值,通常将函数返回值保存到寄存器EAX中。
将当前的ebp赋给esp
从栈中弹出一个值给ebp
弹出返回地址,从返回地址处继续执行
(4)具体函数

print_stackframe(void) {
	int i,j;
	uint32_t ebp=read_ebp();
	uint32_t eip=read_eip();
    for(i=0;i<STACKFRAME_DEPTH&&ebp!=0;i++){
    	cprintf("ebp:0x%08x eip:0x%08x\n",ebp,eip); 
        uint32_t *args=(uint32_t *)ebp+2;
		cprintf("参数:");
        for(j=0;j<4;j++){
            cprintf("0x%08x ", args[j]);
        }
        cprintf("\n");
        print_debuginfo(eip-1);
        eip=((uint32_t *)ebp)[1];
        ebp=((uint32_t *)ebp)[0];
	}
}
read_eip(void) {
    uint32_t eip;
    asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); //内联汇编,读取(ebp-4)的值到变量eip
    return eip;
}static inline uint32_t
read_ebp(void) {
    uint32_t ebp;
    asm volatile ("movl %%ebp, %0" : "=r" (ebp)); //内联汇编,读取edp寄存器的值到变量ebp
    return ebp;
}
void
print_debuginfo(uintptr_t eip) {
    struct eipdebuginfo info;
    if (debuginfo_eip(eip, &info) != 0) {
        cprintf("    <unknow>: -- 0x%08x --\n", eip);
    }
    else {
        char fnname[256];
        int j;
        for (j = 0; j < info.eip_fn_namelen; j ++) {
            fnname[j] = info.eip_fn_name[j];
        }
        fnname[j] = '\0';
        cprintf("    %s:%d: %s+%d\n", info.eip_file, info.eip_line,
                fnname, eip - info.eip_fn_addr);
    }
}

练习六

1、中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
中断描述符表一个表项占8字节。其中015位和4863位分别为offset的低16位和高16位。16~31位为段选择子。通过段选择子获得段基址,加上段内偏移量即可得到中断处理代码的入口。

/* Gate descriptors for interrupts and traps */
struct gatedesc {
    unsigned gd_off_15_0 : 16;        // low 16 bits of offset in segment
    unsigned gd_ss : 16;            // segment selector
    unsigned gd_args : 5;            // # args, 0 for interrupt/trap gates
    unsigned gd_rsv1 : 3;            // reserved(should be zero I guess)
    unsigned gd_type : 4;            // type(STS_{TG,IG32,TG32})
    unsigned gd_s : 1;                // must be 0 (system)
    unsigned gd_dpl : 2;            // descriptor(meaning new) privilege level
    unsigned gd_p : 1;                // Present
    unsigned gd_off_31_16 : 16;        // high bits of offset in segment
};

2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。注意除了系统调用中断(T_SYSCALL)以外,其它中断均使用中断门描述符,权限为内核态权限;而系统调用中断使用异常,权限为陷阱门描述符。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
3.请编程完善trap.c中的中断处理函数trap在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用 print_ticks子程序,向屏幕上打印一行文字100 ticks。
在kern/driver/clock.h中声明了ticks为extern。

 ticks++;
            if((ticks%TICK_NUM)==0){
                    print_ticks();
            }
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值