“山寨,它不仅是个名词,更多是个动词。”
内核的Makefile谈不上博大精深,但也是构思精巧。层层编译,递归链接,最后才生成个zImage。
在此设计个比较通用的 Makefile,秉承"实践出真知"的精神,在山寨的过程中细细咀嚼。
--------
第一部分
--------
-->
我们的编译工具是:
CROSS_COMPILE ?= arm-linux-
为啥是个 "?=" ?
这叫条件赋值:只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。
我们不知到是否有个叫CROSS_COMPILE的环境变量已被定义,比如在~/.bashrc或~/.bash_profile里。“要以大局为重”,所以,用"?="。
等价于:
ifeq ($(origin FOO), undefined)
FOO=bar
endif
origin FOO:
使用系统环境变量的定义覆盖Makefile中的同名变量定义。
返回值有:undefined, default, environment, environment override。
-->
各种编译命令:
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
LDR = $(CROSS_COMPILE)ldr
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
-->
OK, 该部分结束,export它们:
export AS LD CC CPP AR NM LDR STRIP OBJCOPY OBJDUMP
--------
第二部分
--------
make参数:
MAKE = make -s -e
-s,关闭回显。
-e,使用系统环境变量的定义覆盖Makefile中的同名变量定义。
--------
第三部分
--------
根目录:
TOPDIR = $(shell pwd)
子目录:
SUBDIRS := kernel/
SUBDIRS += lib/
SUBDIRS += drivers/
“起始”目录: (也就是放start.S的地方)
STARTDIR := $(TOPDIR)/kernel/cpu
--------
第四部分
--------
最终输出文件:
OUT_BIN := g-boot.bin
OUT_ELF := g-boot.elf
最终输出文件组合前的目标文件:
SUBLIBS := $(addsuffix built-in.o,$(SUBDIRS))
组合过程:
$(OUT_BIN): $(OUT_ELF)
@$(OBJCOPY) -O binary $< $@
$(OUT_ELF): $(STARTDIR) $(SUBDIRS)
@$(LD) $(SUBLIBS) -o $@ -T$(LDSCRIPT)
==>
LDSCRIPT := $(TOPDIR)/g-boot.lds
--------
第五部分
--------
目标文件的递归生成过程:
从start开始,
执行该文件夹下的 Makefile, 文件内的start为入口:
$(STARTDIR):
@$(MAKE) -C $@ start
STARTDIR/Makefile主要分两部分:
start: $(start_obj)
include $(TOPDIR)/config.mk
在进入config.mk前,要设置obj-y,好比进入函数前的参数。
进入config.mk,生成该文件夹下的目标文件 built-in.o。
--------
第六部分
--------
built-in.o由当前目录下的各个目标文件和库及子目录下的built-in.o链接而成。
all: $(SUBDIRS) $(OBJS)
$(LD) -r -o built-in.o $(OBJS) $(SUBLIBS)
$(SUBDIRS):
@$(MAKE) -C $@ all
先进入子目录编译。
由此可以看出,这是个由底到顶的编译过程,好比一棵树,从叶子结点编译后链接为父结点,然后父结点再和兄弟链接,循环递归,最后生成根结点目标文件的过程。
==>
OBJS := $(notdir $(obj-y))
SUBDIRS := $(subst ./,,$(dir $(obj-y)))
SUBLIBS := $(addsuffix built-in.o,$(SUBDIRS))
我们发现这里经过一番折腾后,obj-y是文件还是文件夹都会被一视同仁。
--------
第七部分
--------
各个目标文件和库的生成:
%.o: %.S
@$(CC) $(CFLAGS) -c -o $@ $<
@echo " CC .$(CURDIR)/$@"
%.o: %.s
@$(CC) $(CFLAGS) -c -o $@ $<
@echo " CC .$(CURDIR)/$@"
%.o: %.c
@$(CC) $(CFLAGS) -c -o $@ $<
@echo " CC .$(CURDIR)/$@"
==>
CURDIR := $(subst $(TOPDIR),,$(shell pwd))
CPPFLAGS := -I$(TOPDIR)/include -nostdinc -fno-builtin -ffreestanding -pipe
CFLAGS := $(CPPFLAGS) -Wall -Wstrict-prototypes -fno-stack-protector \
-march=armv4 -mabi=apcs-gnu -mno-thumb-interwork -Os
--------
第八部分
--------
关于make clean,也是个递归的过程:
.PHONY: clean
clean:
@for i in $(SUBDIRS);do $(MAKE) -C $$i clean;done
@rm -f $(OBJS) built-in.o
伪目标(.PHONY)是这样一个目标:
它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令,有时我们也可以将一个伪目标称为标签。
咀嚼完毕……