深圳市航顺芯片技术研发有限公司
直播中

刘英

7年用户 1114经验值
私信 关注
[问答]

ucore lab1有什么?实现功能后能做什么

ucore lab1有什么?实现功能后能做什么?操作系统镜像文件ucore.img是如何一步一步生成的?

回帖(1)

李诗晴

2021-10-20 17:12:00
  一个bootloader
  可以切换到X86保护模式
  能够读磁盘并加载ELF执行文件格式
  显示字符
  一个OS
  可以处理时钟中断
  显示字符
  项目组成
  lab1的整体目录结构如下所示:
  其中一些比较重要的文件说明如下:
  bootloader部分
  boot/bootasm.S :定义并实现了bootloader最先执行的函数start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数。
  boot/bootmain.c:定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串。bootmain函数加载ucore操作系统到内存,然后跳转到ucore的入口处执行。
  boot/asm.h:是bootasm.S汇编文件所需要的头文件,主要是一些与X86保护模式的段访问方式相关的宏定义。
  ucore操作系统部分
  系统初始化部分:
  kern/init/init.c:ucore操作系统的初始化启动代码
  内存管理部分:
  kern/mm/memlayout.h:ucore操作系统有关段管理(段描述符编号、段号等)的一些宏定义
  kern/mm/mmu.h:ucore操作系统有关X86 MMU等硬件相关的定义,包括EFLAGS寄存器中各位的含义,应用/系统段类型,中断门描述符定义,段描述符定义,任务状态段定义,NULL段声明的宏SEG_NULL, 特定段声明的宏SEG,设置中 断门描述符的宏SETGATE(在练习6中会用到)
  kern/mm/pmm.[ch]:设定了ucore操作系统在段机制中要用到的全局变量:任务状态段ts,全局描述符表 gdt[],加载全局描述符表寄存器的函数lgdt,临时的内核栈stack0;以及对全局描述符表和任务状态段的初始化函数gdt_init
  外设驱动部分:
  kern/driver/intr.[ch]:实现了通过设置CPU的eflags来屏蔽和使能中断的函数;
  kern/driver/picirq.[ch]:实现了对中断控制器8259A的初始化和使能操作;
  kern/driver/clock.[ch]:实现了对时钟控制器8253的初始化操作;- kern/driver/console. [ch]:实现了对串口和键盘的中断方式的处理操作;
  中断处理部分:
  kern/trap/vectors.S:包括256个中断服务例程的入口地址和第一步初步处理实现。注意,此文件是由tools/vector.c在编译ucore期间动态生成的;
  kern/trap/trapentry.S:紧接着第一步初步处理后,进一步完成第二步初步处理;并且有恢复中断上下文的处理,即中断处理完毕后的返回准备工作;
  kern/trap/trap.[ch]:紧接着第二步初步处理后,继续完成具体的各种中断处理操作;
  内核调试部分:
  kern/debug/kdebug.[ch]:提供源码和二进制对应关系的查询功能,用于显示调用栈关系。其中补全print_stackframe函数是需要完成的练习。其他实现部分不必深究。
  kern/debug/kmonitor.[ch]:实现提供动态分析命令的kernel monitor,便于在ucore出现bug或问题后,能够进入kernel monitor中,查看当前调用关系。实现部分不必深究。
  kern/debug/panic.c | assert.h:提供了panic函数和assert宏,便于在发现错误后,调用kernel monitor。大家可在编程实验中充分利用assert宏和panic函数,提高查找错误的效率。
  公共库部分
  libs/defs.h:包含一些无符号整型的缩写定义。
  Libs/x86.h:一些用GNU C嵌入式汇编实现的C函数(由于使用了inline关键字,所以可以理解为宏)。
  工具部分
  Makefile和function.mk:指导make完成整个软件项目的编译,清除等工作。
  sign.c:一个C语言小程序,是辅助工具,用于生成一个符合规范的硬盘主引导扇区。
  tools/vector.c:生成vectors.S,此文件包含了中断向量处理的统一实现。
  编译方法
  首先下载lab1.tar.bz2,然后解压lab1.tar.bz2。在lab1目录下执行make,可以生成ucore.img(生成于bin目录下)。ucore.img是一个包含了bootloader或OS的硬盘镜像,通过执行如下命令可在硬件虚拟环境 qemu中运行bootloader或OS:
  $ make qemu 则可以得到如下显示界面*(仅供参考)*
  练习1
  理解通过make生成执行文件的过程。
  问题
  操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
  一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
  补充材料
  如何调试Makefile
  当执行make时,一般只会显示输出,不会显示make到底执行了哪些命令。
  如想了解make执行了哪些命令,可以执行:(记住,V是大写)
  $ make “V=” 要获取更多有关make的信息,可上网查询,并请执行
  $ man make 实验过程
  问题1
  操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
  首先,进入~/ucore_os_lab-master/labcodes_answer/lab1_result目录下
  执行make,并查看详情
  $ make “V=”
  观察输出结果
  (1)通过GCC编译器将Kernel目录下的.c文件编译成OBJ目录下的.o文件。
  为了生成ucore.img,首先需要生成bootblock、kernel
  为了生成bootblock,首先需要生成bootasm.o、bootmain.o、sign
  递归下去
  为了生成kernel,首先需要 kernel.ld init.o readline.o stdio.o kdebug.o
  递归下去
  问题2
  一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
  我们可以查看tools下的sign.c文件
  从sign.c的代码来看,一个磁盘主引导扇区只有512字节。且第510个(倒数第二个)字节是0x55,第511个(倒数第一个)字节是0xAA。
  练习2
  使用qemu执行并调试lab1中的软件。
  实验要求
  从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
  在初始化位置0x7c00设置实地址断点,测试断点正常。
  从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。
  自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
  提示:参考附录“启动后第一条执行的指令”,可了解更详细的解释,以及如何单步调试和
  查看BIOS代码。
  提示:查看 labcodes_answer/lab1_result/tools/lab1init 文件,用如下命令试试如何调试
  bootloader第一条指令:
  $ cd labcodes_answer/lab1_result/ $ make lab1-mon
  补充材料
  我们主要通过硬件模拟器qemu来进行各种实验。在实验的过程中我们可能会遇上各种各样的问题,调试是必要的。qemu支持使用gdb进行的强大而方便的调试。所以用好qemu和gdb是完成各种实验的基本要素。
  默认的gdb需要进行一些额外的配置才进行qemu的调试任务。qemu和gdb之间使用网络端口1234进行通讯。在打开qemu进行模拟之后,执行gdb并输入
  target remote localhost:1234 即可连接qemu,此时qemu会进入停止状态,听从gdb的命令。
  另外,我们可能需要qemu在一开始便进入等待模式,则我们不再使用make qemu开始系统的运行,而使用make debug来完成这项工作。这样qemu便不会在gdb尚未连接的时候擅自运行了。
  gdb的地址断点
  在gdb命令行中,使用b *[地址]便可以在指定内存地址设置断点,当qemu中的cpu执行到指定地址时,便会将控制权交给gdb。
  关于代码的反汇编
  有可能gdb无法正确获取当前qemu执行的汇编指令,通过如下配置可以在每次gdb命令行前强制反汇编当前的指令,在gdb命令行或配置文件中添加:
  define hook-stop x/i $pc end 即可
  gdb的单步命令
  在gdb中,有next, nexti, step, stepi等指令来单步调试程序,他们功能各不相同,区别在于单步的“跨度”上。
  next 单步到程序源代码的下一行,不进入函数。 nexti 单步一条机器指令,不进入函数。 step 单步到下一个不同的源代码行(包括进入函数)。 stepi 单步一条机器指令。 实验过程
  问题1
  从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
  直接看截图吧,就是玩玩gdb那几个命令而已
  
  这是直接拿答案的代码了,拿题目的是这样:
  (1)进入~/moocos/ucore_lab/labcodes/lab1/bin,即ucore.img所在文件夹。
  (2)输入指令:
  qemu -S -s -hda ucore.img -monitor stdio
  (3)打开gdb。
  (4)输入命令,使gdb与qemu通过1234端口进行通信,qemu会停止状态听从gbd命令。
  target remote 127.0.0.1:1234
  (5)输入命令,单步跟踪。
  si
  问题2
  在初始化位置0x7c00设置实地址断点,测试断点正常。
  (1)输入命令,设置断点。
  b *0x7c00
  (2)continue后,测试断点。
  c
  x/2i $pc
  运行结果:
  (gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 0x0000fff0 in ?? () (gdb) b *0x7c00 Breakpoint 1 at 0x7c00 (gdb) c Continuing. Breakpoint 1, 0x00007c00 in ?? () (gdb) x /2i $pc =》 0x7c00: cli 0x7c01: cld
  问题3
  从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。
  使用meld对比bootasm.S和bootlock.asm的代码
  meld /home/moocos/moocos/ucore_lab/labcodes/lab1/boot/bootasm.S /home/moocos/moocos/ucore_lab/labcodes/lab1/obj/bootblock.asm
  看就完了,是一样的
  
  问题4
  自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
  (略)
  补充
  在进入练习3前,再复习一下bootloader做了什么:
  切换到保护模式,启用分段机制 (修改A20地址线)
  读磁盘中ELF执行文件格式的ucore操作系统到内存
  显示字符串信息
  把控制权交给ucore操作系统
  保护模式下,有两个段表:GDT(Global Descriptor Table)和LDT(Local Descriptor Table),每一张段表可以包含8192 (2^13)个描述符,因而最多可以同时存在2 * 2^13 = 2^14 个段。虽然保护模式下可以有这么多段,逻辑地址空间看起来很大,但实际上段并不能扩展物理地址空间,很大程度上各个段的地址空间是相互重叠的。所谓的64TB(2^(14+32) =246)逻辑地址空间是一个理论值,没有实际意义。在32位保护模式下,真正的物理空间仍然只有232字节那么大。(注:在ucore lab中只用到了GDT,没有用LDT。)
  练习3
  分析bootloader进入保护模式的过程。
  实验要求
  BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。
  提示:需要阅读小节**“保护模式和分段机制”**和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:
  为何开启A20,以及如何开启A20
  如何初始化GDT表
  如何使能和进入保护模式
  实验过程
  打开lab1/boot/bootasm.S
  清理环境:关中断,重要的段寄存器清零
  开启A20(为了与最早的PC兼容,物理地址线绑定在了低20位,因此高于1MB的地址默认情况下会回零。 此代码撤消了此操作),通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间。
  设备和芯片的I/O端口操作实现,其实没有复杂的东西在里边 ,I/O端口操作主要是看一堆文档,把整个X86架构的PC机所有I/O端口记住,并记住它们每一个数据寄存器、命令寄存器等操作访问标准(也可以称之协议) ; 记住之后,整个过程中就是按标准使用I/O指令。
  in, out(只能与DX,AX,AL寄存器结合使用) ;
  下面的实现是提供给C使用,语法太晦涩,所以直接使用汇编实现。
  inb 从I/O端口读取一个字节(BYTE, HALF-WORD) ;
  outb 向I/O端口写入一个字节(BYTE, HALF-WORD);
  inw 从I/O端口读取一个字(WORD,即两个字节);
  outw 向I/O端口写入一个字(WORD,即两个字节) ;
  初始化GDT(全局描述表,Global Descriptor Table),一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
  进入保护模式:通过将cr0寄存器PE位(第0位)置1便开启了保护模式;通过长跳转更新cs的基地址;设置段寄存器,并建立堆栈(BIOS数据区:0x0000-0x7c00)
  call bootmain 使用引导程序GDT和段转换,使虚拟地址与物理地址相同,从而从实模式切换到保护模式,从而使有效内存映射在切换期间不会改变。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分