我们平时使用的用户程序和驱动程序不一样,驱动程序作为一个模块连接到内核模块并运行在内核空间里。引用LDD上的一句话“因为2.6内核的模块要和内核源代码树中的目标文件连接,通过这种方式,可得到一个更加健壮的模块加载器,但是需要这些目标文件存在于内核目录树中”。这里提到的内核目录树就是我们在运行我们自己构造的模块前,需要在我们的系统中已经配置好内核源代码树,然后在把构造好的目标模块和内核树连接起来再运行。
查看自己的系统里有没有配置好内核树的方法:在/lib/modules/2.6.35-30-generic目录下面,看看有没有build文件夹,如果有的话,说明我们的系统里已经有内核树了,如果没有的话,就需要自己构建一个内核树了。
1.安装编译内核所需软件
sudo apt-get install build-essential kernel-package libncurses5-dev fakeroot
2.下载内核源码编译
apt-cache search linux-source
apt-get install Linux-source-2.6.35
tar jxvf linux-source-2.6.32.tar.bz2
sudo cp /boot/config-2.6.35-30-generic /usr/src/linux-source-2.6.35/.config
cd /usr/src/linux-source-2.6.35
make menuconfig
make
make bzImage
make modules
make modules_install
最终在/lib/modules目录下面产生一个目录
Makefile 文件,其中default和clean下面一行是以Tab键开头
#如果已经定义KERNELRELEASE,则说明是从内核构造系统调用的
#因此可利用其内建语句
ifneq ($(KERNELRELEASE),)
obj-m := helloworld.o
helloworld-objs += hello.o
#否则,是直接从命令行调用的
#这时要调用内核构造系统
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.ko .*.cmd .tmp_versions Module.* modules.* *.mod.c
KERNELRELEASE是内核顶层Makefile定义的变量,首次执行本例的Makefile时KERNELRELEASE变量没有被定义,所以执行else分支;
-C $(KERNELDIR) 表示跳转到内核源代码目录执行那里的Makefile
M=$(PWD)表示然后返回到当前目录继续读入、执行当前的Makefile
当从内核源码目录返回后,KERNELRELEASE变量已经定义,执行if分支
linux内核makefile体系参考:
http://blog.csdn.net/artechtor/article/details/1424619
hellowork.ko
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
static int namecount = 3;
static char* whom = "world";
static int howmany = 1;
static char* namelist[] = {"init1", "init2", "init3", "init4", "init5"};
static int __init hello_init(void)
{
int i = 0;
for(i = 0; i < howmany; i++)
{
printk(KERN_ALERT "Hello %s,index:%d\n", whom, i+1);
}
printk(KERN_ALERT "countof(namelist):%d\n", sizeof(namelist)/sizeof(char*));
for(i = 0; i < namecount; i++)
{
printk(KERN_ALERT "Hello %s,index:%d\n", namelist[i], i+1);
}
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye....\n");
return;
}
module_init(hello_init);
module_exit(hello_exit);
module_param(howmany, int, 0);
module_param(whom, charp, S_IRUGO);
module_param_array(namelist, charp, &namecount, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
insmod helloworld.ko howmany=10 whom=kitty namelist=john,jack,cathy
内核支持的模块参数类型如下:
bool
invbool
布尔值(取true或false),关联的变量应该是int型。invbool类型反转其值,也就是说,true值变成false,而false变成true。
charp
字符指针指。内核会为用户提供字符串分配内存,并相应设置指针。
int
long
short
uint
ulong
ushort
具有不同长度的基本整数值。以u开头的类型用于无符号数。
模块装载器也支持数组参数,在提供数组值时用逗号划分各数组成员。要声明数组参数,需要使用下面的宏:
module_param_array(name, type,num,perm);
其中,name是数组的名称(也就是模块参数的名称),type是数组元素类型,num是一个整数变量用于记录输入的数组元素个数,而perm是常见的访问许可值。
如果在装载时设置数组参数,则num会被设置为用户提供的值的个数。模块装载器会拒绝接受超过数组大小的值(本例中加载时设置namelist参数超过5个时加载失败)。
perm参数:这个值用来控制谁能访问sysfs中对模块参数的表述(在/sys/module/helloword/parameters目录下面有三个文件:whom/howmany/namelist分别对应三个模块参数,文件内容为模块加载时的参数值。)。如果perm值被设置为0,则不会有对应的sysfs入口项(如果whom参数的给标志被置为0,则在/sys/module/helloword/parameters目录下面没有whom文件);否则,模块参数会在/sys/module中出现,并设置为给定的访问许可。
S_IRUGO 在<linux/stat.h>中定义,如果一个参数通过sysfs被修改,则如果模块修改了这个参数的值一样,但是内核不会以任何方式通知模块。通常情况下,不应该设置模块参数为可写的,除非打算检测这种修改并作出相应的动作。