从Linux内核2.6开始,Linux内核的编译采用Kbuild系统,这同过去的编译系统有很大的不同,尤其对于Linux内核模块的编译。在新的系统下,Linux编译系统会多次扫描Linux的Makefile。
下面我们通过一个简单的驱动示例,来熟悉linux的编译系统,驱动代码如下:
/*
* test.c -- the example of printf "hello world!" in the screen of driver program
*/
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");/* declare the license of the module ,it is necessary */
static int hello_init(void){
printk(KERN_EMERG "Hello World enter!\n");
return 0;
}
static void hello_exit(void){
printk(KERN_EMERG "Hello world exit!\n");
}
module_init(hello_init); /* load the module */
module_exit(hello_exit); /* unload the module */
/* before is some decription of the model,not necessary */
MODULE_AUTHOR("Jalon Xiao");
MODULE_DESCRIPTION("This is an example of programming driver!");
Makefile源码如下:
ifneq ($(KERNELRELEASE),)
$(warning A top-level warning)
obj-m := test.o
else
$(warning A Bottom-level waring)
.PHONY: modules clean
KERNELDIR =/usr/src/linux-headers-3.2.0-24-generic
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
endif
将上面的源码保存在同一目录下,在命令终端运行make可以编译test模块,linux编译系统会编译生成test.ko内核模块。
从上面的编译截图可以看出内核编译系统会两次读取编译模块makefile。后面的打印A top-level warning都为内核编译系统的读取,第一次为make读取。make在执行读取并执行Makefile时,由于没有定义KERNELRELEASE.会执行else后面的目标,第一个目标modules为默认目标。 (MAKE)−C (KERNELDIR) M= (PWD)modules意思是到 (KERNELDIR)目录执行顶层makefile,M=$(PWD) 是参数,modules 是目标。随后由内核编译系统根据参数再次读取模块makefile,由于编译的内核都已经定义了KERNELRELEASE,所以实质上内核编译系统仅仅只读取了obj-m := test.o ,随后根据kbuild的编译规则编译内核模块。
obj-m和obj-y都是Kbuild编译系统的目标。
列如obj-y += foo.o,它告诉kbuild在当前目录下,有一个叫做foo.o的目标文件,它将从foo.c或则foo.S编译得到。
或者obj-m += foo.o,同样告诉kbuild在当前目录下,有一个叫做foo.o的目标文件,它将从foo.c或则foo.S编译得到。
不同的是Kbuild编译完所有的 (obj−y)文件后,并调用” (LD) -r”把所有这些文件合并到built-in.o文件。这个built-in.o会被上一级目录的Makefile使用,最终链接到vmlinux中。而obj-m编译成ko模块。
最简单的kbuild Makefile可以仅包含:
obj-$(CONFIG_FOO) += foo.o
其中CONFIG_FOO可以等于y或m,它的值由.config文件给出。如果$(CONFIG_FOO)既不是y也不是m,那么该文件不会被编译和链接.
如果一个驱动模块不是由一个源文件编写时,makefile应按照如下规则进行,否则会出现
/work/mtkv1.33/kernel/drivers/net/wireless/DPO_RT5572_LinuxSTA_2.6.0.1_20120629/common/crypt_sha2.c:549:1: fatal error: opening dependency file drivers/net/wireless/DPO_RT5572_LinuxSTA_2.6.0.1_20120629/common/.crypt_sha2.o.d: No such file or directory
compilation terminated.
类似的错误!
所以当某个目标由多个源文件编译得到时,可以通过$(<module_name>-objs)或$(<module_name>-y)
把这些源文件告诉kbuild。Kbuild能够识别后缀-objs和-y/-m,例如:
#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
Kbuild会编译所有$(isdn-objs)中的对象,并调用"$(LD) -r"
把它们链接成isdn.o文件。
Makefile里常用的编译选项,参见内核目录:Document/kbuild/modules.txt和Document/kbuild/makefiles.txt。
ccflags-y、asflags-y和ldflags-y分别对应EXTRA_CFLAGS、EXTRA_AFLAGS和EXTRA_LDFLAGS三个,分别用于$(CC)、$(AS)、$(LD)
。这三个变量只在当前Makefile中有效。
为驱动模块添加外部头文件的方法:在文件Kbuild里通过内核关键字EXTRA_CFLAGS来指定。具体为:
EXTRA_CFLAGS += -I$(EXTERNAL_INC) -I$(LOCAL_INC)
# file dev_var_def.mk
PWD := $(shell pwd)
EXTRERNAL_INC := $(PWD)/../include
LOCAL_INC := $(PWD)
另外还有subdir-ccflags-y、subdir-asflags-y
这两个变量作用于当前Makefile及其所有子目录。
这里只介绍了常用的编译选项,其他的参考内核文档。
Makefile只负责编译当前目录中的对象。子目录中的对象,由子目录中的Makefile负责。如何让make调用子目录中的Makefile?答案是把子目录包含到obj-y或obj-m中。例如:
#fs/Makefile
obj-$(CONFIG_EXT2_FS) += ext2/
在一个目录下,obj-y所列出的文件,将被编译成built-in.o文件,而lib-y或lib-m所列出的文件,将在当前目录下生成lib.a文件。
注意:一般lib-y或lib-m只用在lib/和arch/*/lib这两个目录中。
MODULE_NAME=test
#####################in order to unify with the make menuconfig,should define to CONFIG_$(MODULE_NAME)
MODULE_CONFIG=CONFIG_TEST
#############IF not config by make menuconfig,should define MODULE_CONFIG
#define m to compile MODULE_NAME.ko
#CONFIG_TEST=m
CROSS_CONFIG=n
DEBUG = y
ifneq ($(KERNELRELEASE),)
#########################kbuild system start to compile
$(warning A top-level warning)
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DDEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif
EXTRA_LDFLAGS += $(DEBFLAGS) #ccflags-y
#obj-$($(MODULE_CONFIG)) := $(MODULE_NAME).o #why no ok?????
obj-m := $(MODULE_NAME).o
#for Multi-files module
#$(MODULE_NAME)-objs +=
else
########################first read this makefile
$(warning A Bottom-level waring)
#######################define the compile tools
ifeq ($(CROSS_CONFIG), y)
#for Cross-compile
KERNELDIR =/home/xiaojingling/work/mtkv1.33/kernel
ARCH = arm
#FIXME:maybe we need absolute path for different user. eg root
#CROSS_COMPILE = arm-none-linux-gnueabi-
CROSS_COMPILE =
INSTALLDIR := $(shell pwd)
else
#for Local compile
KERNELDIR = /usr/src/linux-headers-3.2.0-24-generic
ARCH = x86
#only need the prefix of the tools,similar to arm compiling tools
CROSS_COMPILE =
INSTALLDIR := ./
endif
########################end of defining the tools
PWD := $(shell pwd)
.PHONY: modules clean
modules:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
endif
问题:
#obj-$($(MODULE_CONFIG)) := $(MODULE_NAME).o #why no ok?????
这样定义的时候居然不能编译目标,不知道哪里出了问题。若这样定义成功可以方便集成到kernel内部。
obj-m := $(MODULE_NAME).o
需要编译其他的模块时,更具实际情况修改如下值就可以了:
MODULE_NAME=test #目标名
CROSS_CONFIG=n #交叉编译时为y
DEBUG = y #调试选项
关于上面的makefile更详细的请参考Linux内核模块LKM编译-自制Makefile模板
参考文档:
Linux 2.6内核Makefile浅析
Linux内核模块LKM编译-自制Makefile模板
Makefile编译选项
Android 驱动之旅