环境:Fedora 12
内核:Linux-2.6.32.2
一、实验
STEP 1:新建一个文件hello.c,内容如下:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("DUAL BSD/GPL");
static int hello_init(void) { printk(KERN_ALERT "hello,world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "goodbye,kernel\n");
}
module_init(hello_init);
module_exit(hello_exit);
|
STEP 2:在hello.c的同一个目录下,新建一个Makefile,内容如下:
obj-m := hello.o
KERNELBUILD := /lib/modules/`uname -r`/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o .*.cmd *.ko *.mod.c .tmp_versions
|
STEP 3:编译,执行make 命令。
STEP 4:装载模块。
STEP 5:卸载模块。
STEP 6:查看系统日志文件。printk函数把内容写入了/var/log/messages里面了。
二、对实验的理解
Q1:为什么模块能够调用printk函数?
在insmod函数装入模块后,模块就连接到了内核,因而可以访问内核的公用符合表。
Q2:操作模块的相关命令?
insmod xx.ko //将模块的代码和数据装入内核,然后使用内核的符合表解析模块中未解析的符号
rmmod xx.ko //从内核中移除模块
lsmod //通过读取/proc/modules,打印出当前装载到内核中的模块
modprobe xx.ko //解析依赖性功能+insmod
Q3:什么是内核符号表?
insmod通过内核符号表来解析模块中未定义的符号。公共内核符号表包含了所有的全局内核项(函数和变量)的地址。当模块被装入内核后,他所导出的任何符号都会变成内核符号表的一部分。新模块可以使用我们自己导出的符号表,从而可以用一个个模块堆叠出一个新的模块,此为模块层叠技术。
如果一个模块需要向其他模块导出符号,则应该使用下面的宏:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
Q4:所有模块都应该包含的头文件?
#include //包含有可装载模块需要的大量符号和函数的定义
#include //指定初始化和清除函数
#include //在装载模块时向模块传递参数,需要这个头文件
…… //其他头文件根据具体需要再加入
Q5:Makefile做了哪些事情?
#表明模块是由目标文件hello.o组成
obj-m := hello.o
#定义了一个变量,表示模块所在的目录,uname -r的结果为当前运行的内核版本 KERNELBUILD := /lib/modules/`uname -r`/build
#-C 的作用是改变目录到 $(KERNELBUILD)指定的位置;M=的作用是让该makefile在构造#modules之前返回到模块源代码目录;modules为make的目录,记得编译内核时有一步是 #make modules。总结,make modules发生的地方在${KERNELBUILD},在make moddules之 #前make编译的地方在${shell pwd} default: make -C $(KERNELBUILD) M=$(shell pwd) modules
#删除make生成的文件 clean:
rm -rf *.o .*.cmd *.ko *.mod.c .tmp_versions
|
用户空间的应用程序可以接受用户的参数,LKM也可以做到,只是方式有些不同而已。相关的宏有:
MODULE_PARM(var, type);
MODULE_PARM_DESC(var, "the description of the var");
模块参数的类型(即MODULE_PARM中的type)有一下几种:
- b byte(unsigned char)
- h short
- i int
- l long
- s string(char*)
这些参数最好有默认值,如果有些必要参数用户没有设置可以通过在module_init指定的init函数返回负值来拒绝模块的加载。 LKM还支持数组类型的模块,如果在类型符号前加上数字n则表示最大程度为n的数组,用“-”隔开的数字分别代表最小和最大的数组长度。
示例:
MODULE_PARM(var, "4i"); // 最大长度为4的整形数组
MODULE_PARM(var, "2-6i"); // 最小长度为2,最大长度为6的整形数组
如何用insmod传入参数,其实man一下就可以了,不过现在的man有些过于简单,所以在此说明一下:
insmod variable=value[,value2...] ...
其中value可以用引号括起来,也可以不用。但是有一点“=”前后不能留有空格,并且value中也不能有空格。
三、带参数的模块加载和卸载实验
STEP 1:把hello.c的代码改为如下:
(根据http://blog.chinaunix.net/u1/55468/showart_2151108.html修改)
/*file: hello.c*/ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h>
MODULE_AUTHOR("xiaosuo "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("This module is a example."); static int int_var = 0; static const char *str_var = "default"; static int int_array[6]; module_param(int_var, int,S_IRUGO); //MODULE_PARM_DESC(int_var, "A integer variable"); module_param(str_var, charp,S_IRUGO); //MODULE_PARM_DESC(str_var, "A string variable"); module_param_array(int_array,int,5,S_IRUGO); //MODULE_PARM_DESC(int_array, "A integer array"); static int __init hello_init(void) { int i; printk(KERN_ALERT "Hello, my LKM.\n"); printk(KERN_ALERT "int_var %d.\n", int_var); printk(KERN_ALERT "str_var %s.\n", str_var); for(i = 0; i < 6; i ++){ printk("int_array[%d] = %d\n", i, int_array[i]); } return 0; } static void __exit hello_exit(void) { printk(KERN_ALERT "Bye, my LKM.\n"); } module_init(hello_init); module_exit(hello_exit)
|
STEP 2:make
STEP 3:输入命令insmod hello.ko int_var=1001.
STEP 4:查看int_var的值是否是我们输入的值:cat /var/log/messages.
参考:《Linux设备驱动程序》
http://blog.chinaunix.net/u1/55468/showart_2151108.html
下一篇博客:linux驱动程序2,将主要分析文件的本质,驱动程序与文件系统接口,内核和应用程序是如何交互的,应用程序对设备的操作是怎样具体一步步深入到硬件设备的。
阅读(1239) | 评论(0) | 转发(0) |