一、Hello World模块
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, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
Makefile
# if KERNELRELEASE is defined, we've benn 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
moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核
printk函数和print函数类似,字串KERN_ALERT是消息优先级
与应用程序的不同
一个终止的应用程序可以在释放资源方面懒惰, 或者完全不做清理工作。
但是模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启.。
和应用程序不同,如果没有库连接到模块中,源文件不应当包含通常的头文件。只有内核不部分的函数才可以在内核模块使用
一个内核的段错误至少会杀掉当前进程,如果不终止整个系统。
用户空间和内核空间
模块在内核空间运行,应用程序在用户控件运行。
系统至少有两种级别,内核(超级模式),最低级模式(用户模式),以此控制对硬件的直接存取以及对内存的非法访问。
内核空间和用户空间,都有自己对应的内存映射(自己的地址空间)
内核的并发
内核编程找那个有几个并发的来源,linux系统中运行多个进程,在同一时间,不止一个进程能够试图使用你的驱动
当你查看内核 API 时, 你会遇到以双下划线(__)开始的函数名. 这样标志的函数名通常是一个低层的接口组件, 应当小心使用.
内核的版本检查
UTS_RELEASE
这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, "2.6.10".
LINUX_VERSION_CODE
这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0x02060a ). [4]4有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
KERNEL_VERSION(major,minor,release)
这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如,KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.
模块的堆叠
可以使用modeprobe,类似于insmod。
如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
几乎所有模块都有
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL")
MODULE_AUTHOR(声明谁编写了模块)
MODULE_DESCRIPION(一个人刻度的关于模块做什么的声明)
MODULE_VERSION(一个diamante修订版本号)
MODULE_ALIAS(模块为人所知的另一个名字)
MODULE_DEVICE_TABLE(来告知用胡空间,模块支持哪些设备)
模块的初始化和关停
模块的初始化通常是:
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initializatioon_function);
__init标志是给内核的一个暗示,给定的函数只是在初始化使用,加载后会丢掉这个初始化函数。
__initdata给只在初始化时用的数据,后面可能还会有__devinit和__devinitdata
清理函数
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
清理函数没有返回值,__exit修饰符用于模块卸载
初始化汇总的错误处理
在之策内核设施时,注册可能失败,几遍最简单的动作常常需要内存分配,分配的内存可能不可用。
因此模块代码必须一致检查返回值,并且确认要求的操作实际上已经成功。
如果注册时发生任何错误,首先第一的事情是决定模块是否能够五路你如何继续初始化它自己。
任何时候,你的模块应当尽力向前,并提供事情失败后具备的能力。
如何模块证实失败,不能完全加载,必须取消失败前注册动作。因此初始化某个点失败,模块必须自己退回所有东西。否则内核就处于不稳定状态。
使用goto是处理错误回复的最好情况
int _init my_init_function(void) { int err; err = register_this(ptr1, "skull"); /* registration takes a pointer and a name */ 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; /* propagate the error */ }
err是错误码,在<linux/errno.h>中。显然在模块清理函数中,是按照注册时相反的顺序注销设施。
void __exit my_cleanup_function(void) { unregister_those(ptr3, "skull"); unregister_that(ptr2, "skull"); unregister_this(ptr1, "skull"); return; }
goto难以管理,清理函数必须撤销注册前的每一项
struct something *item1; struct somethingelse *item2; int stuff_ok; void my_cleanup(void) { if(item1) release_thing(item1); if(item2) release_thing2(item2); if(stuff_ok) unregister_stuff(); return; } int __init my_init(void) { int err = -ENOMEM; item1 = allocate_thing(arguments); item2 = allocate_thing2(arguments2); if(!item2 || !item2) goto fail; err = register_stuff(item1, item2); if(!err) stuff_ok = 1; else goto fail; return 0; fail: my_cleanup(); return err; }
模块加载竞争
内核的某些别的部分会在注册完成之后马上使用任何你注册的设施。换句话说,内核将调用进你的模块,在你初始化函数任然在运行时,所以你的代码必须准备好被调用,一旦完成注册。
模块参数
insmod hellop howmany=10 whom="Mom"
在模块中参数用module_param宏定义来声明,它定义在moduleparam.h module_param使用了3个参数:变量名,类型,以及一个权掩码用来做一个辅助的sysfs入口。
static char *whom = "world"; static int howmany = 1; mdoule_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);
模块参数支持许多类型:
bool、invbool 一个布尔型,invbool颠倒了值
charp 字符指针
int、long、short、uint、ulong、ushort 整型,u开头是无符号值
数组参数,用逗号间隔的列表提供的值,模块加载者也支持。
module_param_array(name, type, num, perm);
name:数组名
type:数组元素类型
num:整型变量
perm:通常的权限值