这是一个简单而完整的实例,对于理解Linux下的驱动模块是非常有帮助的。
1.源码如下:
/*
* hello.c -- the example of printf "hello world!" in the screen of driver program
*/
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("Dual BSD/GPL");/* declare the license of the mole ,it is necessary */
static int hello_init(void)
{
printk(KERN_ALERT "Hello World enter!\n");
return 0;
}
static int hello_exit(void)
{
printk(KERN_ALERT "Hello world exit!\n");
}
mole_init(hello_init); /* load the mole */
mole_exit(hello_exit); /* unload the mole */
进入目录:
[root@Alex_linux /]#cd /work/jiakun_test/moletest
[root@Alex_linux moletest]# vi hello.c
然后拷入上面书上的源码。
2.编译代码:
1>.首先我在2.4内核的虚拟机上进行编译,编译过程如下:
[root@Alex_linux moletest]#gcc -D__KERNEL__ -I /usr/src/linux -DMODULE -Wall -O2 -c -o hello.o hello.c
其中-I选项指定内河源码,也就是内核源码树路径。编译结果:
hello.c:1:22: net/sock.h: No such file or directory
hello.c: In function `hello_init':
hello.c:6: warning: implicit declaration of function `printk'
hello.c:6: `KERN_ALERT' undeclared (first use in this function)
hello.c:6: (Each undeclared identifier is reported only once
hello.c:6: for each function it appears in.)
hello.c:6: parse error before string constant
hello.c: In function `hello_exit':
hello.c:11: `KERN_ALERT' undeclared (first use in this function)
hello.c:11: parse error before string constant
hello.c: At top level:
hello.c:13: warning: type defaults to `int' in declaration of `mole_init'
hello.c:13: warning: parameter names (without types) in function declaration
hello.c:13: warning: data definition has no type or storage class
hello.c:14: warning: type defaults to `int' in declaration of `mole_exit'
hello.c:14: warning: parameter names (without types) in function declaration
hello.c:14: warning: data definition has no type or storage class
在网上查询有网友提示没有引入kernel.h
解决:vi hello.c
在第一行加入:#include <linux/kernel.h>
再次编译仍然报KERN_ALERT没有声明
修改编译条件-I,再次编译:
[root@Alex_linux moletest]#gcc -D__KERNEL__ -I /usr/src/linux -DMODULE -Wall -O2 -c -o hello.o hello.c
[root@Alex_linux moletest]#ls
hello.c hello.o Makefile
[root@Alex_linux moletest]#
2>.接着我尝试在2.6内核的虚拟机上进行编译
编译过程如下:
[root@JiaKun moletest]# ls
hello.c makefile
[root@JiaKun moletest]# vi hello.c
[root@JiaKun moletest]# make
make -C /mylinux/kernel/2.4.18-rmk7 M=/home/alex/test/moletest moles
make: *** /mylinux/kernel/2.4.18-rmk7: No such file or directory. Stop.
make: *** [moles] Error 2
[root@JiaKun moletest]# vi makefile
[root@JiaKun moletest]# make
make -C /usr/src/kernels/2.6.18-53.el5-i686 M=/home/alex/test/moletest moles
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
scripts/Makefile.build:17: /home/alex/test/moletest/Makefile: No such file or directory
make[2]: *** No rule to make target `/home/alex/test/moletest/Makefile'. Stop.
make[1]: *** [_mole_/home/alex/test/moletest] Error 2
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
make: *** [moles] Error 2
[root@JiaKun moletest]# mv makefile Makefile
[root@JiaKun moletest]# make
make -C /usr/src/kernels/2.6.18-53.el5-i686 M=/home/alex/test/moletest moles
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
CC [M] /home/alex/test/moletest/hello.o
Building moles, stage 2.
MODPOST
CC /home/alex/test/moletest/hello.mod.o
LD [M] /home/alex/test/moletest/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
[root@JiaKun moletest]# ls
hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile Mole.symvers
3.执行代码,加载驱动模块:
2.4内核加载模块:
insmod ./hello.o
但是此时并没有输出printk打印的信息。但是可以在/var/log/messages 中看到打印的信息,这是由于KERN_ALERT优先级不够高。这里
需要修改为:KERN_EMERG。再次编译,加载模块即可以看到结果
2.6内核加载模块:
[root@JiaKun moletest]# insmod hello.ko
[root@JiaKun moletest]#
Message from syslogd@ at Sat Jul 26 19:52:44 2008 ...
JiaKun kernel: Hello, world
有的朋友可能会出现insmod命令找不到的错误,这可能有下面几个原因:
<1> 你的系统没有安装mole-init-tools工具,关于此问题,只需安装即可,但是一般装完系统是有这个命令的。
<2> 环境变量没有添加导致不能使用该命令。使用echo $PATH即可查看PATH环境变量,发现没有/sbin这个路径,所以你当然不能使用insmod这个命令了。解决的方法很简单,只需在命令行输入:
PATH = "$PATH:/sbin"即可添加。(insmod在/sbin这个目录下,你可以使用whereis insmod查看)。
<3> insmod这个命令需要在root权限下才能使用。
加载完成后你可以输入lsmod查看hello这个模块哦。
4.卸载驱动模块:rmmod hello.
加载模块后就可在屏幕上看到如下信息:Hello world enter.
卸载时就可在屏幕上看到如下信息:hello world exit.
[root@JiaKun moletest]# rmmod hello.ko
[root@JiaKun moletest]#
Message from syslogd@ at Sat Jul 26 19:52:58 2008 ...
JiaKun kernel: Goodbye, cruel world
另外,如果有多个文件,则按下列方式编写Makefile文件(file1.c、file2.c):
obj -m := molename.o
mole-objs := file1.o file2.o
Ⅱ 如何编译linux驱动模块
第一步:准备源代码
首先我们还是要来编写一个符合linux格式的模块文件,这样我们才能开始我们的模块编译。假设我们有一个源文件mymod.c。它的源码如下:
mymoles.c
1. #include <linux/mole.h> /* 引入与模块相关的宏 */
2. #include <linux/init.h> /* 引入mole_init() mole_exit()函数 */
3. #include <linux/moleparam.h> /* 引入mole_param() */
4
5. MODULE_AUTHOR("Yu Qiang");
6. MODULE_LICENSE("GPL");
7
8. static int nbr = 10;
9. mole_param(nbr, int, S_IRUGO);
10.
11. static int __init yuer_init(void)
12.{
13. int i;
14. for(i=0; i<nbr; 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 mole, I have been unlad./n");
24.}
25.
26. mole_init(yuer_init);
27. mole_exit(yuer_exit);
我们的源文件就准备的差不多了,这就是一个linux下的模块的基本结构。第9行是导出我们的符号变量nbr。这样在你加载这个模块的时候可以动态修改这个变量的值。稍后将演示。yuer_init()函数将在模块加载的时候运行,通过输出的结果可以看到我们的模块是否加载成功。
第二步:编写Makefile文件
首先还是来看看我们Makefile的源文件,然后我们再来解释;
Makefile
obj-m := moles.o #要生成的模块名
moles-objs:= mymod.o #生成这个模块名所需要的目标文件
KDIR := /lib/moles/`uname -r`/build
PWD := $(shell pwd)
default:
make -C $(KDIR) M=$(PWD) moles
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) moles
clean:
rm -rf *.o
在arm板上插入是
insmod mymod
如果出现以下错误
insmod: chdir(/lib/moles): No such file or directory
则运行
mkdir /lib/moles/2.6.25 (与arm内核版本相同)
并将mymod.ko文件复制到该目录下
cp mymod.ko /lib/moles/2.6.25
然后再执行 (insmod 只在/lib/moles/2.6.25目录下查找相关驱动模块)
insmod mymod
现在我来说明一下这个Makefile。请记住是大写的Makefile而不是小写的makefile;
obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj-m := <模块名>.o
moles-objs :这个变量是说明声称模块moles需要的目标文件 格式要求 <模块名>-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/桌面/mymole/mymoles.o
LD [M] /home/yuqiang/桌面/mymole/moles.o
Building moles, stage 2.
MODPOST 1 moles
CC /home/yuqiang/桌面/mymole/moles.mod.o
LD [M] /home/yuqiang/桌面/mymole/moles.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-24-generic'
第四步:加载/卸载我们的模块
从上面的编译中我可以看到。已经有一个moles.ko生成了。这就是我们的模块了。现在我们就可以来加载了。
首先在终端输入:sudo insmod moles.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 moles
在终端输入:dmesg | tail -3
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
[20412.046932] I come from yuer's mole, I have been unlad.
可以从打印的信息中看到,我们的模块的退出函数已经被执行了。说明我们的模块已经被成功的卸载了。到目前位置我们就已经算是对模块的编译到编译运行算是有了一个整体上的认识了。对于以后深入的学习还是应该有点帮助的。下面我们将在看看于模块相关的一些简单的操作。
第五步:加载模块时传递参数
在终端输入:sudo insmod mole_name.ko nbr=4
在终端输入:dmesg | tail -6
显示结果如下:
[20800.655694] Hello, How are you. 9
[21318.675593] I come from onefile mole, 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/mole/<moles_name>/parameters/来查看。在这里我们是这样输入的
在终端输入:ls /sys/moedle/moles/parameters/
显示结果:
nbr
如果我们的模块加载成功了。最后我们还可以通过modinfo来查看我们的模块信息。如下
在终端输入:sudo modinfo moles.ko
显示结果:
filename: moles.ko
license: GPL
author: Yu Qiang
srcversion: 20E9C3C4E02D130E6E92533
depends:
vermagic: 2.6.24-24-generic SMP mod_unload 586
parm: nbr:int
Ⅲ 如何编译一个linux下的驱动模块
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源码https://www.kernel.org/
如我的机器3.5.0版本源码下载地址为:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.5.tar.bz2
下载完后,找一个路径解压,如我解压到/linux-3.5/
然后很重要的一步是:执行命令uname -r,可以看到Ubuntu的版本信息是3.5.0-19-generic
。进入linux源码目录,编辑Makefile,将EXTRAVERSION = 修改为EXTRAVERSION= -19-generic。
这些都是要配置源码的版本号与系统版本号,如果源码版本号和系统版本号不一致,在加载模块的时候会出现如下错误:insmod: error inserting 'hello.ko': -1 Invalid mole 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 mole: "hello world"
======================================================================*/
#include <linux/init.h>
#include <linux/mole.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 ");
}
mole_init(hello_init);
mole_exit(hello_exit);
MODULE_AUTHOR("zeroboundary");
MODULE_DESCRIPTION("A simple HelloWorld Mole");
MODULE_ALIAS("a simplestmole");
4、写一个Makefile对源码进行编译
KERN_DIR = /linux-3.5
all:
make-C $(KERN_DIR) M=`pwd` moles
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 将该Mole加入内核中。在这里需要注意的是要用 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 mole format
用dmesg|tail查看内核日志详细错误
disagrees about version of symbolmole_layout,详细看这里。
http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmoles/index.html
在X86上我的办法是:
make -C/usr/src/linux-headers-3.5.0-19-generic SUBDIRS=$PWD moles
Ⅳ linux编译内核时怎么单个编译一个特定模块
从网上找一个编译模块的makefile,放到你的模块的文件夹里面,然后修改里面的路径指定编译的内核,以及目标名称。make就可以了。
Ⅳ 如何编译一个linux下的驱动模块
Linux内核源码路径:/usr/src/linux(这个源码是从kernel.org网站download的2.4.18版本)
按照《linux设备驱动开发详解》一书中的步骤实现经典例子"hello,world!"的例子。
具体步骤如下:
=============================================
1.源码如下:
/*
* hello.c -- the example of printf "hello world!" in the screen of driver program
*/
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("Dual BSD/GPL");/* declare the license of the mole ,it is necessary */
static int hello_init(void)
{
printk(KERN_ALERT "Hello World enter!\n");
return 0;
}
static int hello_exit(void)
{
printk(KERN_ALERT "Hello world exit!\n");
}
mole_init(hello_init); /* load the mole */
mole_exit(hello_exit); /* unload the mole */
进入目录:
[root@Alex_linux /]#cd /work/jiakun_test/moletest
[root@Alex_linux moletest]# vi hello.c
然后拷入上面书上的源码。
2.编译代码:
1>.首先我在2.4内核的虚拟机上进行编译,编译过程如下:
[root@Alex_linux moletest]#gcc -D__KERNEL__ -I /usr/src/linux -DMODULE -Wall -O2 -c -o hello.o hello.c
其中-I选项指定内河源码,也就是内核源码树路径。编译结果:
hello.c:1:22: net/sock.h: No such file or directory
hello.c: In function `hello_init':
hello.c:6: warning: implicit declaration of function `printk'
hello.c:6: `KERN_ALERT' undeclared (first use in this function)
hello.c:6: (Each undeclared identifier is reported only once
hello.c:6: for each function it appears in.)
hello.c:6: parse error before string constant
hello.c: In function `hello_exit':
hello.c:11: `KERN_ALERT' undeclared (first use in this function)
hello.c:11: parse error before string constant
hello.c: At top level:
hello.c:13: warning: type defaults to `int' in declaration of `mole_init'
hello.c:13: warning: parameter names (without types) in function declaration
hello.c:13: warning: data definition has no type or storage class
hello.c:14: warning: type defaults to `int' in declaration of `mole_exit'
hello.c:14: warning: parameter names (without types) in function declaration
hello.c:14: warning: data definition has no type or storage class
在网上查询有网友提示没有引入kernel.h
解决:vi hello.c
在第一行加入:#include <linux/kernel.h>
再次编译仍然报KERN_ALERT没有声明
修改编译条件-I,再次编译:
[root@Alex_linux moletest]#gcc -D__KERNEL__ -I /usr/src/linux -DMODULE -Wall -O2 -c -o hello.o hello.c
[root@Alex_linux moletest]#ls
hello.c hello.o Makefile
[root@Alex_linux moletest]#
2>.接着我尝试在2.6内核的虚拟机上进行编译
编译过程如下:
[root@JiaKun moletest]# ls
hello.c makefile
[root@JiaKun moletest]# vi hello.c
[root@JiaKun moletest]# make
make -C /mylinux/kernel/2.4.18-rmk7 M=/home/alex/test/moletest moles
make: *** /mylinux/kernel/2.4.18-rmk7: No such file or directory. Stop.
make: *** [moles] Error 2
[root@JiaKun moletest]# vi makefile
[root@JiaKun moletest]# make
make -C /usr/src/kernels/2.6.18-53.el5-i686 M=/home/alex/test/moletest moles
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
scripts/Makefile.build:17: /home/alex/test/moletest/Makefile: No such file or directory
make[2]: *** No rule to make target `/home/alex/test/moletest/Makefile'. Stop.
make[1]: *** [_mole_/home/alex/test/moletest] Error 2
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
make: *** [moles] Error 2
[root@JiaKun moletest]# mv makefile Makefile
[root@JiaKun moletest]# make
make -C /usr/src/kernels/2.6.18-53.el5-i686 M=/home/alex/test/moletest moles
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
CC [M] /home/alex/test/moletest/hello.o
Building moles, stage 2.
MODPOST
CC /home/alex/test/moletest/hello.mod.o
LD [M] /home/alex/test/moletest/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
[root@JiaKun moletest]# ls
hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile Mole.symvers
3.执行代码,加载驱动模块:
2.4内核加载模块:
insmod ./hello.o
但是此时并没有输出printk打印的信息。但是可以在/var/log/messages 中看到打印的信息,这是由于KERN_ALERT优先级不够高。这里
需要修改为:KERN_EMERG。再次编译,加载模块即可以看到结果
2.6内核加载模块:
[root@JiaKun moletest]# insmod hello.ko
[root@JiaKun moletest]#
Message from syslogd@ at Sat Jul 26 19:52:44 2008 ...
JiaKun kernel: Hello, world
有的朋友可能会出现insmod命令找不到的错误,这可能有下面几个原因:
<1> 你的系统没有安装mole-init-tools工具,关于此问题,只需安装即可,但是一般装完系统是有这个命令的。
<2> 环境变量没有添加导致不能使用该命令。使用echo $PATH即可查看PATH环境变量,发现没有/sbin这个路径,所以你当然不能使用insmod这个命令了。解决的方法很简单,只需在命令行输入:
PATH = "$PATH:/sbin"即可添加。(insmod在/sbin这个目录下,你可以使用whereis insmod查看)。
<3> insmod这个命令需要在root权限下才能使用。
加载完成后你可以输入lsmod查看hello这个模块哦。
4.卸载驱动模块:rmmod hello.
加载模块后就可在屏幕上看到如下信息:Hello world enter.
卸载时就可在屏幕上看到如下信息:hello world exit.
[root@JiaKun moletest]# rmmod hello.ko
[root@JiaKun moletest]#
Message from syslogd@ at Sat Jul 26 19:52:58 2008 ...
JiaKun kernel: Goodbye, cruel world
另外,如果有多个文件,则按下列方式编写Makefile文件(file1.c、file2.c):
obj -m := molename.o
mole-objs := file1.o file2.o
转载仅供参考,版权属于原作者。祝你愉快,满意请采纳哦
Ⅵ 如何编译一个linux下的驱动模块
首先,我们要了解一下模块是如何别被构造的。模块的构造过程与用户空间
的应用程序的构造过程有显着不同;内核是一个大的、独立的程序
,
对于它的各
个部分如何组合在一起有详细的明确的要求。
Linux2.6
内核的构造过程也与以
前版本的内核构造过程不同;
新的构造系统用起来更加简单,
并且可产生更加正
确的结果
,
但是它看起来和先前的方法有很大不同。内核的构造系统非常复杂
,
我们所看到的只是它的一小部分。
如果读者想了解更深入的细节,
则应阅读在内
核源码中的
Document/kbuild
目录下的文件。
在构造内核模块之前,
有一些先决条件首先应该得到满足。
首先,
读者要保证你
有适合于你的内核版本的编译器、模块工具
,
以及其他必要工具。在内核文档目
录下的文件
Documentation/Changes
里列出了需要的工具版本;
在开始构造内
核前,
读者有必要查看该文件,
并确保已安装了正确的工具。
如果用错误的工具
版本来构造一个内核
(
及其模块
)
,可能导致许多奇怪的问题。另外也要注意
,
使
用太新版本的编译器偶尔可能也会导致问题。
一旦做好了上面的准备工作之后
,
其实给自己的模块创建一个
makefile
则非常
简单。实际上
,
对于本章前面展示的
" hello world"
例子
,
下面一行就够了
:
obj-m := hello.o
如果读者熟悉
make
,
但是对
Linux2.6
内核构造系统不熟悉的话
,
可能奇怪这个
makefile
如何工作。毕竟上面的这一行不是一个传统的
makefile
的样子。问
题的答案当然是内核构造系统处理了余下的工作。上面的赋值语句
(
它利用了由
GNU make
提供的扩展语法
)
说明有一个模块要从目标文件
hello.o
构造,而从
该目标文件构造的模块名称为
hello.ko.
如果我们想由两个源文件
(
比如
file1.c
和
file2.c )
构造出一个名称为
mole.ko
的模块
,
则正确的
makefile
可如下编写
:
obj-m := mole.o
mole-objs := file1.o file2.o
为了让上面这种类型的
makefile
文件正常工作
,
必须在大的内核构造系统环境
中调用他们。假设读者的内核源码数位于
~/kernel-2.6
目录
,
用来建立你的模
块的
make
命令
(
在包含模块源代码和
makefile
的目录下键入
)
应该是
:
make -C ~/kernel-2.6 M=`pwd` moles
这个命令首先是改变目录到用
-C
选项指定的位置
(
即内核源代码目录
)
,其中保
存有内核的顶层
makefile
文件。这个
M=
选项使
makefile
在构造
moles
目
标前
,
返回到模块源码目录。
然后,
moles
目标指向
obj-m
变量中设定的模块,
在上面的例子里,我们将该变量设置成了
mole.o
。
上面这样的
make
命令对于多个文件的编译显得不是很方便
,
于是内核开发者就
开发了一种
makefile
方式
,
这种方式使得内核树之外的模块构造变得更加容易。
代码清单
1.4
展示了
makefile
的编写方法:
代码清单
1.4 makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /source/linux-2.6.13
PWD := $(shell pwd)
moles:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles
moles_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles_install
clean:
rm -rf *.o *~ core .depend .*. *.ko *.mod.c .tmp_versions
.PHONY: moles moles_install clean
else
obj-m := hello.o
endif
我们再次看到了扩展的
GNU
make
语法在起作用。在一个典型的构造过程中,这
个
makefile
将被读取两次。当从命令行中调用这个
makefile ,
它注意到
KERNELRELEASE
变量尚未设置。我们可以注意到,已安装的模块目录中存在一
个符号连接,
它指向内核的构造树,
这样这个
makefile
就可以定位内核的源代
码目录。如果读者时间运行的内核并不是要构造的内核,则可以在命令行提供
KERNELDIR=
选项或者设置
KERNELDIR
环境变量
,
或者修改
makefile
中设置
KERNELDIR
的那一行。在找到内核源码树
,
这个
makefile
会调用
default:
目
标
,
这个目标使用先前描述过的方法第二次运行
make
命令
(
注意,在这个
makefile
里
make
命令被参数化成
$(MAKE))
,以便运行内核构造系统。在第二
次读取
makefile
时,
它设置了
obj-m,
而内核的
makefile
负责真正构造模块。
这种构造模块的机制看起来很繁琐,可是,一旦我们习惯了使用这种机制
,
则会
欣赏内核构造系统带给我们的便利。需要注意的是
,
上面
makefile
并不完整,
一个真正的
makefile
应包含通常用来清除无用文件的目标
,
安装模块的目标等
等。一个完整的例子可以参考例子代码目录的
makefile
。
Ⅶ 如何编写一个简单的linux内核模块和设备驱动程序
如何编写Linux设备驱动程序
回想学习Linux操作系统已经有近一年的时间了,前前后后,零零碎碎的一路学习过来,也该试着写的东西了。也算是给自己能留下一点记忆和回忆吧!由于完全是自学的,以下内容若有不当之处,还请大家多指教。
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux a-z,还有清华bbs上的有关device driver的一些资料。
一、Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
1、对设备初始化和释放。
2、把数据从内核传送到硬件和从硬件读取数据。
3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据。
4、检测和处理设备出现的错误。
在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据未被处理,则先处理其中的内容。
如何编写Linux操作系统下的设备驱动程序
二、实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。
#define __NO_VERSION__
#include <linux/moles.h>
#include <linux/version.h>
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少。Johnsonm说所有的驱动程序的开头都要包含<linux/config.h>,一般来讲最好使用。
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:
struct file_operations
{
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。
下面就开始写子程序。
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include<linux/config.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test(struct inode *node,struct file *file,char *buf,int count)
{
int left;
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考robert着的《Linux内核设计与实现》(第二版)。然而,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。
static int write_tibet(struct inode *inode,struct file *file,const char *buf,int count)
{
return count;
}
static int open_tibet(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
}
static void release_tibet(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。
struct file_operations test_fops = {
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test,
NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
这样,设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。
int init_mole(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number\n");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_mole(void)
{
unregister_chrdev(test_major,"test");
}
在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译 :
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o molename。
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod –f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,dma,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux操作系统下的设备驱动程序
三、设备驱动程序中的一些具体问题
1。 I/O Port。
和硬件打交道离不开I/O Port,老的isa设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:I/O端口的基地址,
参数2:I/O端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的I/O口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。
2。内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages。 请注意,kmalloc等函数返回的是物理地址!
注意,kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。
内存映射的I/O口,寄存器或者是硬件设备的ram(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作dma传送。这块程序需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
3。中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags, const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。
4。一些常见的问题。
对硬件操作,有时时序很重要(关于时序的具体问题就要参考具体的设备芯片手册啦!比如网卡芯片RTL8139)。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序会发生错误。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。
写在后面:学习Linux确实不是一件容易的事情,因为要付出很多精力,也必须具备很好的C语言基础;但是,学习Linux也是一件非常有趣的事情,它里面包含了许多高手的智慧和“幽默”,这些都需要自己亲自动手才能体会到,O(∩_∩)O~哈哈!
Ⅷ 如何编译/交叉编译内核模块, Linux 2.6.
椤�build 能够编译内核树目录内的内核模块,也能够编译内核树目录外的内核模块(外部内核模块)。. 编译外部内核模块的命令: #cd #make -C M=`pwd` 其中 为要编译的内核模块所在目录, 为内核源码所在的目录。 对于发行版本的Linux ,可以用: #make -C /lib/moles/`uname -r`/build M=`pwd` 注意:使用Kbuild 之前,必须先成功编译过内核源码。 说明: .#make -C M=`pwd` moles 作用与上面的命令一样 .以前的内核版本可以使用 #make -C SUBDIRS=`pwd` moles. 安装外部内核模块 #make -C M=`pwd` moles_install 默认安装目录为:/lib/moles/`uname -r`/extra ,可以通过INSTALL_MOD_PATH 宏在默认安装路径前加前缀。 例如: #make -C INSTALL_MOD_PATH=/opt M=`pwd` moles_install 则编译后的模块会放在/opt/lib/moles/`uname -r`/extra 通过宏INSTALL_MOD_DIR 可以修改是否放在'extra' 下,例如: #make -C INSTALL_MOD_DIR=golf M=`pwd` moles_install 则编译后的模块会放在/lib/moles/`uname -r`/golf . 编译单个文件 #make -C M=`pwd` . 其他命令 #make -C M=`pwd` clean #make -C M=`pwd` help.Kbuild 文件 Linux的Kbuild 会在内核模块目录下查找Kbuild 文件,如果有,则在编译时会使用该文件。示例: 假设有这么几个文件:8123_if.c 8123_if.h 8123_pci.c 8123_bin.o_shipped( 二进制的模块文件) Kbuild 文件的内容: obj-m := 8123.o 8123-y:8123_if.o 8123_pci.o 8123_bin.o Makefile的内容: #为了兼容旧版本的Kbuild ifneq($(KERNELRELEASE),) include Kbuildelse# 正常的Makefile KDIR:=/lib/moles/`uname -r`/buildall::$(MAKE) -C $(KDIR) M=`pwd` $@ # 其他targetgenbin:echo "X" > 8123_bin_shippedendif 注意,没有源码的二进制.o 文件必须以原文件名加_shipped 结尾,例如8123_bin.o_shipped,KBuild 会把8123_bin.o_shipped 复制为8123_bin.o ,然后一起编译。 应该用: ifeq ($(obj),) obj= .
Ⅸ 如何编写一个简单的 Linux 内核模块
编写helloworld.c及其对应的Makefile。早前谨
helloworld.c:
#include <linux/mole.h>#include <linux/kernel.h>int init_hello_mole(void)
{
printk("***************Start***************\n");
printk("Hello World! Start of hello world mole!\n"); return 0;
}void exit_hello_mole(void)
{
printk("***************End***************\n");
printk("Hello World! End of hello world mole!\n");
}
MODULE_LICENSE("Dual BSD/GPL");
mole_init(init_hello_mole);
mole_exit(exit_hello_mole);
Makefile:
# To build moles outside of the kernel tree, we run "make"# in the kernel source tree; the Makefile these then includes this# Makefile once again.# This conditional selects whether we are being included from the# kernel Makefile or not.# called from kernel build system: just declare what our moles areobj-m := helloworld.oCROSS_COMPILE =
CC = gcc # Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r) # The current directory is passed to sub-makes as argument
PWD := $(shell pwd)all: molesmoles:
$(MAKE) -C $(KERNELDIR) M=$(PWD) molesclean:
rm -rf *.o *~ core .depend *.symvers .*.cmd *.ko *.mod.c .tmp_versions $(TARGET)
在Makefile中,在obj-m := helloworld.o这句中,.o的文件名要与编译的.c文件名一致。悔物
KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r)指示当前linux系统内核的源码位置。陆基
Ⅹ linux内核编译过程中选项为m的模块是单独编译的对吗
linux内核编译过程中选项为m的模块是单独编译的是对的,其软件的性能和质量都是不错的