这里不为真实的硬件设备编写内核驱动程序,为了方便描述编写内核驱动程序的过程,我们使用一个虚拟的硬件设备。先大概看下内核驱动程序的编写流程,例子没有实际意义,只是模板。
1.在kernel/drivers目录下新建hello文件夹,在该文件夹中创建hello.c文件:
#include
#include
static int __init hello_init(void) {
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "Goodbye, cruel world\n");
}
MODULE_LICENSE("Dual BSD/GPL");
module_init(hello_init);
module_exit(hello_exit);
__init和__exit标志是给内核的暗示,给定的函数只能在加载和卸载模块时调用。
printk函数在Linux内核中定义并且对模块可用,因为在insmod加载了它之后,模块被连接到内核并且可存取内核的公用符号(函数和变量)。KERN_ALERT是消息的优先级。
2.在hello目录中新增Makefile文件,Makefile是执行编译命令make时用到的:
# If KERNELRELEASE is defined, we've been invoked from the kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
3.在hello目录下键入make执行Makefile文件进行编译,执行结果如下图:
4.加载和卸载模块(只有超级用户才可以加载和卸载模块)
modprobe 类似于insmod,加载一个模块到内核。它的不同在于它会查看要加载的模块是否引用了当前内核没有定义的符号。如果有,modprobe在定义相关符号的模块路径中寻找相关模块,当找到这些模块时,它也把它们加载到内核。
lsmod 查看内核中当前加载的模块列表。
模块代码中应当指定它使用的许可,如:
MODULE_LICENSE("Dual BSD/GPL");
也可以在模块中包含其他描述性的定义,如:MODULE_AUTHOR(声明模块的作者),MODULE_DESCRIPTION(声明关于模块的描述),MODULE_VERSION(版本号),MODULE_ALIAS(模块的别名)以及MODULE_DEVICE_TABLE(模块支持的设备)。
编写习惯是把以MODULE_开头的声明性代码放在文件的末尾。
初始化中的错误处理
在注册内核模块时可能会失败,因为即便最简单的操作也需要分配内存,分配的内存可能不可用,因此模块代码必须一直检查返回值,并确认操作执行成功。如果在注册模块时发生错误导致模块不能正常运行,则必须注销失败前所执行的操作。错误恢复常用goto语句。如:
static int __init my_init_function(void) {
int err;
err = register_this(ptr1, "skull");
if (err) {
goto fail_this;
}
err = register_that(ptr2, "skull");
if (err) {
goto fail_that;
}
err = register_those(ptr3, "skull");
if (err) {
goto fail_those;
}
return 0; // success
fail_those:
unregister_that(ptr2, "skull");
fail_that:
unregister_this(ptr1, "skull");
fail_this:
return err;
}
卸载时按照注册时相反的顺序执行注销操作,卸载模块代码:
static void __exit my_cleanup_function(void) {
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
在使用insmod或者modprobe加载模块时可以指定可变参数的值,后者也可以从它的配置文件(/etc/modprobe.conf)读取参数的值。参数用module_param宏定义来声明,它定义在moduleparam.h文件中。module_param使用了3个参数:变量名、变量类型以及权限掩码。如对hello world模块进行改进(称为hellop),增加两个参数:一个整型值howmany,一个字符串whom:
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
insmod hellop howmany=10 whom="Haha"
模块参数支持许多类型:
bool、invbool:invbool类型与bool相反,真值为false。
charp:字符指针值。
int,long,short,uint,ulong,ushort:u开头的为无符号值。
数组参数module_param_array(name, type, num, perm);
name是数组的名字,type是数组元素的类型,num是一个整型变量,perm是通常的权限值。
权限值定义在
当前进程
尽管内核模块不像应用程序一样顺序执行,但是内核做的大部分操作是代表一个特定进程的。内核代码可以通过存取全局项current引用当前进程,它在
如下面的语句是通过存取task_struct结构中的某些字段打印了当前进程的进程id和命令名称:
printk(KERN_INFO "The process is \"%s\" (pid %i)\n", current->comm, current->pid);
下一篇在此模板基础上编写一个字符驱动。