提示:
1)学会驱动模块传参,搞清楚基本传参知识,基本内容充电
2) 打打基础吧,上学那会不也是一个实验一个实验学习过来的嘛? 现在只是没有经历和时间罢了!
文章目录
需求-场景
驱动模块传参 实验
目标
掌握驱动传参实验,基础知识充电
困难点
驱动模块传参就固定的3个方法,但是不会用,为什么那样也不懂,基础不扎实,总是忘。 所以整理一下,熟悉了解,知所以然。
API 掌握
传参方法 | 使用场景 |
---|---|
module_param(name, type, perm) | 基本类型 |
module_param_array(name, type, nump, perm) | 数组 |
module_param_string(name, string, len, perm) | 字符串 |
module_param
- name: 模块参数的名称
- type: 模块参数的数据类型
- perm: 模块参数的访问权限
参数 type 可以取以下任意一种情况:
基本类型 | 类型说明 |
---|---|
bool | 布尔型 |
inbool | 布尔反值 |
charp | 字符指针(相当于 char *,不超过 1024 字节的字符串) |
short | 短整型 |
ushort | 无符号短整型 |
int | 整型 |
uint | 无符号整型 |
long | 长整型 |
ulong | 无符号长整型 |
知识点
基础充电-应用程序main函数传参
受限补充基础知识,我们在学习C语言时候入门第一个 main 函数
int main(int argc,char *argv[ ])
这个argc argv[] 到底是什么东西, 这个理解了才能更加方便我们理解传参实验
我们在学习的时候,很多时候就是在main 方法里面写各种逻辑和调用,没有用到参数argc 和 *argv 参数,实际当中 他就是一个man 函数入口,在编译之后 在命令行中就是用来传递参数用的。
参数的作用
- argc(argument count)
示命令行参数的数量(包括程序名本身),类型是整数(int)。
例如:运行 ./a.out hello 123,argc 的值是 3(程序名 + 2个参数)。
- argv(argument vector):
是一个字符串数组(char*[]),存储所有命令行参数的具体内容。
argv[0]:程序名(如 “./a.out”)。
argv[1] 到 argv[argc-1]:用户输入的参数。
argv[argc]:固定为 NULL(表示数组结尾)
实验
如下 一个最基础的.c 源码文件 paramtest_001.c
#include <stdio.h>
int main(int argc, char const *argv[]) {
// 打印程序名称和参数数量
printf("程序名称: %s\n", argv[0]);
printf("参数个数: %d\n", argc - 1);
// 检查是否有参数传入
if (argc < 2) {
printf("使用方法: %s [参数1] [参数2] ...\n", argv[0]);
printf("示例: %s hello world 123\n", argv[0]);
return 1; // 返回非零表示错误
}
// 打印所有传入的参数
printf("传入的参数:\n");
for (int i = 1; i < argc; i++) {
printf("%d: %s\n", i, argv[i]);
}
return 0; // 返回0表示成功
}
对它进行编译
gcc paramtest_001.c -o test.out
那么就生成了 parameters.out 执行文件,我们什么也不带参数,
直接执行 可执行文件 ./test.out 结果如下。
我们带带参数 测试实验:
./test.out Android 123
小结
我们上面的实验,是为了补充基础,main 函数的带参数相关的参数说明,在C语言体系下的main 函数的一些基本知识。 那么在Linux 驱动层面,驱动其实也就是一个程序,系统提供了传参方法,如上三种方法。传参形式其实和main 基本一致的,这也是为什么需要 借助main 函数,稍微理解一下传参,方便理解。
基础
具体实验
驱动传参 基本数据类型传递 module_param
测试代码
#include <linux/init.h>
#include <linux/module.h>
static int debug_level = 1;
static char *device_name = "mydevice";
static int max_count = 10;
static bool enable_feature = true;
module_param(debug_level, int, 0644);
module_param(device_name, charp, 0644);
module_param(max_count, int, 0644);
module_param(enable_feature, bool, 0644);
MODULE_PARM_DESC(debug_level, "Debug level (0-3)");
MODULE_PARM_DESC(device_name, "Device name string");
MODULE_PARM_DESC(max_count, "Maximum device count");
MODULE_PARM_DESC(enable_feature, "Enable special feature");
static int __init mymodule_init(void)
{
printk(KERN_INFO "Module loaded with params:\n");
printk(KERN_INFO " debug_level=%d\n", debug_level);
printk(KERN_INFO " device_name=%s\n", device_name);
printk(KERN_INFO " max_count=%d\n", max_count);
printk(KERN_INFO " enable_feature=%d\n", enable_feature);
return 0;
}
static void __exit mymodule_exit(void)
{
printk(KERN_INFO "Module unloaded\n");
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
配置Makefile文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += param_test_01_.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
- 进行编译 进行编译,在同目录下 make 生成 对应的 .ko 文件
- 对 生成的驱动文件 .ko push 到开发版中,准备加载驱动进行传参实验,如下make 指令,生成.ko 文件
insmod 加载驱动实验
如上面的源码c 文件,配置好makefile 文件,进行编译,生成对应的.ko 文件。 比如 param_test_01_.ko 文件。
那么 在 实际加载驱动 执行命令 ,并实际内核打印数据
-rwxrwxrwx 1 root root 83432 Apr 3 2025 param_test_01_.ko
[root@topeet:/mnt/sdcard]# rm -rf param_test_01_.ko
[root@topeet:/mnt/sdcard]# insmod param_test_01_.ko max_count=100
[root@topeet:/mnt/sdcard]# insmod param_test_01_.ko debug_level=2 device_name=newname max_count=20 enable_feature=0
insmod: can't insert 'param_test_01_.ko': File exists
[root@topeet:/mnt/sdcard]# rmmod param_test_01_.ko
[root@topeet:/mnt/sdcard]# insmod param_test_01_.ko debug_level=2 device_name=newname max_count=20 enable_feature=0
[root@topeet:/mnt/sdcard]# rmmod param_test_01_.ko
[root@topeet:/mnt/sdcard]# insmod param_test_01_.ko debug_level=2 device_name=wangfangchen max_count=100 enable_feature=true
insmod: can't insert 'param_test_01_.ko': Invalid argument
[root@topeet:/mnt/sdcard]# insmod param_test_01_.ko debug_level=2 device_name=wangfangchen max_count=100 enable_feature=1
[root@topeet:/mnt/sdcard]#
[ 1874.846610] Module loaded with params:
[ 1874.846697] debug_level=1
[ 1874.846707] device_name=mydevice
[ 1874.846714] max_count=100
[ 1874.846721] enable_feature=1
[ 1918.763564] Module unloaded
[ 1921.075181] Module loaded with params:
[ 1921.075272] debug_level=2
[ 1921.075283] device_name=newname
[ 1921.075290] max_count=20
[ 1921.075297] enable_feature=0
[ 2235.599363] Module unloaded
[ 2255.078734] param_test_01_: `true' invalid for parameter `enable_feature'
[ 2255.118823] param_test_01_: `true' invalid for parameter `enable_feature'
[ 2260.383783] Module loaded with params:
[ 2260.383926] debug_level=2
[ 2260.383951] device_name=wangfangchen
[ 2260.383973] max_count=100
[ 2260.383991] enable_feature=1
[root@topeet:/]#
实验总结和坑点规整
基于基本的理论知识进行实验,还是有几点需要注意的地方,规整如下:
- 驱动传参,先声明方法,如上是基本数据类型,声明出来 module_param,并给类型和权限【一般就写0644,对于新手而言】
- 传参引入的头文件是 <linux/module.h> ,不要写成了 <linux/moduleparam.h>,或者同时存在时候规避一下,不然引用头文件加载同一个方法导致传参失败
- boolean 类型传参,实际传递参数是0 和1,如果用true 和 false 就会失败的,系统不认识。如上实验打印信息
- 定义传参的变量,定义的什么变量那么就在实际传参时候 传递什么变量,不然会找不到、不认识参数
驱动传参 数组类型传递 module_param_array
测试代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#define MAX_ARR_SIZE 5
static int my_array[MAX_ARR_SIZE] = {1, 2, 3, 4, 5}; // 定义数组参数和元素计数变量
static int arr_size = MAX_ARR_SIZE;
// 注册数组参数
module_param_array(my_array, int, &arr_size, 0644);
MODULE_PARM_DESC(my_array, "An integer array parameter (max 5 elements)");
static int __init array_param_init(void)
{
int i;
printk(KERN_INFO "Array Parameter Module Loaded\n");
printk(KERN_INFO "Array contains %d elements:\n", arr_size);
for (i = 0; i < arr_size; i++) {
printk(KERN_INFO " my_array[%d] = %d\n", i, my_array[i]);
}
return 0;
}
static void __exit array_param_exit(void)
{
printk(KERN_INFO "Array Parameter Module Unloaded\n");
}
module_init(array_param_init);
module_exit(array_param_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Demonstration of module_param_array usage");
配置Makefile文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += param_array.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
insmod 加载驱动实验
如上在基本类型传参中已经说明了一些注意点和操作步骤,在此不再赘述。 测试实验如下:
[root@topeet:/mnt/sdcard]# insmod param_array.ko my_array=1,8,9,10
[root@topeet:/mnt/sdcard]# dmesg | tail -10
[ 70.877300] device_name=mydevice
[ 70.877311] max_count=10
[ 70.877321] enable_feature=1
[ 200.886989] Module unloaded
[ 264.149899] Array Parameter Module Loaded
[ 264.150008] Array contains 4 elements:
[ 264.150017] my_array[0] = 1
[ 264.150024] my_array[1] = 8
[ 264.150032] my_array[2] = 9
[ 264.150038] my_array[3] = 10
[root@topeet:/mnt/sdcard]# rmmod param_array.ko
[root@topeet:/mnt/sdcard]#
[root@topeet:/mnt/sdcard]#
[root@topeet:/mnt/sdcard]#
[root@topeet:/mnt/sdcard]# insmod param_array.ko my_array=1,8,9,100,1000,100000
insmod: can't insert 'param_array.ko': Invalid argument
[root@topeet:/mnt/sdcard]# insmod param_array.ko my_array=1,100,1000,100000,0
[root@topeet:/mnt/sdcard]# dmesg | tail -10
[ 317.998094] param_array: `1' invalid for parameter `my_array'
[ 318.039803] my_array: can only take 5 arguments
[ 318.039904] param_array: `1' invalid for parameter `my_array'
[ 331.826700] Array Parameter Module Loaded
[ 331.826804] Array contains 5 elements:
[ 331.826812] my_array[0] = 1
[ 331.826819] my_array[1] = 100
[ 331.826827] my_array[2] = 1000
[ 331.826833] my_array[3] = 100000
[ 331.826840] my_array[4] = 0
[root@topeet:/mnt/sdcard]#
拓展 - module_param_array 方法的数组大小参数使用指针类型
作为初级开发者,这个大小为什么用指针类型,实际实验 发现用 int 类型,居然直接编译警告,报错。 下面列举原因:
允许动态调整数组大小
- 当通过命令行传递数组参数时,实际传入的元素数量可能 不确定(例如 param_array=1,2,3 有 3 个元素,而
param_array=5 只有 1 个元素)。 - 使用指针类型可以让内核在解析参数时 动态修改 数组大小的值,反映实际传入的元素数量。如果直接用 int
类型,则无法在函数内部修改外部的值。
static int my_array[10];
static int my_array_size; // 需要指针传递以动态更新
module_param_array(my_array, int, &my_array_size, 0644);
当用户传入 my_array=1,2,3 时,内核会将 my_array_size 自动更新为 3。
当然,如果已经指定了大小,传入具体值的地址,那么范围就必须小于这个值了。 如上面实验定义了最大值为5,最后取变量等会5 的地址来表示大小
#define MAX_ARR_SIZE 5
。。。。。。
static int arr_size = MAX_ARR_SIZE;
// 注册数组参数
module_param_array(my_array, int, &arr_size, 0644);
与内核参数解析机制兼容
- 内核的参数解析函数(如 module_param_array 的实现)需要将解析后的数组大小 写回 调用者的变量中。这是典型的 C
语言“通过指针传递输出参数”模式。 - 如果直接传递 int,函数内部无法修改外部的值(C 语言的传值限制)。
驱动传参 字符串类型传递 module_param_string
module_param_string(name, string, len, perm) 参数说明
参数说明如下:
- name:参数名称(用户空间可见)
- string:内核模块中存储字符串的字符数组
- len:字符串缓冲区的长度(包括终止符 \0)
- perm:在 sysfs 中的访问权限(如 0644)
测试代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#define MAX_STR_LEN 32
static char my_string[MAX_STR_LEN] = "default";
module_param_string(custom_str, my_string, MAX_STR_LEN, 0644);
MODULE_PARM_DESC(custom_str, "A custom string parameter");
static int __init string_param_init(void)
{
printk(KERN_INFO "String parameter module loaded\n");
printk(KERN_INFO "Got string: %s\n", my_string);
return 0;
}
static void __exit string_param_exit(void)
{
printk(KERN_INFO "String parameter module unloaded\n");
}
module_init(string_param_init);
module_exit(string_param_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Module parameter string example");
配置Makefile文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += param_string.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
insmod 加载驱动实验
如上在基本类型和数组类型传参中已经说明了一些注意点和操作步骤,在此不再赘述。 测试实验如下:
[root@topeet:/mnt/sdcard]# insmod param_string.ko custom_str="HelloKernel"
[root@topeet:/mnt/sdcard]# dmesg | tail -n 5
[ 3312.338558] String parameter module loaded
[ 3312.338569] Got string: Hello
[ 3328.222961] String parameter module unloaded
[ 3338.792898] String parameter module loaded
[ 3338.792996] Got string: HelloKernel
[root@topeet:/mnt/sdcard]#
实验分析总结
module_param_string(custom_str, my_string, MAX_STR_LEN, 0644);
- MAX_STR_LEN 字符串长度大小参数是int 类型,固定大小,和上面的数组类型 参数范围大小都是有范围约束的,但是一个是实际大小值 作为缓冲区,数组参数传递是指针。
- 传递字符串value 值,中间不能用空格和标点符号,这个暂未仔细分析原因、机制
- 传递的字符串,实际上char [] 数组类型来传递、接收的。
驱动传参 module_param_string 与 module_param(charp) 的区别详解
实际实验如上,自己在想,既然字符传递通过普通类型传递方法来传递 与 字符串传递效果一样,那两者之间有什么区别?
基本定义对比
module_param(…, charp, …)
char *device_name = "default";
module_param(device_name, charp, 0644);
- 类型:字符指针 (char *)
- 存储方式:直接使用指针指向字符串
- 内存管理:需要手动管理内存
module_param_string
char device_name[32] = "default";
module_param_string(name, device_name, sizeof(device_name), 0644);
- 类型:字符数组
- 存储方式:固定大小的缓冲区
- 内存管理:自动管理,无需担心内存释放
关键区别
特性 | 存储方式 | 内存分配 | 最大长度限制 | 安全性 | 使用便捷性 |
---|---|---|---|---|---|
module_param(…, charp, …) | 动态指针 | 需要手动分配/释放 | 无明确限制(依赖分配的内存) | 较低(可能缓冲区溢出) | 较复杂(需管理内存) |
module_param_string | 固定大小的字符数组 | 预分配固定大小 | 有明确的长度限制 | 较高(有长度检查) | 较简单 |
总结
- 基本基础充电
- 三种传参实验