make qemu 背后发生了什么
make qemu
当我们执行make qemu的时候,实际上是在执行GNUMakefile中的
qemu: $(IMAGES) pre-qemu
$(QEMU) $(QEMUOPTS)
#上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。
#"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。
#每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。下面就详细讲解,每条规则的这三个组成部分。
#一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象。除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)
#例如:
#clean:
# rm *.o
#上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于"伪目标 ",作用是删除对象文件。
所以当我们执行make qemu的时候,实际上是在执行:
$(QEMU) $(QEMUOPTS)。
#其中
$(QEMU) = qemu-system-i386
$(QEMUOPTS) = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT)
所以当我们执行$(QEMU) $(QEMUOPTS)的时候,实际上是在执行:
qemu-system-i386 -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT)
kernel.img
其中,$(OBJDIR)/kern/kernel.img是一个尚未制作完成的文件,当用到他的时候才会开始制作,具体过程如下(完整文件见MIT6.828-Lab1-kern/Makefrag及注释 - Pril - 博客园 (cnblogs.com)):
# How to build the kernel disk image
#构建kernel.img需要首先准备好$(OBJDIR)/kern/kernel和$(OBJDIR)/boot/boot
#dd命令可参考《Linux in Action》4.5节
#第一条dd命令:向$(OBJDIR)/kern/kernel.img中写入10000block个0x0字节(也可以理解为创建一个10000个block的镜像文件kernel.img~,每个block的大小可以有ibs选项设置,默认是512bytes)
#第二条dd命令:将编译出来的boot elf文件加载到kernel.img~的第一个block中
#第三条dd命令:第三步是将编译出来的kernel elf文件从第二个block开始加载到kernel.img中(seek=blocks:从输出文件开头跳过blocks个块后再开始复制。)
#第四条mv命令:将$(OBJDIR)/kern/kernel.img~改名为$(OBJDIR)/kern/kernel.img
$(OBJDIR)/kern/kernel.img: $(OBJDIR)/kern/kernel $(OBJDIR)/boot/boot
@echo + mk $@
$(V)dd if=/dev/zero of=$(OBJDIR)/kern/kernel.img~ count=10000 2>/dev/null
$(V)dd if=$(OBJDIR)/boot/boot of=$(OBJDIR)/kern/kernel.img~ conv=notrunc 2>/dev/null
$(V)dd if=$(OBJDIR)/kern/kernel of=$(OBJDIR)/kern/kernel.img~ seek=1 conv=notrunc 2>/dev/null
$(V)mv $(OBJDIR)/kern/kernel.img~ $(OBJDIR)/kern/kernel.img
all: $(OBJDIR)/kern/kernel.img
$(OBJDIR)/kern/kernel(ELF文件)
从注释可知$(OBJDIR)/kern/kernel.img主要由$(OBJDIR)/boot/boot和$(OBJDIR)/kern/kernel组成,而且$(OBJDIR)/kern/kernel是一个ELF文件,其构成代码如下:
# How to build the kernel itself
#第一条命令:使用加号修饰符让命令始终执行。命令行执行时不受到 make 的 -n -t -q 三个参数的影响,忽略这三个参数。
#ld命令是二进制工具集GNU Binutils的一员,是GNU链接器,用于将目标文件与库链接为可执行程序或库文件。ld [options] <objfile...>
#第二条命令:
#$(LD) -o $(KERN_LDFLAGS):生成一个名为$@(obj/kern/kernel)的文件的文件,如何生成:通过链接$(KERN_OBJFILES),$(GCC_LIB)和二进制文件-b binary $(KERN_BINFILES)
# $(KERN_BINFILES)在lab1定义为空,但是在lab3不是
#$(KERN_LDFLAGS):-m elf_i386 -T kern/kernel.ld -nostdlib
#ld -m elf_i386:输出为 elf 的目标格式,386是目标平台
#ld -T kern/kernel.ld:使用kernel.ld作为链接器脚本。这个脚本替换了ld的默认链接器脚本
#ld -nostdlib:不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。这个选项常用于编译内核、bootloader等程序,它们不需要启动文件、标准库文件。
#第三条命令:
#OBJDUMP -S 显示混合了反汇编的源代码
#这条主要作用是反汇编obj/kern/kernel,生成kernel.asm文件
#第四条命令:
#nm命令是linux下针对某些特定文件的分析工具,能够列出库文件(.a、.lib)、目标文件(*.o)、可执行文件的符号表
#-n 、-v 或 --numeric-sort:按符号对应地址的顺序排序,而非按符号名的字符顺序。
#分析obj/kern/kernel,生成obj/kern/kernel.sym文件
$(OBJDIR)/kern/kernel: $(KERN_OBJFILES) $(KERN_BINFILES) kern/kernel.ld \
$(OBJDIR)/.vars.KERN_LDFLAGS
@echo + ld $@
$(V)$(LD) -o $@ $(KERN_LDFLAGS) $(KERN_OBJFILES) $(GCC_LIB) -b binary $(KERN_BINFILES)
$(V)$(OBJDUMP) -S $@ > $@.asm
$(V)$(NM) -n $@ > $@.sym
根据注释可以知道kernel主要由$(KERN_OBJFILES),$(GCC_LIB)和二进制文件-b binary $(KERN_BINFILES)通过链接命令ld链接而成,链接器脚本是kern/kernel.ld。
所以接下来我们分析$(KERN_OBJFILES),$(GCC_LIB)和二进制文件$(KERN_BINFILES)
$(KERN_OBJFILES)
在最后面添加输出语句 echo $(KERN_OBJFILES),然后make -n obj/kern/kernel,即可以看见这个变量的内容:
obj/kern/entry.o obj/kern/entrypgdir.o obj/kern/init.o obj/kern/console.o obj/kern/monitor.o obj/kern/pmap.o obj/kern/env.o obj/kern/kclock.o obj/kern/printf.o obj/kern/trap.o obj/kern/trapentry.o obj/kern/syscall.o obj/kern/kdebug.o obj/kern/printfmt.o obj/kern/readline.o obj/kern/string.o
其实$(KERN_OBJFILES)是在KERN_SRCFILES的基础改变而来的:
KERN_SRCFILES := kern/entry.S \
kern/entrypgdir.c \
kern/init.c \
kern/console.c \
kern/monitor.c \
kern/pmap.c \
kern/env.c \
kern/kclock.c \
kern/picirq.c \
kern/printf.c \
kern/trap.c \
kern/trapentry.S \
kern/sched.c \
kern/syscall.c \
kern/kdebug.c \
lib/printfmt.c \
lib/readline.c \
lib/string.c
通过列三个语句,我们就可以实现KERN_SRCFILES的改造,从而得到$(KERN_OBJFILES)
#OBJDIRS += kern
#$(patsubst a,b,c)是调用make的内置函数patsubst,功能是把文本c中的模式a替换为b
#第一条命令:将KERN_SRCFILES中的.c形式的字符串全部变成kern/%.o的字符串形式,例如lib/string.c =》obj/lib/string.o
#第二条命令:将KERN_OBJFILES(由KERN_SRCFILES转换而来)中的.S形式的字符串全部变成obj/%.o的字符串形式,例如obj/entry.S =》obj/kern/entry.o
#第三条命令:将KERN_OBJFILES(由KERN_SRCFILES转换而来)中的obj/lib/%形式的字符串全部变成obj/kern/%的字符串形式,例如kern/kern/entry.o =》 kern/lib/entry.o
#总结:就是将KERN_SRCFILES中的“ %/%.c ” 或者 “ %/%.S ”全部变成 obj/kern/%.o (这里的改变都是指字符串的改变)
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES))
KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES))
但是问题来了,我们这样做只是实现了字符串上的改变,例如我们只是将kern/entry.S改成了obj/kern/entry.o,并没有真正实现编译,$(OBJDIR)/kern/kernel引用$(KERN_OBJFILES)的时候,只引用了类似obj/kern/entry.o这样的字符串,引用一个字符串又有什么意义呢?
我们真正想做的是,当我们引用obj/kern/entry.o,实际是在引用obj/kern中的entry.o文件,这个文件由kern/entry.S编译而来。
于是,在kern/Makefrag中,有这样一个函数,它的作用就是实现上述功能:
# How to build kernel object files
#$(@D)示"$@"的目录部分(不以斜杠作为结尾) ,如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录) 。这里是$(OBJDIR)/kern
#CC := $(GCCPREFIX)gcc -pipe #正常运行的话结果是i386-jos-elf-gcc -pipe
#makefile同名目标处理:合并依赖
#编译kern/,lib/下的.c,.S文件,生成的.o文件放在obj/kern下
#在调用$(KERN_OBJFILES)的时候将会调用到$(OBJDIR)/kern/%.o
$(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.KERN_CFLAGS
@echo + cc $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: kern/%.S $(OBJDIR)/.vars.KERN_CFLAGS
@echo + as $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: lib/%.c $(OBJDIR)/.vars.KERN_CFLAGS
@echo + cc $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(KERN_OBJFILES)总结:就是$(KERN_OBJFILES)中的所有文件编译后的集合,用于制作kernel elf文件。
$(GCC_LIB)
如法炮制,在最后一行加上@echo $(GCC_LIB),可以看到echo $(GCC_LIB)的值是
/usr/local/lib/gcc/i386-jos-elf/4.6.4/libgcc.a
实际上$(GCC_LIB)的值由以下命令得出:
GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name)
但是这个命令暂未看懂。
libgcc.a是GCC的一个库文件,用来对付GCC在多平台上运行时的差异问题。
$(KERN_BINFILES)
二进制文件,lab1中这个变量定义为空,可以参考lab3中的定义
KERN_BINFILES := user/hello \
user/buggyhello \
user/buggyhello2 \
user/evilhello \
user/testbss \
user/divzero \
user/breakpoint \
user/softint \
user/badsegment \
user/faultread \
user/faultreadkernel \
user/faultwrite \
user/faultwritekernel
估计是用户定义的函数。
$(OBJDIR)/boot/boot(ELF文件)
由上文可知,在构建kernel.img的时候,除了用到了$(OBJDIR)/kern/kernel文件之外,还用到了$(OBJDIR)/boot/boot文件。boot

浙公网安备 33010602011771号