驱动模块的Makefile详细解析及其相关知识

 

ifeq ($(KERNELRELEASE),)
       KERNELDIR ?= /home/linux/linux-2.6.22.6
        PWD := $(shell pwd)
        modules:
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
        modules_install:
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
        clean:
                rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
        .PHONY: modules modules_install clean
else
                obj-m := myhello.o 
endif

 

    上诉代码中,第一个ifeq ($(KERNELRELEASE),)目前并无用处,它的由来是指在Linux源码根目录下的Makefile编译内核时,KERNELRELEASE宏会被定义,那么如果是从源码根目录开始的make则会将myhello.o模块编译进内核。
    KERNELDIR ?= /home/linux/linux-2.6.22.6,这句是对KERNELDIR进行赋值,这个变量是后面我们用到的指代内核源码目录用的
    PWD := $(shell pwd),这句是对PWD变量进行赋值,作用是将$(shell pwd)的返回结果(即求得当前目录的路径)赋值给PWD,(即在shell里,$(shell xxx)就是相当于在terminal中执行 xxx命令)。这个变量我们在后面指代我们要编译的驱动程序所在的位置

 

modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

    解释1:这句是Makefile的规则:这里的$(MAKE)就相当于make-C 选项的作用是指将当前工作目录转移到你所指定的位置。“M=”选项的作用是,当用户需要以某个内核为基础编译一个外部模块的话,需要在make modules 命令中加入“M=dir”,程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO文件。

    解释2:这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;在上面的例子中,我们将该变量设置成了hello.o

modules_install:
         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

    这个命令是模块的安装,在Makefile中搜索“lib\/modules”可以看到下面的语句,通过阅读你不难找到这个“MODLIB”的用处,它是用来指定安装路径的,而变量“INSTALL_MOD_PATH”往往为空。

MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
        Export MODLIB
.PHONY: modules modules_install clean

    这句话是的作用是保证modules,modules_install,clean这三个命令能正常完成。

     .PHONY这是一个特殊目标名称,还有其它的,如:.SUFFIXES.DEFAULT.PRECIOUS.INTERMEDIATE.SECONDARY.SECONDEXPANSION.DELETE_ON_ERROR.IGNORE .LOW_RESOLUTION_TIME .SILENT .EXPORT_ALL_VARIABLES .NOTPARALLEL
它们的具体用法可以参考GNU手册中的Special Built-in Target Names章节。
    .PHONY目标的具体意思是如果在Makefile的工作目录中有名如:modules,modules_install,clean等文件时命令会出错。它是防止这出错的方式。

附录A:Make命令执行过程:
1、一次读取变量"MAKEFILE"定义的makefile文件列表;
2、读取工作目录下的makefile文件(根据命名的查找顺序"GNUmakefile"、"makefile"、"Makefile",先找到哪个就读取哪  个);
3、一次读取工作目录makefile文件中使用指示符"include"包含的文件;
4、查找重建所以已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重现执行);
5、初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支;
6、根据“终极目标”以及其他目标的依赖关系建立依赖关系链接;
7、执行除“终极目标”以外的所有目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件);
8、执行“终极目标”所在的规则。

 

附录B:库文件的分类 

    我们知道,Linux下文件的类型并不像windows一般依赖于其后缀名,在linux下后缀名是可有可无的,但是为了区分,有些文件还是加了后缀名:
1、*.ko 是kernel object 的缩写,是Linux 2.6内核使用的动态连接文件,在Linux系统启动时加载内核模块。
2、*.o 是相当于windows中的.obj文件。
注意:*.ko与*.o的区别在于,*.ko是linux 2.6内核编译之后生成的,多了一些module信息,如author,license之类的。*.o文件则是linux 2.4内核编译生成的。

3、*.a 是静态库,由多个*.o组成在一起,用于静态连接。
4、*.so 是shared object的缩写,用于动态连接,和windows的dll差不多。
5、*.la 为libtool自动生成的一些共享库。

 

附录B:几个有用函数notdir,wildcard和patsubst

makefile里的函数使用,和取变量的值类似,是以一个‘$’开始,然后是一个括号里面是函数名和需要的参数列表,多个变量用逗号隔开,形式如下:
return = $(functionname  arg1,arg2,arg3...)
备注:可能这里的'$'更像是从某个地址取值类似的操作。
1、 wildcard

使用:SRC = $(wildcard *.c ./foo/*.c)
搜索当前目录及./foo/下所有以.c结尾的文件,生成一个以空格间隔的文件名列表,并赋值给SRC.当前目录文件只有文件名,子目录下的文件名包含路径信息,比如./foor/bar.c。

2、notdir

使用:SRC = $(notdir wildcard)
去除所有的目录信息,SRC里的文件名列表将只有文件名。

3、patsubst

使用:OBJ = $(patsubst %.o %.c $(SRC))
patsubst是patten substitude的缩写,匹配替代的意思。
这句是在SRC中找到所有.c 结尾的文件,然后把所有的.c换成.o。

 

附录C:一个简单的驱动模块--makefile的经典构成

//------------Makefile----------------------
 obj-m := hello.o
 KERNELDIR :=/lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd)
 modules:
     $(MAKE)-C $(KERNELDIR) M=$(PWD) modules #注意前面必须为tab
 modules_install:
     $(MAKE)-C $(KERNELDIR) M=$(PWD) modules_install #注意前面必须为tab

 

下面逐一分析一下各个语句:

  • obj-m := hello.o
这句意为有一个模块需要从目标文件hello.o中构造,构造的模块名称为hello.ko.
  • KERNELDIR := /lib/modules/$(shell uname -r)/build
这里是定义一个变量KERNELDIR,并且赋值为"/lib/modules/$(shell uname -r)/build"。
这个值中要解释的只有一点,即$(shell uname -r):

大家可以尝试在terminal中输入 $:uname -r是什么结果,没错,这个命令会获取当前内核的版本号,如“2.6.38.2”。

然后我们再查看"/lib/modules"目录下有哪些文件:

$:ls /lib/modules/
结果为:
2.6.38.2  2.6.38-8-generic
所以"/lib/modules/$(shell uname -r)/build"的意思已经很明确了,就是当前内核的源代码目录
  • PWD := $(shell pwd)
有了KERNELDIR的解释,相信这个也不多说了,就是获取当前目录了。总之在shell里,$(shell xxx)就是相当于在terminal中执行 xxx命令。
  • $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;在上面的例子中,我们将该变量设置成了hello.o。
posted @ 2013-03-21 09:40  西五师兄!  阅读(2002)  评论(0)    收藏  举报