编译模块--Makefile

来源:互联网 发布:网络歌手好听的歌2015 编辑:程序博客网 时间:2024/05/16 07:36

编译模块--Makefile

分类: ldd3笔记 2606人阅读 评论(0)收藏举报
1.最基本的一个Makefile文件:
# hello.o为你需要编译的源代码,若有多个源代码应使用方法二的方式
obj-m:=hello.o

#方法二
obj-m:=module.o
module-objs:=file1.o file2.o

将上面的内容保存为Makefile,在命令行输入“make -C /lib/modules/$(shell uname -r)/build M=`PWD` modules”就可以编译生成.ko文件了。首先将目录改变到-C选项指定的位置(即内核源代码目录,其中有内核的顶层makefile文件),M=选项让该makefile在构造modules目标之前返回到当前目录。

更方便的操作:
###########################################################
# 如果定义了KERNELRELEASE,则说明是从内核构造系统调用的
# 因此可利用其内建语句
ifneq ($(KERNELRELEASE),)
obj-m :=hello.o
#否则,是直接从命令行调用的
#这时要调用内核构造系统
else
KERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
###########################################################
//实验中该文件必须保存为Makefile,第一个字母小写都不可以,原因目前不知
//解释:
1.执行make命令,读取当前目录下的Makefile
2.因为没有定义KERNELRELEASE,所以执行else部分
3.执行default目标,即执行命令 “ $(MAKE) -C $(KERNELDIR) M=$(PWD) modules”
4.该命令相当于重新执行make命令,但是该make命令首先调用-C选项所指的路径下的makefile,这个makefile定义了KERNELRELEASE变量,然后执行modules目标。
5.modules目标又调用了一次make命令,执行当前目录下的makefile文件,就把obj-m变量定义了。然后又执行make命令生成最终的.ko文件。(这一块大概如此,具体尚没有时间搞清楚,有了解透彻的朋友希望指教。)
6.如上就是整个执行过程了。

下面是我自己写的一个makefile文件,如下:
##############################################################
##文件说明,编译2.6.x的linux驱动的makefile文件
# 作者:wyj
# 创建时间:2008-09-23
##使用说明:
# 变量DRI_NAME MOD_NAME NOD_NAME 分别设置伪驱动程序的名称,生成模块的名称和设备节点的名称
# 使用make install进行安装驱动 需要root权限
# 使用make uninstall进行卸载驱动 需要root权限
# 使用make clean清除多余的文件,包括生成的模块代码

# 驱动名称
DRI_NAME :=demo
# 模块名称
MOD_NAME :=IMTI_DEMO
# 节点名称
NOD_NAME :=/dev/demo

obj-m :=$(DRI_NAME).o
KERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD ?=$(shell pwd)

default:
    $(MAKE) -C ${KERNELDIR} M=${PWD} modules
#安装的伪代码
.PHONY:install uninstall clean
install:default
    insmod demo.ko   
    mknod $(NOD_NAME) c `awk '$$2=="'${MOD_NAME}'" {print $$1}' /proc/devices` 0
#卸载的伪代码
uninstall:
    rm -f $(NOD_NAME)
    rmmod $(DRI_NAME)
#清理垃圾的伪代码
clean:
    @rm -rf *.o *.ko .tmp_versions *~ Module.symvers .*.cmd *.mod.c
######################################################################
这个makefile功能比较简单,只是以前的一个东西,做为一个纪念。见笑了。

好了,关于驱动的编译过程就记录到这里。今天是冬至第三天,也就是一九了,天气很冷,办公室空调暖和。最近比较闲,很喜欢这样的轻松气氛,然后投入到研究之中,纪念之。

//装载和卸载
insmod:将驱动模块加载到内核
rmmod:将驱动模块移出内核
lsmod:查看模块信息。信息来源于文件/proc/modules
modprobe:装载模块,可以查找依赖关系。处理层叠模块。

insmod,类似ln,将模块的代码和数据装入内核,然后使用内核的符号表解析模块中任何未解析的符号。不修改模块的磁盘文件,仅仅修改内存中的副本。
insmod支持一些命令行选项,所以允许在装载时进行配置,这样比编译时配置更加的灵活。

//版本依赖
要注意编译和加载是需要模块和需要的内核树版本对应,所以需要注意版本的问题。这个以后细细研究。

//模块层叠技术
允许模块到处符号,新模块可以使用这些符号,我们就可以在已有的模块上层叠新的模块,这就是模块层叠技术。
工具modprobe
使用如下宏到处符号:
EXPORT_SYMBOL(name)
EXPORT_SYMBOL_GPL(name)
GPL版本表示导出的符号只允许被GPL许可证下的模块使用。

//预备知识
所有的模块代码中都必须包含下面的代码:
#include <linux/modules.h>  //包含有可装载模块需要的大量符号和函数的定义
#include <linux/init.h>  //指定初始化和清除函数
#include <moduleparam.h>    //可在装载模块时向模块传递参数

一些重要的宏:
MODULE_LICENSE("GPL")    //使用GPL协议,其它协议有"GPL v2"(GPL版本2) "GPL and additional rights"(GPL及附加权利) "Dual BSD/GPL"(BSD/GPL双重许可证) "Dual MPL/GPL" (MPL/GPL双重许可证)以及"Proprietary"(专有)。
若没有声明协议,则默认位专有,内核开发者不太愿意帮助装载专有模块遇到问题的用户。
其它宏:
MODULE_AUTHOR  //描述模块作者
MODULE_DESCRIPTION  //说明模块用途的简短描述
MODULE_VERSION    //代码修订号
MODULE_ALIAS    //模块的别名
MODULE_DEVICE_TABLE    //用来告诉用户空间模块所支持的设备

这些宏可以出现在源代码函数以外的任何地方,现在通常将它们放在文件最后




模块参数

分类: ldd3笔记 91人阅读 评论(0)收藏举报
/*带模块参数的驱动模块
  使用(insmod hello.ko howmany=n whom="str" )执行的话,可将n和"str"传给模块*/

#include <linux/init.h>
#include <linux/module.h>

static char *whom="world";
static int howmany=1;
module_param(howmany,int,S_IRUGO);
module_param(whom,charp,S_IRUGO);

MODULE_LICENSE("Dual BSD/GPL");//告诉内核,该模块才用了自由许可证,否则转载时会遭到内核抱怨

static int hello_init(void)//装载时运行
{
    int i;
    for(i=0;i<howmany;i++)
        printk(KERN_ALERT "hello %s./n",whom);//printk()内核态的输出函数;KERN_ALERT,显示的优先级
    return 0;
}

static void hello_exit(void)//卸载时运行
{
    printk(KERN_ALERT "Goodbye,cruel world. My first drivers is over!/n");
}

module_init(hello_init);    //指定装载时调用的函数
module_exit(hello_exit);    //指定卸载时调用的函数

//注意代码中的修改,增加了两个静态全局变量,howmany和whom,分别声明为命令行参数,这样的话,在加载的时候通过命令行穿进来参数了。
为了让参数对insmod可见,参数必须使用module_param宏声明, module_param宏的使用方法如下:
module_param(name, type, perm);  //name是参数名称,type是参数类型,perm是参数访问权限。
module_param_array(name, type, num, perm); //可以声明一个数组,name是数组名,type是数组元素的类型,num是数组元素个数,perm还是权限。
上面的例子中S_IRUGO表示主,组,其它用户都可写。






编译内核模块

分类: Linux内核 1901人阅读 评论(1)收藏 举报

编译内核模块的方法与编译一般应用程序的方法略有不同. 我们会发现在内核源码树的层层目录中, 都存在有Makefile.即这些Makefile是分层次组织的. 以往的内核版本中, 编译模块比较麻烦, 需要我们对这些Makefile做出许多更改.2.6的内核采用了"kbuild"编译系统, 简化了这些问题.关于kbuild, 可参考内核源码树中的 /Documentation/kbuild/modules.txt.

编译之前, 肯定是需要源文件的. 这些源文件可以放在内核源码树中, 也可以放在内核源码树之外的任何地方. 根据源文件存在的目录, 存在两种编译方法: 在源码树之中和在源码树之外.

在源码树中编译模块


官方内核模块的源代码都是按模块(驱动)类型组织的, 我们到内核源码树的drivers目录可以看到char, usb, block之类的子目录. 那么我们在内核源码树中添加文件时, 最好也遵循这些分类. 分类的规则自己灵活把握.

下面以前面的"hello, world"这个简单的模块为例, 来看看如何在内核源码树中编译模块.

1, 不新建子目录
(1) 先在内核源码树中的drivers目录编辑一个c源程序, 名为hello.c.
(2) 修改drivers目录的Makefile文件, 添加: obj-m += hello.o
(3) 重新编译内核(回到源码树根目录, 运行 $ sudo make).

这样, 在drivers目录多出了这样几个文件: hello.mod.c, hello.mod.o, hello.o, hello.ko. hello.ko就是编译出来的模块了.

2, 新建子目录
如果源文件比较多, 可以在drivers目录中新建子目录. 还是以hello, world为例:
(1) 在内核源码树的drivers目录中新建一个hello子目录, 并将hello.c放在hello目录中.
(2) 修改drivers目录的Makefile文件, 添加: obj-m += hello/
(3) 在hello目录中新建一个Makefile文件, 内容为: obj-m += hello.o
(4) 重新编译内核(回到源码树根目录, 运行 $ sudo make).

这样, 新生成的模块文件就位于hello目录中.

若在内核源码树中编译模块, 如果不新建子目录, 那么只需修改当前目录的Makefile, 否则应该在当前新建的子目录中新建Makefile指定编译选项, 并修改上层目录的Makefile以让kbuild能够进入新建的子目录.


在源码树之外编译模块


还是以上面的hello, world为例. 在当前目录有个hello.c:

(1)首先在模块代码所在的目录新建一个Makefile, 内容为:
obj-m := hello.o
(2)这样调用make命令:
$ sudo make -C /usr/local/src/kernel/linux-2.6.16.20 SUBDIRS=$PWD modules
这里/usr/local/src/kernel/linux-2.6.16.20是内核源码树所在的目录.

-C表示要求make先切换到-C指定的目录. SUBDIRS(也可以用M代替SUBDIRS)使make在编译模块之前回到当前目录.

整个编译过程实际上是执行-C指定的内核源码树的Makefile, 并通过SUBDIR指定你要编译的内核源文件的目录.

简化命令行输入
每次调用make的时候输入这些参数比较比较麻烦, 可以这样来改写Makefile以简化:

obj-m += hello.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
这样, 只需在当前目录调用 $ sudo make 就可以完成上面的工作. 调用 $sudo make clean 将删除所有新生成的文件.

上面的Makefile是这样确定内核源码树所在的目录的: 我们先到/lib/modules目录, 会看到一些以内核版本为名的目录,目录中有一个build文件, 它是一个符号连接, 指向内核源码树. 那么如何确定进入哪个内核版本的目录呢? 这就可以通过 $uname -r 来确定, 它指出了当前运行内核的版本.

还可以进一步简化这个Makefile:
obj-m := hello.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

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

这样不用在Makefile中一次又一次地指定内核代码树的目录.

上面的例子中只讨论了所有的代码在一个文件中的情况. 若代码分布在多个源文件中,  比如file1.c, file2.c, 生成hello.ko. 应该这样写Makefile:
obj-m :=  hello.o
hello-objs := file1.o file2.o
注意, 虽然我们的目的是生成.ko文件, 但在Makefile中写为.o!