编写Linux设备驱动前应该知道哪些
helloworld示例驱动代码看完之后,是不是还抱着些疑问?下面让我娓娓道来~
1. 头文件
#include <linux/module.h>//module.h包含了大量加载模块的函数
#include <linux/init.h>//init.h包含了初始化和清理函数
#include <linux/version.h>//version.h包含在简历的内核版本信息
这三个头文件是写驱动时所必备的头文件,就类似C语言中的<stdio.h>这个头文件啦。不过值得注意的是,可不要在驱动的头文件里出现C的标准库哦。
2. 模块声明与描述
在hello world里有见过这样一个函数。
MODULE_LICENSE("GPL");//内核人事的特定许可
//GPL===>适用GNU通用公共许可的任何版本
//GPL v2===>只适用GPL的版本2
除了它以外还有下列模块函数等,他们主要是为了描述当前这个驱动是叫什么,干什么用的,怎么用等信息的。都是为了方便程序员去了解这些信息用的。
MODULE_AUTHOR("");//声明是谁编写的该模块
MODULE_DESCRIPION("");//该模块是干嘛的描述
MODULE_VERSION("");//模块版本
MODULE_ALIAS("");//模块的另一个为人所知的名称,即别称
MODULE_DEVICE_TABLE();//模块支持哪些设备===》?????
不过上述信息如何在终端打印出来呢?
使用lsmod,还是有下面这个命令就可以啦
modinfo audio -a
Usage: modinfo [-adlp0] [-F keyword] MODULE -a Shortcut for '-F author' -d Shortcut for '-F description' -l Shortcut for '-F license' -p Shortcut for '-F parm' -F keyword Keyword to look for -0 Separate output with NULs
3. 不同于应用层的printk打印
#include <linux/kernel.h>
int printk(const char * fmt, ...);
功能与用户空间的printf类似。但printk不支持浮点数。
4. 属于驱动的命令行传参
#include <linux/moduleparam.h>
module_param(variable, type, perm);
//宏定义, 创建模块参数, 可以被用户在模块加载时调整( 或者在启动时间, 对于内嵌代码). 类型可以是 bool, charp, int, invbool, short, ushort, uint, ulong,或者 intarray.
驱动的命令行传参,多用与挂载驱动时,加上相应的参数,实现不同的功能
insmod audio.ko gpio=10
#传入参数gpio,值为10
5. 驱动出入口
moudle_init()
使用moudle_init
是强制的. 这个宏定义增加了特别的段到模块目标代码中, 表明在哪里找到模块的初始化函数. 没有这个定义, 你的初始化函数不会被调用.
module_exit()
如果你的模块没有定义一个清理函数, 内核不会允许它被卸载.
一个标识 __exit
的函数只在模块卸载或者系统停止时调用; 任何别的使用是错的. 其次, moudle_exit 声明对于使得内核能够找到你的清理函数是必要的.
6.编写驱动最应该知道的一些原则
-
驱动的出口和入口在编写时,需注意,应当编写成静态函数,虽内核没有强制要求,但是其他地方不会用到(《Linux设备驱动程序》p23)
-
在驱动入口的初始化函数里,由于内核超小的栈决定了,几乎必定会在驱动进行动态内存分配等。在注册驱动是一定有可能会注册失败的,所以在模块代码中必须时刻保证检查返回值,并且要求的操作已经成功。
-
由于是驱动,底层的,无论如何,也要尽力保证哪怕某一步的操作失败都有可替代选项或降级选项,来保证驱动可以正常的运行下去;实在是发生重大错误或失败时也要具备料理后事的能力。
-
这个料理后事的能力是指在一个特别类型的失败后完全不能加载, 你必须取消任何在失败前注册
的动作. 内核不保留已经注册的设施的每模块注册, 因此如果初始化在某个点失败, 模块必须能自己退回所有东西。如果不具备这个能力,内核极大可能的会运行在一个不稳定状态,不知道在什么时候就会崩溃。 -
料理的方法。虽然在正常编程中极不推荐goto方法,因为它会使代码逻辑变得混乱,难以理解,根据《软件开发的201个原则–第92-程序首先是写给人看的》所说代码应该是易于理解的
7.驱动一直粘着内核不放–驱动依赖
版本依赖
驱动是紧密结合到一个特殊的内核版本的数据结构和函数原型上的;一个模块所看见的内核接口可能因内核版本的不同会有很大的差异性。所以你编写的模块驱动代码要针对每一个内核驱动版本都应该重新编译。
当然并不是说内核是就认在他这个版本上所建立的驱动,其他的驱动就不理睬,更重要的原因是,驱动是高度适配特定的内核的;在适配的第一步对是一个当前内核树中的文件(称为 vermagic.o
)连接你的模块; 这个东东含有相当多的有关要为其建立模块的内核的信息, 包括目标内核版本, 编译器版本, 以及许多重要配置变量的设置.
当尝试加载一个模块, 这些信息被检查与运行内核的兼容性. 如果不匹配, 模块不会加载; 代之的是你见到如下内容:
$: insmod hello.ko
$: Error inserting './hello.ko': -1 Invalid module format
#/var/log/message
跨版本发行多版本 p19
当然,因驱动高度依赖内核源码,想要对驱动进行开发,那必须在你的开发文件夹里有系统内核源码,并且在编译驱动时,就已经确保内核已经编译完成。
平台依赖
略、暂时没看懂,后续再研究这个平台依赖
8.模块加载竞争
内核的某些别的部分会在注册完成之后马上开始使用任何你刚才所注册的设施。换句话说,当你还在初始化时,系统内核有些地方就可能已经调用到你的模块里,所以你的代码必须准备好被调用,一旦它完成了它的第一个注册. 不要注册任何设施,直到所有的需要支持那个设施的你的内部初始化已经完成。
你也必须考虑到如果你的初始化函数决定失败会发生什么, 但是内核的一部分已经在使用你的模块已注册的设施. 如果这种情况对你的模块是可能的, 你应当认真考虑根本不要使初始化失败. 毕竟, 模块已清楚地成功输出一些有用的东西. 如果初始化必须失败, 必须小心地处理任何可能的在内核别处发生的操作, 直到这些操作已完成.