第一步:准备源代码
创新互联是一家专注于成都网站设计、成都网站制作与策划设计,白云网站建设哪家好?创新互联做网站,专注于网站建设十多年,网设计领域的专业建站公司;建站业务涵盖:白云等地区。白云做网站价格咨询:13518219792
首先我们还是要来编写一个符合linux格式的模块文件,这样我们才能开始我们的模块编译。假设我们有一个源文件mymod.c。它的源码如下:
mymodules.c
1. #include linux/module.h /* 引入与模块相关的宏 */
2. #include linux/init.h /* 引入module_init() module_exit()函数 */
3. #include linux/moduleparam.h /* 引入module_param() */
4
5. MODULE_AUTHOR("Yu Qiang");
6. MODULE_LICENSE("GPL");
7
8. static int nbr = 10;
9. module_param(nbr, int, S_IRUGO);
10.
11. static int __init yuer_init(void)
12.{
13. int i;
14. for(i=0; inbr; i++)
15. {
16. printk(KERN_ALERT "Hello, How are you. %d/n", i);
17. }
18. return 0;
19.}
20.
21.static void __exit yuer_exit(void)
22.{
23. printk(KERN_ALERT"I come from yuer's module, I have been unlad./n");
24.}
25.
26. module_init(yuer_init);
27. module_exit(yuer_exit);
我们的源文件就准备的差不多了,这就是一个linux下的模块的基本结构。第9行是导出我们的符号变量nbr。这样在你加载这个模块的时候可以动态修改这个变量的值。稍后将演示。yuer_init()函数将在模块加载的时候运行,通过输出的结果可以看到我们的模块是否加载成功。
第二步:编写Makefile文件
首先还是来看看我们Makefile的源文件,然后我们再来解释;
Makefile
obj-m := modules.o #要生成的模块名
modules-objs:= mymod.o #生成这个模块名所需要的目标文件
KDIR := /lib/modules/`uname -r`/build
PWD := $(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o .* .cmd *.ko *.mod.c .tmp_versions
ARM平台
Makefile
obj-m += mymod.o
KDIR := /home/workspace2/kernel/linux-2.6.25 #如果是用于arm平台,则内核路径为arm内核的路径
PWD = $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o
在arm板上插入是
insmod mymod
如果出现以下错误
insmod: chdir(/lib/modules): No such file or directory
则运行
mkdir /lib/modules/2.6.25 (与arm内核版本相同)
并将mymod.ko文件复制到该目录下
cp mymod.ko /lib/modules/2.6.25
然后再执行 (insmod 只在/lib/modules/2.6.25目录下查找相关驱动模块)
insmod mymod
现在我来说明一下这个Makefile。请记住是大写的Makefile而不是小写的makefile;
obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj-m := 模块名.o
modules-objs :这个变量是说明声称模块modules需要的目标文件 格式要求 模块名-objs := 目标文件
切记:模块的名字不能取与目标文件相同的名字。如在这里模块名不能取成 mymod;
KDIR :这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境
M= :指定我们源文件的位置
PWD :这是当前工作路径$(shell )是make的一个内置函数。用来执行shell命令。
第三步:编译模块
现在我们已经准备好了我们所需要的源文件和相应的Makefile。我们现在就可以编译了。在终端进入源文件目录输入make
运行结果:
make[1]: Entering directory `/usr/src/linux-headers-2.6.24-24-generic'
CC [M] /home/yuqiang/桌面/mymodule/mymodules.o
LD [M] /home/yuqiang/桌面/mymodule/modules.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/yuqiang/桌面/mymodule/modules.mod.o
LD [M] /home/yuqiang/桌面/mymodule/modules.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-24-generic'
第四步:加载/卸载我们的模块
从上面的编译中我可以看到。已经有一个modules.ko生成了。这就是我们的模块了。现在我们就可以来加载了。
首先在终端输入:sudo insmod modules.ko
现在我们来看看我们的模块加载成功没有呢?
在终端输入:dmesg | tail -12 这是查看内核输出信息的意思。tail -12 显示最后12条;
显示结果如下:
[17945.024417] sd 9:0:0:0: Attached scsi generic sg2 type 0
[18046.790019] usb 5-8: USB disconnect, address 9
[19934.224812] Hello, How are you. 0
[19934.224817] Hello, How are you. 1
[19934.224818] Hello, How are you. 2
[19934.224820] Hello, How are you. 3
[19934.224821] Hello, How are you. 4
[19934.224822] Hello, How are you. 5
[19934.224824] Hello, How are you. 6
[19934.224825] Hello, How are you. 7
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
看到了吧。我们的模块的初始化函数yuer_init();已经成功运行了。说明我们的模块已经加载成功;
现在我们再来卸载模块试试看。
在终端输入:sudo rmmod modules
在终端输入:dmesg | tail -3
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
[20412.046932] I come from yuer's module, I have been unlad.
可以从打印的信息中看到,我们的模块的退出函数已经被执行了。说明我们的模块已经被成功的卸载了。到目前位置我们就已经算是对模块的编译到编译运行算是有了一个整体上的认识了。对于以后深入的学习还是应该有点帮助的。下面我们将在看看于模块相关的一些简单的操作。
第五步:加载模块时传递参数
在终端输入:sudo insmod module_name.ko nbr=4
在终端输入:dmesg | tail -6
显示结果如下:
[20800.655694] Hello, How are you. 9
[21318.675593] I come from onefile module, I have been unlad.
[21334.425373] Hello, How are you. 0
[21334.425378] Hello, How are you. 1
[21334.425380] Hello, How are you. 2
[21334.425381] Hello, How are you. 3
这样我们就可以看到在模块加载的时候动态设置了我们的一个变量。初始化函数中的循环只执行了4次。
可能你会问我怎么知道一个模块可以设置那些变量呢。当然,你可以先不设变量加载一次。然后可以在终端输入ls /sys/module/modules_name/parameters/来查看。在这里我们是这样输入的
在终端输入:ls /sys/moedle/modules/parameters/
显示结果:
nbr
如果我们的模块加载成功了。最后我们还可以通过modinfo来查看我们的模块信息。如下
在终端输入:sudo modinfo modules.ko
显示结果:
filename: modules.ko
license: GPL
author: Yu Qiang
srcversion: 20E9C3C4E02D130E6E92533
depends:
vermagic: 2.6.24-24-generic SMP mod_unload 586
parm: nbr:int
modprobe命令\x0d\x0aLinux命令:modprobe 。\x0d\x0a功能说明:自动处理可载入模块。\x0d\x0a语法:modprobe [-acdlrtvV][--help][模块文件][符号名称 = 符号值]。\x0d\x0a补充说明:modprobe可载入指定的个别模块,或是载入一组相依的模块。modprobe会根据depmod所产生的相依关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。\x0d\x0a\x0d\x0a内容\x0d\x0a1、modprobe 命令是根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。\x0d\x0a2、删除模块的命令是:modprobe -r filename。\x0d\x0a3、系统启动后,正常工作的模块都在/proc/modules文件中列出。使用lsmod命令也可显示相同内容。\x0d\x0a4、在内核中有一个“Automatic kernel module loading"功能被编译到了内核中。当用户尝试打开某类型的文件时,内核会根据需要尝试加载相应的模块。/etc/modules.conf或 /etc/modprobe.conf文件是一个自动处理内核模块的控制文件。\x0d\x0amodprobe命令主要用于在Linux 2.6内核中加载和删除Linux内核模块。通过此命令用户可以轻松地加载和删除Linux内核模块,同时使用此命令加载Linux内核模块时,Linux内核会自动解决内核模块之间的依赖关系,将相互依赖的模块自动加载,如下所示:\x0d\x0a1) 加载RAID1阵列级别模块:\x0d\x0a[root@rhel5 boot]# modprobe raid1 \x0d\x0a\x0d\x0a2) 显示已加载的RAID1阵列级别模块:\x0d\x0a[root@rhel5 boot]# lsmod |grep raid1 raid1 25153 0 \x0d\x0a\x0d\x0a3) 删除RAID1阵列级别模块:\x0d\x0a[root@rhel5 boot]# modprobe -r raid1 \x0d\x0a\x0d\x0a4) 显示RAID1阵列级别模块:\x0d\x0a[root@rhel5 boot]# lsmod |grep raid1 \x0d\x0a\x0d\x0amodprobe命令在加载模块时会自动解决依赖的模块。当加载的模块需依赖另一个模块时,系统会自动将此模块加载。而当用户使用insmod命令加载模块时,则不会自动解决相对应的依赖模块。\x0d\x0amodprobe命令中的"-r"参数表示删除指定模块,"-c"参数表示显示/etc/modprobe.conf配置文件的参数,"-C"参数表示指定内核配置文件,"-f"表示覆盖,"-l"表示显示模块的绝对路径。\x0d\x0a[root@rhel5 boot]# modprobe -l|grep raid /lib/modules/2.6.18-8.el5/kernel/drivers/md/raid0.ko /lib/modules/2.6.18-8.el5/kernel/drivers/md/raid456.ko /lib/modules/2.6.18-8.el5/kernel/drivers/md/raid1.ko /lib/modules/2.6.18-8.el5/kernel/drivers/md/raid10.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/megaraid.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/aacraid/aacraid.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/megaraid/megaraid_mbox.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/megaraid/megaraid_mm.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/megaraid/megaraid_sas.ko /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/raid_class.ko \x0d\x0a\x0d\x0a以上命令表示显示系统中所有的编译模块,并过滤包含有raid字符串的模块。
假设模块的源文件为hello.c,源码如下:
使用该文件编译内核模块。
正常情况下,Makefile文件内容如下:
执行 make 命令,生成hello.ko文件。
执行 sudo insmod hello.ko 命令,安装该模块。
执行 lsmod 命令,查看安装的模块。就会看到第一行的就是hello模块。
但是,如果想自定义模块名称为 xmodule ,而不是默认的 hello ,如何实现呢?方法如下:
在Makefile中重命名obj-m并将obj-m的依赖关系设置为原始模块(hello)
修改后的Makefile文件内容如下:
将obj-m设置为 xmodule .o,并使 xmodule .o依赖于 hello .o.
执行 make 命令后,生成 xmodule .ko, 而不是 hello .ko,
安装命令: sudo insmod xmodule.ko
查看命令: lsmod ,就会看到被安装名为 xmodule 的模块。
linux下编译运行驱动
嵌入式linux下设备驱动的运行和linux x86 pc下运行设备驱动是类似的,由于手头没有嵌入式linux设备,先在vmware上的linux上学习驱动开发。
按照如下方法就可以成功编译出hello world模块驱动。
1、首先确定本机linux版本
怎么查看Linux的内核kernel版本?
'uname'是Linux/unix系统中用来查看系统信息的命令,适用于所有Linux发行版。配合使用'uname'参数可以查看当前服务器内核运行的各个状态。
#uname -a
Linux whh 3.5.0-19-generic #30-Ubuntu SMPTue Nov 13 17:49:53 UTC 2012 i686 i686 i686 GNU/Linux
只打印内核版本,以及主要和次要版本:
#uname -r
3.5.0-19-generic
要打印系统的体系架构类型,即的机器是32位还是64位,使用:
#uname -p
i686
/proc/version 文件也包含系统内核信息:
# cat /proc/version
Linux version 3.5.0-19-generic(buildd@aatxe) (gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) ) #30-UbuntuSMP Tue Nov 13 17:49:53 UTC 2012
发现自己的机器linux版本是:3.5.0-19-generic
2、下载机器内核对应linux源码
到下面网站可以下载各个版本linux源码
如我的机器3.5.0版本源码下载地址为:
下载完后,找一个路径解压,如我解压到/linux-3.5/
然后很重要的一步是:执行命令uname -r,可以看到Ubuntu的版本信息是3.5.0-19-generic
。进入linux源码目录,编辑Makefile,将EXTRAVERSION = 修改为EXTRAVERSION= -19-generic。
这些都是要配置源码的版本号与系统版本号,如果源码版本号和系统版本号不一致,在加载模块的时候会出现如下错误:insmod: error inserting 'hello.ko': -1 Invalid module format。
原因很明确:编译时用的hello.ko的kenerl 不是我的pc的kenerl版本。
执行命令cp /boot/config-3.5.0-19-generic ./config,覆盖原有配置文件。
进入linux源码目录,执行make menuconfig配置内核,执行make编译内核。
3、写一个最简单的linux驱动代码hello.c
/*======================================================================
Asimple kernel module: "hello world"
======================================================================*/
#include linux/init.h
#include linux/module.h
MODULE_LICENSE("zeroboundaryBSD/GPL");
static int hello_init(void)
{
printk(KERN_INFO"Hello World enter\n");
return0;
}
static void hello_exit(void)
{
printk(KERN_INFO"Hello World exit\n ");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("zeroboundary");
MODULE_DESCRIPTION("A simple HelloWorld Module");
MODULE_ALIAS("a simplestmodule");
4、写一个Makefile对源码进行编译
KERN_DIR = /linux-3.5
all:
make-C $(KERN_DIR) M=`pwd` modules
clean:
make-C $(KERN_DIR) M=`pwd` clean
obj-m += hello.o
5、模块加载卸载测试
insmod hello.ko
rmmod hello.ko
然后dmesg|tail就可以看见结果了
最后,再次编译驱动程序hello.c得到hello.ko。执行insmod ./hello.ko,即可正确insert模块。
使用insmod hello.ko 将该Module加入内核中。在这里需要注意的是要用 su 命令切换到root用户,否则会显示如下的错误:insmod: error inserting 'hello.ko': -1 Operation not permitted
内核模块版本信息的命令为modinfo hello.ko
通过lsmod命令可以查看驱动是否成功加载到内核中
通过insmod命令加载刚编译成功的time.ko模块后,似乎系统没有反应,也没看到打印信息。而事实上,内核模块的打印信息一般不会打印在终端上。驱动的打印都在内核日志中,我们可以使用dmesg命令查看内核日志信息。dmesg|tail
可能还会遇到这种问题insmod: error inserting 'hello.ko': -1 Invalid module format
用dmesg|tail查看内核日志详细错误
disagrees about version of symbolmodule_layout,详细看这里。
在X86上我的办法是:
make -C/usr/src/linux-headers-3.5.0-19-generic SUBDIRS=$PWD modules