Linux驱动 helloWorld
根据下面的helloWorld.c驱动来了解总结内核中的printk,__init,__exit
helloWorld.c
#include <linux/init.h>
#include <linux/module.h>
static char* name = "the man like wind ";
module_param(name, charp, S_IRUGO);
static int age = 18;
module_param(age, int, 0644);
static int __init helloWorld_init(void){
int ret = 0;
printk(KERN_INFO "------------------>see you again , world :-)\n");
printk(KERN_INFO "- name : %s\n",name);
printk(KERN_INFO "- age : %d\n",age);
return ret;
}
static void __exit helloWorld_exit(void){
printk(KERN_INFO"------------------>bye %s , good world :(\n",name);
}
int add_integer(int a,int b){
return a+b;
}
EXPORT_SYMBOL(add_integer);
int sub_integer(int a,int b){
return a-b;
}
EXPORT_SYMBOL(sub_integer);
module_init(helloWorld_init);
module_exit(helloWorld_exit);
MODULE_AUTHOR("Qian <XXXXXXXX@qq.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("V9.9");
MODULE_ALIAS("a simple helloWorld driver");
1.__exit 和 __init
__init,__initdata,__exit,__exitdata的定义全部在<Linux/init.h>,这些宏定义的作用就是告诉编译器把这些函数或者数据放在相应的selection中,其中__init修饰函数。__initdata修饰变量。
静态加载(直接编入内核)
__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段,当模块直接编进内核时,在linux启动到最后可以看到类似下面的提示,静态加载时__exit将没有作用也不会编入内核,因为内置了就不能卸载
PHY: 0:01 - Link is Up - 100/Full
VFS: Mounted root (nfs filesystem) on device 0:14.
devtmpfs: mounted
Freeing init memory: 196K
INIT: version 2.86 booting
动态加载(insmod modprobe)
__init进行初始化工作,在模块装载之后,模块装载就会将初始化函数扔掉,这样可以将该函数占用的内存释放出来。__exit进行清理工作
2.printk打印函数
日志级别一共有8个级别,printk的日志级别定义如下(在include/Linux/kernel.h中):
#define KERN_EMERG 0/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
#define KERN_ALERT 1/*报告消息,表示必须立即采取措施*/
#define KERN_CRIT 2/*临界条件,通常涉及严重的硬件或软件操作失败*/
#define KERN_ERR 3/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
#define KERN_WARNING 4/*警告条件,对可能出现问题的情况进行警告*/
#define KERN_NOTICE 5/*正常但又重要的条件,用于提醒*/
#define KERN_INFO 6/*提示信息,如驱动程序启动时,打印硬件信息*/
#define KERN_DEBUG 7/*调试级别的消息*/
查看修改打印级别
#查看Ubuntu打印级别
study@ubuntu:~$ cat /proc/sys/kernel/printk
4 4 1 7
#####################################################################
#打印出来4个数字的意义
#控制台日志级别:优先级高于该值的消息将被打印至控制台
#默认的消息日志级别:将用该优先级来打印没有优先级的消息
#最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)
#默认的控制台日志级别:控制台日志级别的缺省值
#####################################################################
#发现在Ubuntu下无法修改打印级别,本着好学的态度,在一块开发板上,这样当我们在驱动中为了调试,应该把打印级别调到最低就是7
~ # cat /proc/sys/kernel/printk
7 4 1 7
~ # echo 6 4 1 7 > /proc/sys/kernel/printk
~ # cat /proc/sys/kernel/printk
6 4 1 7
3.几个驱动使用的命令
insmod #加载驱动,会执行module_init()修饰的函数
rmmod #卸载驱动,执行module_exit()修饰的函数
lsmod #列出目前加载的驱动 等同于cat /proc/modules
tree /sys/module/ #insmod后会在/sys/moudle生成驱动名的一个目录
modprobe #在加载时会同时加载依赖的模块 依赖关系在/lib/modules/<kerel_version>/modules.dep,格式很简单,下面是copy过来的一个
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko
modinfo #查看模块信息
4驱动程序结构
- 模块加载函数 ,insmod时会被内核执行,完成初始化工作
- 模块卸载函数 ,rmmod时会被内核执行,完成清理工作
- 模块许可声明 ,MODULE_LICENSE(“GPL v2”),不声明会在加载时提示污染内核
- 模块参数(可选),外界可以传给驱动变量的一个数值
- 导出符号(可选),可以导出一个函数给其他驱动使用
- 作者别名等其他描述信息(可选) , 使用MODULE_xx()声明
4.1模块参数
在/sys/module/驱动名字/parameters所以该驱动导出模块参数的文件,可以cat查看参数值
#可以传入的参数类型有byte short ushort int uint long ulong charp boo等
static char* name = "the man like wind ";
module_param(name, charp, S_IRUGO);
static int age = 18;
module_param(age, int, 0644);
#这样在使用时可以insmod *.ko name="leo" age=23
#如果是静态加载进入内核可以在uboot的bootargs里设置“模块名.参数名=值”
4.2导出符号
使用下面宏导出符号到内核符号表中
EXPORT_SYMBOL()
EXPORT_SYMBOL_GPL()
#可以通过/proc/kallsyms查找导出符号相关信息
int add_integer(int a,int b){
return a+b;
}
EXPORT_SYMBOL(add_integer);
int sub_integer(int a,int b){
return a-b;
}
EXPORT_SYMBOL(sub_integer);