简单入门linux设备驱动之第三部分:向设备驱动程序传递参数

声明:内容搬自阿三哥网站,只是翻译了一下。侵删。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)?

·3 编程
·4 编译
·5 加载设备驱动程序
·6 使用 dmesg 命令验证参数
·7 卸载设备驱动程序

 

·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,你都必须往硬件寄存器写入一些东西。如果改变了变量但是没有人通知你你要怎么办呢?我想你应该明白了。如果你还没理解,请看下面的解释吧。

·3 编程

在这个例子里,我们解释了一切(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 = &notify_param, // Use our setter ...
        .get = &param_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号。

-------------BE SEXY AND BRAINY---------
原文地址:https://www.cnblogs.com/yuxiaolan/p/14401061.html