在嵌入式linux应用开发中,可以给main()函数传递参数,这样应用程序就能知道最初的控制参数是什么,当然也可以选择不向应用程序传递参数。也可以在执行 Shell 脚本时,向脚本传递参数。在驱动开发中,会使用到insmod命令来加载一个驱动模块,这时候我们也可以使用insmod命令向驱动模块传递参数。
在 Linux 应用程序中,main 函数作为应用程序的入口函数,main 函数的形参一般会有两种写法,如果执行应用程序无需传参,则可以写成如下形式:
int main(void)
{
/* 代码 */
}
如果在执行应用程序的时候需要向应用程序传递参数,则写法如下:
int main(int argc, char **argv)
{
/* 代码 */
}
argc 形参表示传入参数的个数,包括应用程序自身路径和程序名,譬如运行当前目录下的 hello 可执行文件,并且传入参数,如下所示:
./hello 123
那么此时参数个数为 2,并且这些参数都是作为字符串的形式传递给 main 函数:
argv[0]等于"./hello"
argv[1]等于"123"
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
/* 向文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0)
{
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0)
{
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
编译后,可以通过以下命令来运行该程序
./test /dev/led 1 //打开 LED 灯
我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
以下实例我们向脚本传递三个参数,并分别输出,其中 $0 为执行的文件名:
#!/bin/bash
echo "Shell 传递参数!";
echo "文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";
Shell 脚本还有几个特殊字符用来处理参数
$# 传递到脚本的参数个数
$* 以一个单字符串显示所有向脚本传递的参数。
如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。
如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
使用示例:
#!/bin/bash
echo "Shell 传递参数实例!";
echo "第一个参数为:$1";
echo "参数个数为:$#";
echo "传递的参数作为一个字符串显示:$*";
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
#!/bin/bash
echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done
echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done
参数传递分为两种
内置模块参数传递:即将模块编译构建进内核镜像。
外置模块参数传递:使用insmod等命令装在的内核模块。
对于内置模块参数传递,一般在bootloader中可向内置的模块传递参数,例如可以在bootargs中设置模块名.参数名=值的形式给该内置的模块传递参数;对于外置模块,在装载内核模块时,我们可以向模块传递参数,形式为:
insmode(或 modprobe)模块名 参数名=参数值
如果不传递参数,参数将使用模块内定义的缺省值。
向驱动模块传递参数,必须事先在驱动源码中声明某一个变量可作为模块参数传递,并且指定变量的权限,常见的权限参数如下:
上述宏定义中,S_I是公共的写法,R = read,W = write ,X = execute , USR = user,GPR = group。
当然,也可以这样看:可以将数字最后三位转化为二进制:xxx xxx xxx,高位往低位依次看,第一位为 1 表示文件所有者可读,第二位为 1 表示文件所有者可写,第三位为 1 表示文件所有者可执行;接下来三位表示文件所有者同组成员的权限;再下来三位为不同组用户权限。
使用'|'(或操作),可以一次设置多个权限。
传递普通的参数(例如 bool、char、int),使用下列宏定义:
module_param(name,type,perm);
name:要传递进去参数的名称。
type:要传递进去参数的类型。
perm:要传递进去参数的读写权限。
module_param()宏在/sys/module下创建子目录。上述代码,将在/sys/module/中可查看对应的参数:
传递数组参数使用下列的宏定义:
module_param_array(name,type,nump,perm)
name:要传递进去参数的名称。
type:要传递进去参数的类型。
nump:实际传入进去参数的个数。
perm:要传递进去参数的读写权限。
可以向/sys/module/下的参数名称写入参数,这意味将改变参数的值,如果使用module_param()和module_param_array(),则无法获知参数的值是否改变,如果我们想要让系统获知参数的改变,并根据参数改变进一步执行处理,则需要使用module_param_cb()注册参数值改变时的处理函数。在内核中,module_param_cb()宏用于注册回调,每当参数(形参)发生变化时,将调用此回调函数:
module_param_cb(name, ops, arg, perm)
name:要传递进去参数的名称。
ops:该参数的set和get操作。
arg:传递给ops中回到函数的参数。
perm:要传递进去参数的读写权限。
编写内核代码如下:
#include
#include
#include
#include
int value, arr_data[4];
char *name;
int callback_date = 0;
//声明模块参数
module_param(value, int, S_IRUSR|S_IWUSR); //integer 类型
module_param(name, charp, S_IRUSR|S_IWUSR); //String 类型
module_param_array(arr_data, int, NULL, S_IRUSR|S_IWUSR); //整型数组
/*----------------------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...\n");
printk(KERN_INFO "New value of callback_date = %d\n", callback_date);
return 0;
}
return -1;
}
const struct kernel_param_ops my_param_ops =
{
.set = notify_param,
.get = param_get_int,
};
module_param_cb(callback_date, &my_param_ops, &callback_date, S_IRUGO|S_IWUSR|S_IWGRP );
static int __init test_init(void)
{
int i;
//打印出模块参数
printk(KERN_INFO "Value = %d \n", value);
printk(KERN_INFO "callback_date = %d \n", callback_date);
printk(KERN_INFO "Name = %s \n", name);
for (i = 0; i < (sizeof arr_data / sizeof (int)); i++)
{
printk(KERN_INFO "arr_data[%d] = %d\n", i, arr_data[i]);
}
printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
return 0;
}
static void __exit test_exit(void)
{
printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
测试代码运行在ubuntu的虚拟机里面,参照https://blog.csdn.net/xxxx123041/article/details/131560875编写相对应的makefile文件如下:
KERNELDIR :=/usr/src/linux-headers-5.15.0-79-generic
CURRENT_PATH := $(shell pwd)
obj-m := mytest.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
执行make命令编译,编译结果如下图所示:
使用insmod加载内核驱动模块并传递参数
使用dmesg命令查看内核的打印,可以看到参数已经传递了进去。
在加载内核驱动前的/sys/module目录下并没有mytest驱动模块,在加载驱动之后sys/module目录下出现了mytest驱动模块
向该模块下的parametes目录下的callback_data参数传递数据,如下图所示
再次使用dmesg命令查看内核打印,可以看到,回调函数已经执行: