操作系统Ucore Lab1

采用的系统是官方给的Ubuntu镜像。

练习一 理解通过make生成执行文件的过程

关于Makefile的详细介绍见
https://blog.csdn.net/weixin_38391755/article/details/80380786

问题1:OS镜像文件ucore.img是如何一步一步生成的(需要比较详细地解释Makefile中的每一条相关命令和命令参数的含义,以及说明命令导致的结果)?

首先转到Lab1对应的目录下,先清空make:
在这里插入图片描述
然后执行 make “V=” 了解make执行了哪些命令:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

查看Makefile文件,找到如下代码:
在这里插入图片描述

发现生成需要ucore.img需要生成kernel和bootblock。
1.编译生成bin/kernel所需的文件:
生成这些.o文件的相关Makefile代码为:

$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))

1)init.c是OS的初始化启动代码,把init.c编译为名为init.o的中间目标文件(-c参数表示生成init.o的目标文件,-o表示生成可执行文件。gcc -c a.c -o a.o表示把源文件a.c编译成指定文件名a.o的中间目标文件。)

gcc -c kern/init/init.c -o obj/kern/init/init.o

2)把readline.c编译为名为readline的中间目标文件:

gcc -c kern/libs/readline.c -o obj/kern/libs/readline.o

3)编译stdio.c生成stdio.o:

gcc -c kern/libs/stdio.c -o obj/kern/libs/stdio.o

4)kdebug.c提供源码和二进制对应关系的查询功能,用于显示调用栈关系。编译kdebug.c生成kdebug.o:

gcc  -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o    

5)panic.c提供了panic函数,便于在发现错误后,调用kernel monitor。编译panic.c生成panic.o:

gcc  -c kern/debug/panic.c -o obj/kern/debug/panic.o

6)clock.c实现了对时钟控制器8253的初始化操作。编译clock.c生成panic.o:

gcc  -c kern/driver/clock.c -o obj/kern/driver/clock.o

7)kmonitor.c实现提供动态分析命令的kernel monitor,便于在ucore出现bug或问题之后,能够进入kernel monitor中,查看当前调用关系。编译kmonitor.c生成kmonitor.o:

gcc  -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o

8)console.c实现了对串口和键盘的中断方式的处理操作。将console.c编译生成console.o:

gcc -c kern/driver/console.c -o obj/kern/driver/console.o

9)intr.c实现了通过设置CPU的Eflags来屏蔽和使能中断的函数。编译intr.c生成intr.o:

gcc -c kern/driver/intr.c -o obj/kern/driver/intr.o

10)picirq.c实现了对控制中断器8259A的初始化和使能操作。编译picriq.c生成picriq.o:

gcc -c kern/driver/picirq.c -o obj/kern/driver/picirq.o

11)vectors.S包括256个中断服务例程的入口地址和第一步初步处理时先。此文件是由tools/vector.c在编译ucore期间动态生成的。编译voctor.S生成vector.o:

gcc -c kern/trap/vectors.S -o obj/kern/trap/vectors.o

12)trapentry.S紧接着第一步初步处理后,进一步完成第二步初步处理;并且又恢复中断上下文的处理,即中断处理完毕后的返回准备操作。编译trapentry.S生成trapentry.o:

gcc -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o

13)trap.c紧接着第二步初步处理后,继续完成具体的各种中断处理操作。编译trap.c生成trap.o:

gcc -c kern/trap/trap.c -o obj/kern/trap/trap.o
  1. pmm.c设定ucore操作系统在段机制中要用到的全局变量:任务状态栏ts,全局描述符表gdt[],加载全局描述符表寄存器的函数lgdt,临时的内核栈stack(),以及对全局描述符表和任务状态段的初始化函数gdt_init。编译pmm.c生成pmm.o:
gcc -c kern/mm/pmm.c -o obj/kern/mm/pmm.o

15)编译printfmt.c生成printfmt.o:

gcc -c libs/printfmt.c -o obj/libs/printfmt.o

16)编译string.c生成string.o:

gcc -c libs/string.c -o obj/libs/string.o

2.链接生成bin/kernel:
生成kernel需要第一步所生成的那些.o文件和kernel.ld,而kernel.ld已经存在。生成kernel的Makefile相关代码为:

# 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)

make执行的对应的命令如下:
在这里插入图片描述

其中ld命令是关键,用于把目标代码文件连接为可执行文件或者库文件:
-m模拟指定的连接器elf_i386;
-T指定命令文件为tools/kernel.ld,即让连接器使用指定的该脚本;
-o指定输出文件名字为kernel。

3.生成bootblock
在生成bootblock之前需要生成bootasm.o、bootmain.o、sign:
1)生成bootasm.o需要bootasm.S,bootasm.S定义并实现bootloader最先执行的函数start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用了bootmain.c中的bootmain函数。实际命令为:

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

关键的参数:
-ggdb:生成可供gdb使用的调试信息。这样才能用qemu+gdb来调试bootloader or ucore;
-m32:生成适用于32位环境的代码。我们用的模拟硬件是32bit的80386,所以ucore也要是32位的软件;
-gstabs:生成stabs格式的调试信息。这样要ucore的monitor可以显示出便于开发者阅读的函数调用栈信息
-nostdinc:不使用标准库。标准库是给应用程序用的,我们是编译ucore内核,OS内核是提供服务的,所以所有的服务要自给自足。
-fno-stack-protector:不生成用于检测缓冲区溢出的代码。
-Os:为减小代码大小而进行优化。根据硬件spec,主引导扇区只有512字节,我们写的简单bootloader的最终大小不能大于510字节。
-I

:添加搜索头文件的路径。
-Wall:产生尽可能多的警告信息。
-fno-builtin:除非用__builtin_前缀,否则不进行builtin函数的优化

2)生成bootmain.o需要bootmain.c,bootmain.c定义并实现了bootmain函数,实现了通过屏幕、串口和并口显示字符串,bootmain函数加载ucore操作系统到内存,然后跳到ucore的入口处执行。实际命令为:

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

3)生成sign需要sign.c,sign.c是一个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

关键的参数:
-I

:添加搜索头文件的路径。
-Wall:产生尽可能多的警告信息。
-O2:优化程序。
-g:生成可供gdb使用的调试信息。

  1. 由bootasm.o,bootmain.o,sign生成bootblock.o:
    实际命令为:
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 \
   obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o

关键的参数:
-m:模拟指定的连接器为elf_i386;
-N:指定读取/写入文本和数据段;
-e:使用指定的符号start作为程序的初始执行点;
-Ttext:使用指定的地址0x7C00作为文本段的起始点;
-nonstdlib:不使用标准库。

5) 拷贝二进制代码bootblock.o到bootblock.out:

objcopy -S -O binary obj/bootblock.o obj/bootblock.out

关键的参数:
-S:移除所有符号和重定位信息
-O :指定输出格式

6)使用sign工具处理bootblock.out,生成bootblock:

bin/sign obj/bootblock.out bin/bootblock

5.生成ucore.img:

  1. 生成一个有10000个块的文件,每个块默认512字节,用0填充
dd if=/dev/zero of=bin/ucore.img count=10000
  1. 把bootblock中的内容写到第一个块
dd if=bin/bootblock of=bin/ucore.img conv=notrunc

3)从第二个块开始写kernel中的内容

dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc

其中dd命令的作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的 转换。第一条命令即将/dev/zero全盘数据,这里用0填充,备份到bin/下的 ucore.img文件。
关键参数:
if=文件名:输入文件名,缺省即标准输入;
of=文件名:输出文件名,缺省即标准输出;
seek=blocks:从输出文件开头跳过blocks个块后开始复制;
conv:用指定的参数转换文件,此处的参数notrunc为不截短输出文件。

问题2:一个被认为是符合规范的硬盘主引导扇区的特征是什么?
通常,我们将包含MBR引导代码的扇区称为主引导扇区。通常由3部分组成:
主引导程序(MBR,占446字节)、磁盘分区表项(占4×16个字节,负责说明磁盘上的分区情况)、结束标志(占2个字节,其值为AA55)。
因为sign.c是用于生成一个符合规范的硬盘主引导扇区,所以截取了sign.c中的部分代码并加以注释来更清晰地分析主引导分区的特征:

char buf[512];          //定义buff数组
memset(buf, 0, sizeof(buf));     //将buff数组初始化为0    
buf[510] = 0x55;
buf[511] = 0xAA;    //将buff数组最后两位初始化为0x55,0xAA

得出主引导扇区的特征:
大小为512个字节;
第510个字节为0x55;
第511个字节为0xAA;
其余字节为0。

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页