我可真是太喜欢操作系统开发这个系列了 :eileen_love: :eileen_love: :eileen_love:
这一部分本来是写在第三章里面的,不过后来发现make工具需要介绍的内容还挺多的,就单独拿出来做个番外篇罢。
我打算在番外篇里面写一些与操作系统开发主线关联性不太强,但是又需要用到的知识,这部分知识可能有同学已经会用了,就可以直接跳过番外篇看正文咯。
make工具可以按照编写的规则来进行自动化的编译和链接,在使用make的时候需要编写和提供makefile(规则文件),下面先简单介绍一下makefile的语法。
基本规则格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 在makefile当中,使用"#"进行注释 # 对于一般编译规则,有如下结构: 目标... : 依赖文件集合... 命令1 命令2 ...... # 需要注意的是,上方的结构每个命令前均有tab缩进 # 静态模式的规则格式如: <targets...> : <target-pattern> : <prereq-patterns...> 命令1 命令2 ...... # 例: # bootloader bin/boot.bin : bootloader/boot.asm nasm $< -o $@ $(INCLUDE) bin/loader.bin : bootloader/loader.asm nasm $< -o $@ $(INCLUDE) # 在上面两个例子中,分别表示通过bootloader/boot.asm生成bin/boot.bin,以及通过bootloader/loader.asm生成bin/loader.bin # 下面的nasm $< -o $@ $(INCLUDE)即是生成过程中所使用的指令 # 在指令中出现了一些莫名其妙的符号,后续会详细解释 |
变量及相关操作符
在Makefile中,变量都是存储的字符串,一般程序语言里面的变量赋值都是用“=”,makefile也是一样。不过不同的是,不仅仅只有等号,makefile还有下面几个操作符:
- "=":只有一个等号,表示使用变量最后一次定义的有效值,与大多数程序语言的使用方法相同。
- ":=":冒号加等号,表示使用变量最前面定义的有效值。
- "?=":问号加等号,表示如果前面没有被赋值,则使用当前值,否则使用前面所赋的值。
- "+=":加号加等号,表示变量追加,在已经定义的变量后面追加新的变量,这一个用法与很多程序语言(如Python)类似。
在使用变量时,需要按照类似$(变量名)这样的格式进行调用,同时,变量的值也可以指向另一个变量,如:v1 = $(v2)。
内置变量
在makefile中,有一些内置变量可以直接进行调用,详细有哪些内置变量可以在这里找到。
自动变量
在介绍自动变量以前,还是先来看一段示例代码:
1 2 3 4 5 6 7 | # 其实这一段代码和上面基本规则格式那一段是一样的 INCLUDE = -I include # bootloader bin/boot.bin : bootloader/boot.asm nasm $< -o $@ $(INCLUDE) bin/loader.bin : bootloader/loader.asm nasm $< -o $@ $(INCLUDE) |
在上面的代码中,出现了刚才介绍过的变量,即$(INCLUDE),但同时还出现了几个我们不认识的符号,比如$<和$@。这两个符号其实也是变量,而且是自动变量。
- $@:指代当前目标(构建目标),在上面的示例代码中分别为bin/boot.bin和bin/loader.bin,即(其中一句)指令实际上是nasm $< -o bin/boot.bin -I include。
- $<:指代第一个前置条件,在上面的示例代码中分别为bootloader/boot.asm和bootloader/loader.asm(注意,只指定第一个,也就是说即便boot.asm后面跟了其他内容或文件,也只表示boot.asm),即(其中一句)指令实际上是nasm bootloader/boot.asm -o bin/boot.bin -I include。
- $?:指代目标更新的所有前置条件,比如某个编译规则如:bin/loader.bin : bootloader/loader.asm bootloader/loader_new.asm,此时$?仅指代bootloader/loader_new.asm,因为这个条件是最新的。
- $^:指代所有前置条件,还是以上一个规则为例,此时$^指代的就是bootloader/loader.asm bootloader/loader_new.asm。
- $+:指代所有前置条件,且不去除重合,这个变量与$^类似,唯一区别是在指代是不会去除重合部分。
- $*:指代目标模式中”%“及其之前的部分,例:某个编译规则的目标为dir/a.foo.b,目标规则为a.%.b,则$*匹配dir/a.foo
- $(@D) 和 $(@F):这两个变量分别指向$@的目录名和文件名,例:当$@表示bin/boot.bin时,$(@D)表示bin, $(@F)表示boot.bin。
- $(<D) 和 $(<F):可以结合第2条和第7条来猜一猜这两个变量的使用方法,这里就不多说了。
通配符
makefile的通配符和bash一样,比如*、?、...等。
更加高级的用法
这一部分的用法会比较少接触到,或者是开发后期才会接触到,建议有需要使用到的时候再详细学习。
回声
一般情况下,make会将执行的每条指令先打印出来,再执行,如下:
1 2 3 4 | INCLUDE = -I include bin/boot.bin : bootloader/boot.asm # 这里有一句注释 nasm $< -o $@ $(INCLUDE) |
上述规则在执行时,会得到以下结果:
1 2 | # 这里有一句注释 nasm $< -o $@ $(INCLUDE) |
如果我们不希望在命令执行时将其内容打印出来,可以在前面加@进行屏蔽,例如将上面的第4行修改为@# 这里有一句注释,此时这句注释就不会被打印出来,但是nasm $< -o $@ $(INCLUDE)仍会被打印出来。
条件判断
makefile支持条件判断,使用方法如下:
1 2 3 4 5 | ifeq ($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif |
上面代码判断当前编译器是否 gcc ,然后指定不同的库文件。
循环
makefile支持循环结构,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 | LIST = one two three all: for i in $(LIST); do \ echo $$i; \ done # 等同于 all: for i in one two three; do \ echo $i; \ done |
上面的代码运行结果为:
1 2 3 | one two three |
函数
makefile中同样支持函数封装,但我们的开发中暂时用不上,后续有需要再进行补充吧。 :ava_bitipop: