声明:内容搬自阿三哥网站,只是翻译了一下。侵删。https://embetronicx.com/tutorials/linux/device-drivers/
正文如下:
这是“linux设备驱动系列”的教程。本系列的目的是提供简单实用的示例,使每个人都能以简单的方式理解这些概念。现在让我们即将学习“linux设备驱动第三部分:向设备驱动程序传递参数”。
内容速览
·1 linux设备驱动第三部分:向设备驱动程序传递参数
·2 模块参数宏
·2.1 module_param()
·2.2 module_param_array()
·2.3 module_param_cb()
·2.3.1 我们什么时候需要通知(notification)?
·1 linux设备驱动第三部分:向设备驱动程序传递参数
在同一个程序中,我们可以向任何函数传递参数。但是有没有可能向任意程序传递参数呢?我觉得可能有。对吗?是的,我们可以。在C语言的编程中,我们可以向程序传递参数。为了实现这个功能,我们需要在main()函数的定义中添加 argc 和 argv ;我想大家应该都知道这个。现在回到我们的话题,我们能够向设备驱动程序传递参数吗?好的,在这一小节的教程中,我们会关注这个话题。
·2 模块参数宏
·module_param()
·module_param_array()
·module_param_cb()
在讨论这些宏之前,我们必须要先了解变量的权限。
权限有以下几种类型:
·S_IWUSR
·S_IRUSR
·S_IXUSR
·S_IRGRP
·S_IWGRP
·S_IXGRP
其中的 S_I 是公共的头(header)。
R = 读(read),W = 写(write),X = 执行(execute)。
USR = 用户(user),GRP = 组(group)
使用或运算符“|”我们可以一次设置多个权限。
·2.1 module_param()
这个宏用来初始化变量。module_param()需要三个参数:变量名、变量的类型、变量在sysfs入口的权限。这个宏应该放在函数的外部;通常是放在源文件的头部附近。module_param()宏定义在linux/moduleparam.h中。
module_param(name, type, perm);【perm-->permission】
module_param()宏会在/sys/module目录下创建一个下级目录。举个栗子:
module_param(valueETX, int, S_IWUSR|S_IRUSR);
它会创建一个sysfs入口。(/sys/module/hello_world_module/parameters/valueETX)
模块参数支持很多种参数类型:
·bool 布尔变量(true/false)。
·invbool invbool类型取布尔类型的相反值,比如true值会变成false值,false值会变成true值。
·charp 字符指针(char pointer)变量。系统会给用户提供的字符串分配内存,指针也被相应的赋值。
·int
·long
·short
·uint
·ulong
·ushort 不同长度的基本整数类型。u开头的变量表示为无符号值。
·2.2 module_param_array()
这个宏可以使用数组来传递参数。模块加载器支持数组参数;参数的值之间由逗号分隔。声明一个数组:
module_param_array(name, type, num, perm);
其中:name--数组的名字,
type--数组元素的类型,
num--一个整数变量值(可选),不配置时使用NULL,
perm--权限配置。
·2.3 module_param_cb()
这个宏用来注册参数被改变时的回调函数(callback)。我猜你可能不太能理解。让我来解释一番。
举个栗子,
我用module_param()函数创建了一个参数。
module_param(valueETX, int, S_IWUSR|S_IRUSR);
你可以在命令行改变valueETX的值,如下:
>>sudo su >>echo 1 > /sys/module/hello_world_module/parameters/valueETX
它会更新变量valueETX的值。但是没有办法通知你的模块:valueETX的值已经改变了。
使用module_param_cb(),我们就能收到通知。
如果你想你的变量发生改变时你能收到通知,我们就需要注册我们的处理函数到文件操作结构体中。
struct kernel_param_ops { int (*set)(const char *val, const struct kernel_param *kp); int (*get)(char *buffer, const struct kernel_param *kp); void (*free)(void *arg); };
更详细的解释请参考之后的程序。
·2.3.1 我们什么时候需要通知(notification)?
我来告诉你实际应用场景。无论什么时候变量被置为1,你都必须往硬件寄存器写入一些东西。如果改变了变量但是没有人通知你你要怎么办呢?我想你应该明白了。如果你还没理解,请看下面的解释吧。
在这个例子里,我们解释了一切(module_param, module_param_array, module_param_cb)。
对于module_param(),我创建了两个变量。一个是整数(valueETX),另一个是字符串(namaETX)。
对于module_param_array(),我创建了一个整数数组变量(arr_valueETX)。
对于module_param_cb(),我创建了一个整数变量(cb_valueETX)。
你可以使用sysfs入口改变所有变量的值,入口位于/sys/module/hello_world_module/parameters/目录下。
但是你改变时不会受到任何通知,除了你使用module_param_cb()宏创建的变量。
从github上获取源代码
/***************************************************************************//** * file hello_world.c * * details Simple hello world driver * * author EmbeTronicX * * *******************************************************************************/ #include<linux/kernel.h> #include<linux/init.h> #include<linux/module.h> #include<linux/moduleparam.h> int valueETX, arr_valueETX[4]; char *nameETX; int cb_valueETX = 0; module_param(valueETX, int, S_IRUSR|S_IWUSR); //integer value module_param(nameETX, charp, S_IRUSR|S_IWUSR); //String module_param_array(arr_valueETX, int, NULL, S_IRUSR|S_IWUSR); //Array of integers /*----------------------Module_param_cb()--------------------------------*/ int notify_param(const char *val, const struct kernel_param *kp) { int res = param_set_int(val, kp); // Use helper for write variable if(res==0) { printk(KERN_INFO "Call back function called... "); printk(KERN_INFO "New value of cb_valueETX = %d ", cb_valueETX); return 0; } return -1; } const struct kernel_param_ops my_param_ops = { .set = ¬ify_param, // Use our setter ... .get = ¶m_get_int, // .. and standard getter }; module_param_cb(cb_valueETX, &my_param_ops, &cb_valueETX, S_IRUGO|S_IWUSR ); /*-------------------------------------------------------------------------*/ /* ** Module init function */ static int __init hello_world_init(void) { int i; printk(KERN_INFO "ValueETX = %d ", valueETX); printk(KERN_INFO "cb_valueETX = %d ", cb_valueETX); printk(KERN_INFO "NameETX = %s ", nameETX); for (i = 0; i < (sizeof arr_valueETX / sizeof (int)); i++) { printk(KERN_INFO "Arr_value[%d] = %d ", i, arr_valueETX[i]); } printk(KERN_INFO "Kernel Module Inserted Successfully... "); return 0; } /* ** Module Exit function */ static void __exit hello_world_exit(void) { printk(KERN_INFO "Kernel Module Removed Successfully... "); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>"); MODULE_DESCRIPTION("A simple hello world driver"); MODULE_VERSION("1.0");
·4 编译
Makefile代码如下
obj-m += hello_world_module.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
在终端输入 sudo make 。
·5 加载设备驱动程序
sudo insmod hello_world_module.ko valueETX=14 nameETX="EmbeTronicX" arr_valueETX=100,102,104,106
·6 使用 dmesg 命令验证参数
现在我们的模块已经加载好了。使用 dmesg 命令查看。下面的图片中,每个值都被传递到了我们的设备驱动程序中。
现在我们来检验一下module_param_cb()是否被调用。为此,我要在sysfs中改变变量的值。你有两种方法来写这个变量。
1. sudo sh -c "echo 13 > /sys/module/driver/parameters/cb_valueETX"
2. 输入 sudo su 。输入密码如果需要的话。然后输入 echo 13 > /sus/module/hello_world_module/parameters/cb_valueETX
现在使用 demsg 命令检验。
结果如上。我们的回调函数已经被调用了。但是你改变其他变量的值,你却不会收到通知。
·7 卸载设备驱动程序
最后使用命令 sudo rmmod hello_world_module 卸载驱动程序。
---------------------分割线-------------------
我希望大家都已经理解了。如果你有任何疑问,请在下面评论。在下一小节中,我们会了解linux设备驱动程序中的major号和minor号。