当前位置:文档之家› 操作系统-ucore-lab1

操作系统-ucore-lab1

HUNAN UNIVERSITY操作系统实验报告目录一、内容 (2)二、目的 (2)三、实验设计思想和流程 (3)四、主要文件结构说明 (4)五、实验环境以及实验过程与结果分析(包含实验详细过程) (4)练习1:理解通过make生成执行文件的过程 (4)练习2:使用qemu执行并调试lab1中的软件。

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

(9)练习4:分析bootloader加载ELF格式的OS的过程。

(11)练习5:实现函数调用堆栈跟踪函数 (14)练习6:完善中断初始化和处理 (16)六、实验体会 (18)一、内容lab1中包含一个bootloader和一个OS。

这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。

而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。

为了实现lab1的目标,lab1提供了6个基本练习和1个扩展练习,要求完成实验报告。

二、目的操作系统是一个软件,也需要通过某种机制加载并运行它。

在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。

为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。

lab1 提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。

通过分析和实现这个bootloader和ucore OS,读者可以了解到:计算机原理CPU的编址与寻址: 基于分段机制的内存管理CPU的中断机制外设:串口/并口/CGA,时钟,硬盘Bootloader软件编译运行bootloader的过程调试bootloader的方法PC启动bootloader的过程ELF执行文件的格式和加载外设访问:读硬盘,在CGA上显示字符串ucore OS软件编译运行ucore OS的过程ucore OS的启动过程调试ucore OS的方法函数调用关系:在汇编级了解函数调用栈的结构和处理过程中断管理:与软件相关的中断处理外设管理:时钟三、实验设计思想和流程依照实验指导书完成了六个对应练习:练习1:理解通过make生成执行文件的过程练习2:使用qemu执行并调试lab1中的软件练习3:分析bootloader进入保护模式的过程练习4:分析bootloader加载ELF格式的OS的过程练习5:实现函数调用堆栈跟踪函数练习6:完善中断初始化和处理四、主要文件结构说明五、实验环境以及实验过程与结果分析(包含实验详细过程)实验环境为:LINUX_64系统。

练习1:理解通过make生成执行文件的过程1.操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)# create ucore.imgUCOREIMG := $(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)//为了生成bootblock,首先需要生成bootasm.o、bootmain.o、sign$(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)(1)通过GCC编译器将Kernel目录下的.c文件编译成OBJ目录下的.o文件。

(2)ld命令根据链接脚本文件kernel.ld将生成的*.o文件,链接成BIN目录下的kernel文件(3)通过GCC编译器将boot目录下的.c, .S文件以及tools目录下的sign.c文件编译成OBJ 目录下的*.o文件。

(4)ld命令将生成的*.o文件,链接成BIN目录下的bootblock文件。

(5)dd命令将dev/zero, bin/bootblock,bin/kernel 写入到bin/ucore.img注:/dev/zero文件代表一个永远输出 0的设备文件,使用它作输入可以得到全为空的文件。

因此可用来创建新文件和以覆盖的方式清除旧文件。

下面使用dd命令将从zero设备中创建一个10K大小(bs决定每次读写1024字节,count定义读写次数为10次),但内容全为0的文件。

dd 是 Linux/UNIX 下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。

2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?从以下代码可以看出:buf缓冲区最后两位为0x55和0xAA,并且需要扇区大小满足512字节。

(1) 编译过程:在解压缩后的ucore 源码包中使用make 命令即可。

例如lab1中:chy@laptop: ~/lab1$ make在lab1目录下的bin目录中,生成一系列的目标文件:ucore.img:被qemu访问的虚拟硬盘文件kernel: ELF格式的toy ucore kernel执行文,被嵌入到了ucore.img中bootblock: 虚拟的硬盘主引导扇区(512字节),包含了bootloader执行代码,被嵌入到了ucore.img中sign:外部执行程序,用来生成虚拟的硬盘主引导扇区还生成了其他很多文件,这里就不一一列举了。

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

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

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

2.3. 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。

(1)修改Makefile中debug段代码,使其默认执行如下命令:-S -s$(V)$(QEMU) -e “$(QEMU) –S –s –d in_asm –D $(BINDIR)/q.log -parallel stdio -hda $< -serial null “为了与qemu配合进行源代码级别的调试,需要先让qemu进入等待gdb调试器的接入并且还不能让qemu中的CPU执行,因此启动qemu的时候,我们需要使用参数-S、–s这两个参数来做到这一点。

修改gdbinit文件如下:执行make debug(2)q.log(3)q.log与bootasm.S和bootblock.asm中的代码相同。

4. 自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

修改debuginit如下:断点设置正常:练习3:分析bootloader进入保护模式的过程。

BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。

请分析bootloader是如何完成从实模式进入保护模式的。

//关中断和清除段数据:包括将flag置0和将段寄存器置0.globl startstart:.code16cli //关中断cld //清除方向标志xorw %ax, %ax //ax清0movw %ax, %ds //ds清0movw %ax, %es //es清0movw %ax, %ss //ss清0开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间。

seta20.1: # 等待8042键盘控制器不忙inb $0x64, %al #testb $0x2, %al #jnz seta20.1 #movb $0xd1, %al # 发送写8042输出端口的指令outb %al, $0x64 #seta20.1: # 等待8042键盘控制器不忙inb $0x64, %al #testb $0x2, %al #jnz seta20.1 #movb $0xdf, %al # 打开A20outb %al, $0x60 #初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可lgdt gdtdesc进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式movl %cr0, %eaxorl $CR0_PE_ON, %eaxmovl %eax, %cr0通过长跳转更新cs的基地址ljmp $PROT_MODE_CSEG, $protcseg.code32protcseg:设置段寄存器,并建立堆栈movw $PROT_MODE_DSEG, %axmovw %ax, %dsmovw %ax, %esmovw %ax, %fsmovw %ax, %gsmovw %ax, %ssmovl $0x0, %ebpmovl $start, %esp转到保护模式完成,进入boot主方法call bootmain练习4:分析bootloader加载ELF格式的OS的过程。

通过阅读bootmain.c,了解bootloader如何加载ELF文件。

通过分析源代码和通过qemu来运行并调试bootloader&OSbootloader如何读取硬盘扇区的?bootloader是如何加载ELF格式的OS?要了解bootloader是如何读取硬盘扇区的需要了解elf.h文件读取扇区static voidreadsect(void *dst, uint32_t secno) {waitdisk();outb(0x1F2, 1); // 设置读取扇区的数目为1outb(0x1F3, secno & 0xFF);outb(0x1F4, (secno >> 8) & 0xFF);outb(0x1F5, (secno >> 16) & 0xFF);outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);// 上面四条指令联合制定了扇区号// 在这4个字节线联合构成的32位参数中// 29-31位强制设为1// 28位(=0)表示访问"Disk 0"// 0-27位是28位的偏移量outb(0x1F7, 0x20); // 0x20命令,读取扇区waitdisk();insl(0x1F0, dst, SECTSIZE / 4); // 读取到dst位置,// 幻数4因为这里以DW为单位}/* 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 imageuint16_t e_machine; // 3=x86, 4=68K, etc.uint32_t e_version; // file version, always 1uint32_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 0uint32_t e_flags; // architecture-specific flags, usually 0uint16_t e_ehsize; // size of this elf headeruint16_t e_phentsize; // size of an entry in program headeruint16_t e_phnum; // number of entries in program header or 0uint16_t e_shentsize; // size of an entry in section headeruint16_t e_shnum; // number of entries in section header or 0uint16_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 segmentuint32_t p_va; // virtual address to map segmentuint32_t p_pa; // physical address, not useduint32_t p_filesz; // size of segment in fileuint32_t p_memsz; // size of segment in memory (bigger if contains bss)uint32_t p_flags; // read/write/execute bitsuint32_t p_align; // required alignment, invariably hardware page size };bootloader如何加载ELF格式的OS:在bootmain函数中,voidbootmain(void) {// 首先读取ELF的头部readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);// 通过储存在头部的幻数判断是否是合法的ELF文件if (ELFHDR->e_magic != ELF_MAGIC) {goto bad;}struct proghdr *ph, *eph;// ELF头部有描述ELF文件应加载到内存什么位置的描述表,// 先将描述表的头地址存在phph = (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文件0x1000位置后面的0xd1ec比特被载入内存0x00100000// ELF文件0xf000位置后面的0x1d20比特被载入内存0x0010e000// 根据ELF头部储存的入口信息,找到内核的入口((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();bad:outw(0x8A00, 0x8A00);outw(0x8A00, 0x8E00);while (1);}练习5:实现函数调用堆栈跟踪函数我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。

相关主题