ucore-lab1-练习1report

时间:2023-03-09 08:36:58
ucore-lab1-练习1report

练习1 report

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

  1.   GNU make是一种代码维护工具,在大中型项目中,它将根据程序各个模块的更target新情况,自动地维护和生成目标代码。make命令执行时,需要一个Makefile文件,以告诉make命令需要怎样去编译和链接程序。使用Makefile文件时,有如下规则:(1)如果这个工程没有编译过,那么所有的c文件都要编译并链接;(2)如果这个工程的某几个c文件被修改,那么只编译被修改的c文件,并链接目标程序;(3)如果这个工程的头文件被改变了,那么需要编译引用这几个头文件的c文件,并链接目标程序。

    Makefile文件大致由target、prerequisites、command组成,target表示要生成的目标文件,prerequisites指要生成target所需要的文件或是目标,command是make需要执行的命令(shell命令),必须以[tab]开头。只要prerequisites中有一个以上的文件比文件要新,command的指令就会被执行,这就是Makefile的规则。

    2.    编译所有生成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

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

  3.链接生成bin/kernel:

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

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

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

其中ld命令是关键,用于把目标代码文件连接为可执行文件或者库文件:

-m模拟指定的连接器elf_i386;

-T指定命令文件为tools/kernel.ld,即让连接器使用指定的该脚本;

-o指定输出文件名字为kernel。

  4.生成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<dir>:添加搜索头文件的路径。

-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<dir>:添加搜索头文件的路径。

-Wall:产生尽可能多的警告信息。

-O2:优化程序。

-g:生成可供gdb使用的调试信息。

4) 由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 <bfdname>:指定输出格式

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

2) 把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

得出主引导扇区的特征:

  1. 大小为512个字节;
  2. 第510个字节为0x55;
  3. 第511个字节为0xAA;
  4. 其余字节为0。