从Hello World开始,编程学习的永远开端
#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, linux device driver world\n"); }
module_init(hello_init); module_exit(hello_exit);
|
Makefile文件
PWD := $(shell pwd) KERNELVER = $(shell uname -r) KERNELDIR = /usr/src/linux-$(KERNELVER) INSTALLDIR = /lib/modules/$(KERNELVER)/kernel/dt/
#mymodule-objs := file1.o file2.o #obj-m := mymodule.o obj-m := hello.o
modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install: cp hello.ko $(INSTALLDIR)
clean: rm -rf *.o *.ko *.mod.* modules.order Module.symvers
.PHONY: modules modules_install clean
|
编译模块
debian:~/Desktop/dt/modules/hello# ls hello.c Makefile debian:~/Desktop/dt/modules/hello# make make -C /usr/src/linux-2.6.25 M=/root/Desktop/dt/modules/hello modules make[1]: Entering directory `/usr/src/linux-2.6.25' CC [M] /root/Desktop/dt/modules/hello/hello.o Building modules, stage 2. MODPOST 1 modules CC /root/Desktop/dt/modules/hello/hello.mod.o LD [M] /root/Desktop/dt/modules/hello/hello.ko make[1]: Leaving directory `/usr/src/linux-2.6.25' debian:~/Desktop/dt/modules/hello# make modules make -C /usr/src/linux-2.6.25 M=/root/Desktop/dt/modules/hello modules make[1]: Entering directory `/usr/src/linux-2.6.25' Building modules, stage 2. MODPOST 1 modules make[1]: Leaving directory `/usr/src/linux-2.6.25' debian:~/Desktop/dt/modules/hello# make modules_install cp hello.ko /lib/modules/2.6.25/kernel/dt/ debian:~/Desktop/dt/modules/hello#
|
装载,卸载模块
debian:~/Desktop/dt/modules/hello# lsmod | grep hello debian:~/Desktop/dt/modules/hello# ls hello.c hello.mod.c hello.o modules.order hello.ko hello.mod.o Makefile Module.symvers debian:~/Desktop/dt/modules/hello# insmod hello.ko debian:~/Desktop/dt/modules/hello# lsmod | grep hello hello 1376 0 debian:~/Desktop/dt/modules/hello# rmmod hello debian:~/Desktop/dt/modules/hello# lsmod | grep hello debian:~/Desktop/dt/modules/hello# dmesg Hello, world Goodbye, linux device driver world
|
笔记
1.应用程序可以调用它并未定义的函数,因为链接过程能够解析外部引用从而使用适当的函数库。模块仅仅被连接到内核,它只能调用由内核导出的函数,不存在可链接的函数库。
2.模块运行在内核空间,应用程序运行在用户空间。
3.应用程序退出时可不管资源释放或者其他的清除工作,但模块的退出函数必须仔细撤销初始化函数所作的一切,否则,在系统重新引导之前某些东西就会残留在系统中。
4.内核代码可以通过访问current来获得当前进程,在中定义,是一个指向struct task_struct的指针。
printk(KERN_INFO "The process is \"%s\" (pid %i\n",current->comm, current->pid);
打印当前进程的进程ID和命令名。
5.内核API中具有两个下划线前缀(__)的函数名是接口的底层组件,谨慎调用。
6.内核代码不能实现浮点数运算。
7.insmod和modprobe都是装载模块到内核。区别在于,后者会考虑要装载的模块是否引用了一些当前内核不存在的符号。如果有这类引用,modprobe会在当前模块搜索路径中查找定义了这些符号的其他模块。如果找到了这些模块(即要装载的模块所依赖的模块),它会同时将这些模块载入内核。此时如果使用insmod,则该命令会失败,并在系统日志中出现“unresolved symbols”信息。
8.rmmod是从内核中移除模块,但如果内核认为该模块仍然在用,或者内核被配置为禁止移除模块,则无法移除模块。
9.lsmod通过读取/proc/modules虚拟文件来列出当前装载到内核中的所有模块。
10.公共内核符号表包含了所有全局内核项(函数和变量)的地址,这是实现模块化驱动程序必须的。当模块被装载到内核后,它所导出的任何符号都会变成内核符号表的一部分。通常情况下,模块只是实现自己的功能,无需导出任何符号,但如果其他模块要从某个模块获得好处,也可以导出符号。 Linux内核头文件提供了一个方便的方法用于管理符号对模块外部的可见性,减少了可能造成的名字空间污染,并适当隐藏信息。
如果一个模块需要向其他模块导出符号,使用下面的宏。
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
这2个宏都是将给定符号导出模块外部。_GPL版本使得导出的模块只能在GPL许可证下的模块能使用。符号必须在模块文件的全局部分导出,不能再函数中导出,因为上面的2个宏被扩展为一个特殊变量的声明,该变量必须是全局的。该变量在模块可执行文件的特殊部分(“ELF段”)中保持,装载时,内核通过这个段来寻找模块导出的变量。
11.所有的模块代码都必须包含下面2个头文件
#include
#include
前者包含有可装载模块需要的大量符号和函数的定义。后者的目的是制定初始化和清除函数。
12.模块应指定代码使用的许可证。
MODULE_LICENSE("GPL");
内核识别的许可证:"GPL","GPL v2","GPL and additional rights","Dual BSD/GPL","DUAL MPL/GPL"。
如果模块没有显示标记为上述内核可识别的许可证,则被假设为专有的,内核开发者不太愿意帮助因为装载专有模块而遇到问题的用户。
13.其他描述性定义
MODULE_AUTHOR(描述模块作者),
MODULE_DESCRIPTION(说明模块用途的简短描述),
MODULE_VERSION(代码修订号),
MODULE_ALIAS(模块别名),
MODULE_DEVICE_TABLE(告诉用户空间模块所支持的设备)。
14.初始化和关闭模块初始化函数负责注册模块所提供的任何设施。
static int __init initialization_function(void) { /*初始化代码*/ } module_init(initialization_function);
|
__init标记表示该函数只在初始化期间使用。在模块装载后,模块装载器就会将初始化函数扔到,这样可将该函数占用的内存释放。__init标记的使用是可选的。
module_init的使用是强制性的。这个宏在模块的目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。没有这个定义,初始化函数永远不会被调用。
15.清除函数在模块被移除前注销接口并向系统中返回所有资源。
static void __exit cleanup_function(void){ /*清除代码*/ } module_exit(cleanup_function);
|
__exit修饰词标记该代码仅用于模块卸载。如果模块被直接嵌入到内核中,或者内核配置不允许卸载模块,则被标记为__exit的函数被简单的丢弃。因此,被标记为__exit的函数只有在模块被卸载或者系统关闭时调用。
若一个模块未定义清除函数,则内核不允许卸载该模块。
16.初始化过程中的出错处理
一个简单的例子
int __init my_init_function(void) { int err;
/*使用指针和名称注册*/ erro = register_this(ptr1, "skull"); if (erro) goto fail_this; err = register_that(ptr2, "skull"); if (erro) goto fail_that; err = register_those(ptr3, "skull"); if (erro) goto fail_those;
return 0; /*成功*/
fail_those: unregister_that(ptr2, "skull"); fail_that: unregister_this(ptr1, "skull"); fail_this: return err; /*返回错误码*/ }
|
每当发生错误的时候从初始化函数中调用清除函数,这种方法将减少代码的重复并使代码更清晰,条理。当然清除函数必须在撤销没想设施的注册前检查它的状态。
简单的例子
struct something *item1; struct somethingelse *item2; int stuff_ok;
void my_cleanup(void) { if (item1) release_thing1(item1); if (item2) release_thing2(item2); if (stuff_ok) unregister_stuff(); return; } int __init my_init(void) { int err = -ENOMEM;
item1 = allocate_thing1(arguments1); item2 = allocate_thing2(arguments2); if (!item1 || item2) goto fail; err = register_stuff(item1, item2); if(!err) stuff_ok = 1; else goto fail; return 0;
fail: my_cleanup(); return err; }
|
清除函数并非退出代码调用,因此不能将清除函数标记为__exit。
17.模块参数
内核运行对驱动程序制定参数,这些参数在装载驱动程序时候改变。
在运行insmod或者modprobe命令装载模块时候给参数赋值,modprobe还可以从配置文件(/etc/modprobe.conf)中读取参数值。
在insmod改变模块参数之前,模块必须让这些参数对insmod命令可见,参数必须使用module_param()宏来声明,这个宏在moduleparam.h中定义。
module_param(name, type, perm);
name,变量名称; type,类型; perm,用于sysfs入口项的访问许可掩码。
声明数组参数使用下面的宏:
这个宏必须放到任何函数之外,通常在源文件头部。
module_param_arry(name, type, num, perm);
name,数组名称; type,数组元素类型; num,整形变量; perm,访问许可值。
例子(转自tekkman.cublog.cn)
#include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja"; static int howmany = 1; static int TNparam[] = {1,2,3,4}; static int TNparam_nr = 4; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO); module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void) { int i; for (i = 0; i < howmany; i++) printk(KERN_ALERT "(%d) Hello, %s !\n", i, whom); for (i = 0; i < 8; i++) printk(KERN_ALERT "TNparam[%d] : %d \n", i, TNparam[i]); return 0; }
static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n"); }
module_init(hello_init); module_exit(hello_exit);
|
阅读(1467) | 评论(0) | 转发(0) |