本博客参照内核官方英文文档html
linux的内核makefile主要用于编译整个内核源码,按照用户的需求生成各类目标文件,对于用户来讲,编译内核时很是简单的,只须要几个指令就能够作到,可是对于一个驱动开发者而言,了解内核源码的编译机制是很是必要的。linux
须要了解的是:make是linux下的一个程序软件,makefile至关于针对make程序的配置文件,当咱们执行make命令时,make将会在当前目录寻找Makefile文件,而后根据Makefile的配置对源文件进行编译。git
linux内核源代码的编译也是使用make工具和makefile,可是它在普通的C程序编译的基础上对配置和编译选项进行了扩展,这就是kbuild系统,专门针对linux的内核编译,使得linux内核的编译更加简洁而高效。github
首先咱们须要认识一下linux内核镜像的各类形式,毕竟编译内核最主要的目的就是生成内核镜像,它有几种形式:vmlinux、vmlinux.bin、vmlinuz、zImage、bzImage。架构
对于这一系列的生成文件能够参考官方文档函数
在linux中,因为内核代码的分层模型,以及兼容不少平台的特性,makefile文件分布在各个目录中,对每一个模块进行分离编译,下降耦合性,使编译方式更加灵活。
makefile主要是如下五个部分:工具
若是须要将一个模块配置进内核,须要在makefile中进行配置:ui
obj-y += foo.o
将foo.o编译进内核,根据make的自动推导原则,make将会自动将foo.c编译成foo.o。spa
上述方式基本上用于开发时的模块单独编译,当须要一次编译整个内核时,一般是在top makefile中这样写:命令行
obj-$(CONFIG_FOO) += foo.o
在.config文件中将CONFIG_FOO变量配置成y,当须要修改模块的编译行为时,就能够统一在配置文件中修改,而不用到makefile中去找。
kbuild编译全部的obj-y的文件,而后调用$(AR) rcSTP将全部被编译的目标文件进行打包,打包成build-in.o文件,须要注意的是这仅仅是一份压缩版的存档,这个目标文件里面并不包含符号表,既然没有符号表,它就不能被连接。
紧接着调用scripts/link-vmlinux.sh,将上面产生的不带符号表的目标文件添加符号表和索引,做为生成vmlinux镜像的输入文件,连接生成vmlinux。
对于这些被编译进内核的模块,模块排列的顺序是有意义的,容许一个模块被重复配置,系统将会取用第一个出现的配置项,而忽略随后出现的配置项,并不会出现后项覆盖前项的现象。
连接的顺序同时也是有意义的,由于编译进内核的模块一般由xxx_initcall()来描述,内核对这些模块分了相应的初始化优先级,相同优先级的模块初始化函数将会被依次放置在同一个段中,而这些模块执行的顺序就取决于放置的前后顺序,由连接顺序所决定。
linux的initcall机制能够参考另外一篇博客:linux的initcall机制
全部在配置文件中标记为-m的模块将被编译成可加载模块.ko文件。
若是须要将一个模块配置为可加载模块,须要在makefile中进行配置:
obj-m += foo.o
一样的,一般能够写成这样的形式:
obj-$(CONFIG_FOO) += foo.o
在.config文件中将CONFIG_FOO变量配置成m,在配置文件中统一控制,编译完成时将会在当前文件夹中生成foo.ko文件,在内核运行时使用insmod或者是modprobe指令加载到内核。
一般的,驱动开发者也会将单独编译本身开发的驱动模块,当一个驱动模块依赖多个源文件时,须要经过如下方式来指定依赖的文件:
obj-m += foo.o foo-y := a.o b.o c.o
foo.o 由a.o,b.o,c.o生成,而后调用$(LD) -r 将a.o,b.o,c.o连接成foo.o文件。
一样地,makefile支持以变量的形式来指定是否生成foo.o,咱们能够这样:
obj-$(CONFIG_FOO) += foo.o foo-$(CONFIG_FOO_XATTR) += a.o b.o c.o
根据CONFIG_FOO_XATTR的配置属性来决定是否生成foo.o,而后根据CONFIG_FOO属性来决定将foo.o模块编入内核仍是做为模块。
须要理解的一个原则就是:一个makefile只负责处理本目录中的编译关系,天然地,其余目录中的文件编译由其余目录的makefile负责,整个linux内核的makefile组成一个树状结构,对于上层makefile的子目录而言,只须要让kbuild知道它应该怎样进行递归地进入目录便可。
kbuild利用目录指定的方式来进行目录指定操做,举个例子:
obj-$(CONFIG_FOO) += foo/
当CONFIG_FOO被配置成y或者m时,kbuild就会进入到foo/目录中,可是须要注意的是,这个信息仅仅是告诉kbuild应该进入到哪一个目录,而不对其目录中的编译作任何指导。
*** 须要注意的是,在以前的版本中,编译的选项由EXTRA_CFLAGS, EXTRA_AFLAGS和 EXTRA_LDFLAGS修改为了ccflags-y asflags-y和ldflags-y. ***
ccflags-y asflags-y和ldflags-y这三个变量的值分别对应编译、汇编、连接时的参数。
同时,全部的ccflags-y asflags-y和ldflags-y这三个变量只对有定义的makefile中使用,简而言之,这些flag在makefile树中不会有继承效果,makefile之间相互独立。
这两个编译选项与ccflags-y和asflags-y效果是一致的,只是添加了subdir-前缀,意味着这两个编译选项对本目录和全部的子目录都有效。
使用CFLAGS_或者AFLAGS_前缀描述的模块能够为模块的编译单独提供参数,举个例子:
CFLAGS_foo.o = -DAUTOCONF
在编译foo.o时,添加了-DAUTOCONF编译选项。
顶层makefile中定义了如下变量:
这是一个字符串,用于构建安装目录的名字(通常使用版本号来区分)或者显示当前的版本号。
定义当前的目标架构平台,好比:"X86","ARM",默认状况下,ARCH的值为当前编译的主机架构,可是在交叉编译环境中,须要在顶层makefile或者是命令行中指定架构:
make ARCH=arm ...
指定安装目录,安装目录主要是为了放置须要安装的镜像和map(符号表)文件,系统的启动须要这些文件的参与。
INSTALL_MOD_PATH:为模块指定安装的前缀目录,这个变量在顶层makefile中并无被定义,用户可使用,MODLIB为模块指定安装目录.
默认状况下,模块会被安装到$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)中,默认INSTALL_MOD_PATH不会被指定,因此会被安装到/lib/modules/$(KERNELRELEASE)中。
若是这个变量被指定,模块就会将一些额外的、运行时非必要的信息剥离出来以缩减模块的大小,当INSTALL_MOD_STRIP为1时,--strip-debug选项就会被使用,模块的调试信息将被删除,不然就执行默认的参数,模块编译时会添加一些辅助信息。
这些全局变量一旦在顶层makefile中被定义就全局有效,可是有一点须要注意,在驱动开发时,通常编译单一的模块,执行make调用的是当前目录下的Makefile.
在这种状况下这些变量是没有被定义的,只有先调用了顶层makefile以后,这些变量在子目录中的makefile才被赋值。
vmlinux中打包了全部模块编译生成的目标文件,在驱动开发者眼中,在内核启动完成以后,它的做用至关于一个动态库,既然是一个库,若是其余开发者须要使用里面的接口,就须要相应的头文件。
天然地,build也会生成相应的header文件供开发者使用,一个最简单的方式就是用下面这个指令:
make headers_install ARCH=arm INSTALL_HDR_PATH=/DIR
ARCH:指定CPU的体系架构,默认是当前主机的架构,可使用如下命令查看当前源码支持哪些架构:
ls -d include/asm-* | sed 's/.*-//'
INSTALL_HDR_PATH:指定头文件的放置目录,默认是./usr。
至此,build工具将在指定的DIR目录生成基于arm架构的头文件,开发者在开发时就能够引用这些头文件。
为了清晰地了解kbuild的执行,有必要对kbuild的执行过程作一下梳理:
好了,关于linux内核编译build系统的讨论就到此为止啦,若是朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
关于linux可加载模块编译makefile介绍可参考另外一篇博客:linux内核可加载模块makefile简述
原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身.