- 操作系统镜像文件 ucore.img 是如何一步一步生成的?(需要比较详细地解释 Makefile 中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
大致流程:
使用make V=
命令,由终端的输出结果得知:makefile先调用了gcc,将ucore的源代码编译为目标文件也就是.o文件,再使用ld命令将目标文件链接成可执行程序,最后使用dd命令将bootloader放进一个虚拟硬盘中,生成虚拟硬盘ucore.img.Qemu再在硬盘中的数据的基础上来执行相应的代码。
详细地解释 Makefile 中每一条相关命令和命令参数的含义,以及说明命令导致的结果:
makefile的基本命令与参数及其作用:
- . 表示了所有文件;
- ? 表示任意单个字符;
- […] 表示一个字符类;
- [^…] 表示相反的字符类。
- ~ 表示当前用户的/home路径,~加用户名可以表示该用户的/home路径。
- $@ 目标文件的文件名;
- $% 仅当目标文件为归档成员文件(.lib 或者 .a)时,显示文件名,否则为空;
- $< 依赖(prerequisite)列表里面的第一个文件名;
- $? 所有在prerequisite列表里面比当前目标新的文件名,用空格隔开;
- $^ 所有在prerequisite列表中的文件,用空格隔开; 如果有重复的文件名(包含扩展名),会自动去除重复;
- + 与 + 与 +与^相似,也是prerequisite列表中的文件名用空格隔开,不同的是这里包含了所有重复的文件名;
- $* 显示目标文件的主干文件名,不包含后缀部分。
此外,上面的每个变量都带有两个不同的变种,用于适应不同种类的make。分别是在后面附加一个“D”或者“F”
-f :执行make的时候,带上-f <文件名>参数,来指定make命令从哪里读取makefile文件;而如果我们不显式指定,则make就会在当前目录下依次查找名字为GNUmakefile, makefile,和 Makefile的文件来作为其makefile文件.
- :让make忽略该命令的错误,例如,通常在Makefile中使用-include来代替include,忽略由于包含文件不存在或者无法创建时的错误提示,make继续执行。
- @ :关闭回显。不显示命令,只显示结果.而make参数-s或–slient则是禁止所有执行命令的显示.
- $(shell pwd) :获取当前目录的绝对路径 例如:-include $(shell pwd)/src/C_SRC
- ?= :变量在之前没有赋值的情况下才会对这个变量进行赋值.
- := :直接展开定义变量.
- = :递归展开,可能陷入无限循环.
由上面的截图我们知道,makefile文件最后调用了dd命令分别将数据,bootblock,kernel文件放入虚拟硬盘中形成ucore.img操作系统镜像文件。
在makefile文件中对应的代码为:
# create ucore.img
UCOREIMG := $(call totarget,ucore.img) //先指定目标文件名为ucore.img
$(UCOREIMG): $(kernel) $(bootblock) //ucore.img的依赖文件为kernel和bootblock
$(V)dd if=/dev/zero of=$@ count=10000 //下面是命令,用于将数据,bootblock,kernel放到虚拟硬盘中
$(V)dd if=$(bootblock) of=$@ conv=notrunc
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img) //指定那个名称ucore.img
下面是生成bootblock的代码:
# create bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
bootblock = $(call totarget,bootblock)
$(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)
@$(OBJDUMP) -t $(call objfile,bootblock) | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,bootblock)
@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)
@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
$(call create_target,bootblock)
由上述bootblock生成代码我们知道,bootblock依靠bootasm.o,bootmain.o,sign
# create 'sign' tools
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)
- ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00
- obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
- -m 模拟为i386上的连接器
- -nostdlib 不使用标准库
- -N 设置代码段和数据段均可读写
- -e 指定入口
- -Ttext 制定代码段开始位置
- 拷贝二进制代码bootblock.o到bootblock.out
- objcopy -S -O binary obj/bootblock.o obj/bootblock.out
- -S 移除所有符号和重定位信息
- -O 指定输出格式
- 使用sign工具处理bootblock.out,生成bootblock
- bin/sign obj/bootblock.out bin/bootblock
生成kernel文件的代码:
# -------------------------------------------------------------------
# kernel
KINCLUDE += kern/debug/ \
kern/driver/ \
kern/trap/ \
kern/mm/
KSRCDIR += kern/init \
kern/libs \
kern/debug \
kern/driver \
kern/trap \
kern/mm
KCFLAGS += $(addprefix -I,$(KINCLUDE))
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))
KOBJS = $(call read_packet,kernel libs)
# 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)
$(call create_target,kernel)
- 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
特征:
- 只有512字节。
- 最后一个字节为:0xAA
- 倒数第二个字节为:0x55