首页 > 系统相关 >【驱动】Linux内核调试之使用模块参数

【驱动】Linux内核调试之使用模块参数

时间:2024-06-10 13:00:35浏览次数:12  
标签:int module bool param Linux array parameter 调试 内核

  • 环境:
  • 处理器架构:arm64
  • 内核源码:linux-6.6.29
  • ubuntu版本:20.04.1
  • 代码阅读工具:vim+ctags+cscope

本文主要介绍内核开发中常用的模块传参手段,通过模块参数传递可以通过用户态来获取内核的一些信息,也可以通过用户态写入一些值来控制内核相关行为。一般内核开发者很喜欢使用模块传参来调试内核功能,如damon模块(数据访问监控器)。

主要由以下部分组成:

常用内核API

module_param

/**
 * module_param - typesafe helper for a module/cmdline parameter
 * @name: the variable to alter, and exposed parameter name.
 * @type: the type of the parameter
 * @perm: visibility in sysfs.
 *
 * @name becomes the module parameter, or (prefixed by KBUILD_MODNAME and a
 * ".") the kernel commandline parameter.  Note that - is changed to _, so
 * the user can use "foo-bar=1" even for variable "foo_bar".
 *
 * @perm is 0 if the variable is not to appear in sysfs, or 0444
 * for world-readable, 0644 for root-writable, etc.  Note that if it
 * is writable, you may need to use kernel_param_lock() around
 * accesses (esp. charp, which can be kfreed when it changes).
 *
 * The @type is simply pasted to refer to a param_ops_##type and a
 * param_check_##type: for convenience many standard types are provided but
 * you can create your own by defining those variables.
 *
 * Standard types are:
 *      byte, hexint, short, ushort, int, uint, long, ulong
 *      charp: a character pointer
 *      bool: a bool, values 0/1, y/n, Y/N.
 *      invbool: the above, only sense-reversed (N = true).
 */
#define module_param(name, type, perm)                          \
        module_param_named(name, name, type, perm)

是最常规的传参方式,支持对普通数据类型的参数的读写。

  • name :表示模块参数名 (模块中定义和sysfs中显示的都是这个名字)
  • type:表示数据类型,如uint表示unsigned int
  • perm:sysfs文件中参数文件的访问权限 (一般8进制表示)

例如:

static unsigned int param_uint;
module_param(param_uint, uint, 0600);
MODULE_PARM_DESC(param_uint, "This is a uint parameter!"); 

通过以下方式可以设置这个参数:

1)加载模块时

insmod module_param_test.ko param_uint=100 

2)cmdline传递

cmdline中加入 module_param_test.param_uint=100 字段

3)通过写sysfs节点

echo 100 > /sys/module/module_param_test/parameters/param_uint

通过sysfs查看模块参数:

cat /sys/module/module_param_test/parameters/param_uint

module_param_array

/**
 * module_param_array - a parameter which is an array of some type
 * @name: the name of the array variable
 * @type: the type, as per module_param()
 * @nump: optional pointer filled in with the number written
 * @perm: visibility in sysfs
 * 
 * Input and output are as comma-separated values.  Commas inside values
 * don't work properly (eg. an array of charp).
 *
 * ARRAY_SIZE(@name) is used to determine the number of elements in the
 * array, so the definition must be visible.
 */
#define module_param_array(name, type, nump, perm)              \
        module_param_array_named(name, name, type, nump, perm)

即是数组类型支持。

  • name:表示数组名
  • type:数组元素类型
  • nump:一个整型变量,用于存储数组中元素的数量,可选(不关心可以写为NULL)
  • perm:sysfs文件中参数文件的权限 (一般8进制表示)

例如:

/* array: echo "1,2,3,4,4" > param_array */
static int param_array[5];
static int array_num;
//module_param_array(param_char_array, int, NULL, 0600);
module_param_array(param_array, int, &array_num, 0600);
MODULE_PARM_DESC(param_bool, "This is a array parameter!");

通过以下方式可以设置这个参数:

1)加载模块时传递

 insmod module_param_test.ko param_array=1,2,3,4,4 

2)通过cmdline传递

cmdline中加入 module_param_test.param_array=1,2,3,4,4 字段

3)通过写sysfs节点

echo 1,2,3,4,4 > /sys/module/module_param_test/parameters/param_array

通过sysfs查看模块参数:

cat /sys/module/module_param_test/parameters/param_array

module_param_cb

/** 
 * module_param_cb - general callback for a module/cmdline parameter
 * @name: a valid C identifier which is the parameter name.
 * @ops: the set & get operations for this parameter.
 * @arg: args for @ops
 * @perm: visibility in sysfs.
 *
 * The ops can have NULL set or get functions.
 */
#define module_param_cb(name, ops, arg, perm)                                 \
        __module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0

即是参数的回调函数支持。

  • name :表示模块参数名 (模块中定义和sysfs中显示的都是这个名字)
  • ops:参数的 set&get 操作集
  • arg:用于操作集的参数 perm:sysfs文件中参数文件的权限 (一般8进制表示)

例如:

static int param_int_cb;
int param_int_cb_store(const char *val, const struct kernel_param *kp)
{
        int value;
        int err;

        err = kstrtoint(val, 0, &value);
        if (err)
                return err;

        if (value > 0)
                pr_info("value:%d\n", value);

        //return param_set_int(val, kp);
        return param_set_uint_minmax(val, kp, 0, 1000);
}

int param_int_cb_show(char *buffer, const struct kernel_param *kp)
{
        int value = *((int *)kp->arg);

        if (value > 0)
                return sprintf(buffer, "value:%d > 0\n", value);
        else
                return sprintf(buffer, "value:%d <= 0\n", value);
}
static const struct kernel_param_ops param_int_cb_ops = {
        .set = param_int_cb_store,
        //.get = param_get_int, /* default */
        .get = param_int_cb_show,
};
module_param_cb(param_int_cb, &param_int_cb_ops, &param_int_cb, 0600);
MODULE_PARM_DESC(param_int_cb, "This is param_int_cb\n");

读写参数方式和上面介绍的类似,这里需要注意的是:当读参数param_int_cb时就会回调param_int_cb_show函数,写参数param_int_cb时就会回调param_int_cb_store,使得我们能有机会拦截参数来做一些操作。

module_param_named

/**
 * module_param_named - typesafe helper for a renamed module/cmdline parameter
 * @name: a valid C identifier which is the parameter name.
 * @value: the actual lvalue to alter.
 * @type: the type of the parameter
 * @perm: visibility in sysfs.
 *
 * Usually it's a good idea to have variable names and user-exposed names the
 * same, but that's harder if the variable must be non-static or is inside a
 * structure.  This allows exposure under a different name.
 */
#define module_param_named(name, value, type, perm)                        \
        param_check_##type(name, &(value));                                \
        module_param_cb(name, &param_ops_##type, &value, perm);            \
        __MODULE_PARM_TYPE(name, #type)

即是参数的重命名支持。

  • name:表示参数的别名/重命名,会在sysfs中显示
  • value:表示参数名,在模块中定义的变量名
  • type:表示数据类型
  • perm:sysfs文件中参数文件的权限

例如:

/* bool eg: echo 0/1/n/y/N/Y  > param_bool1_named */
static bool param_bool1;
module_param_named(param_bool1_named, param_bool1, bool, 0600);
MODULE_PARM_DESC(param_bool1_named, "This is a bool parameter!");

读写参数方式和上面介绍的类似,这里需要注意的是:模块中定义为param_bool1这个变量名,但是sysfs中使用的是这个param_bool1_named别名。

注:都在include/linux/moduleparam.h文件中定义

支持的参数数据类型

内核支持的参数数据类型在定义module_param的时候有说明:

include/linux/moduleparam.h
Standard types are:
     byte, hexint, short, ushort, int, uint, long, ulong
     charp: a character pointer
     bool: a bool, values 0/1, y/n, Y/N.
     invbool: the above, only sense-reversed (N = true).
  • byte :表示字节大小,是无符号char类型 ,unsigned char
  • hexint:读的时候会显示16进制, 表示无符号的 int类型,即是 unsigned int
  • short:表示有符号的short类型,即是 short
  • ushort:表示无符号的short类型,即是 unsigned short
  • int:表示有符号的int类型,即是 int
  • uint:表示无符号的 int类型,即是 unsigned int
  • long:表示有符号的long类型,即是 long
  • ulong:表示无符号的long类型,即是 unsigned long
  • charp:char 指针类型,也就是字符串
  • bool:布尔类型
  • invbool:反布尔类型
  • 此外还支持llong (long long)和ullong (unsigned long long)类型。

注:这些api的时候内核源码中有大量的例子,直接搜索即可知道内核开发者是如何使用。我们在实际内核开发中,如何在海量的源码中获得我们所需要的东西并在我们的优化代码中得以使用也是也是内核开发者需要具备的素养。

参数文件访问权限

常见权限如下:

  • 0 :无任何权限 ,在sysfs中不显示这个参数文件
  • 0666: -rwxrwxrwx 即是用户、组、其他 都可读可写 会编译错误,权限比较高,禁止使用。权限0666意味着任何用户都可以读写该文件。在内核模块中,通常需要保护模块的参数不被恶意修改,以避免潜在的安全风险。
  • 0444: -r--r--r-- -> 用户、组、其他都只读
  • 0600:-rw------- 用户可读可写,组、其他无权限
  • 0644:-rw-r--r-- 用户可读可写,组、其他只读 当然也可以使用形如S_IRUSR这样的表示方法。

模块参数的读写

对于内核态,直接读取定义的模块参数即可。

而对于用户态,是通过sysfs来读取它的。

读取格式:

cat  /sys/module/xxx/parameters/param

xxx表示想读取的模块 param表示具体的参数

例如:示例中的module_param_test模块,读模块参数如下:

cat  /sys/module/module_param_test/parameters/param_uint 100

对于内核态,直接读取定义的模块参数即可。

而对于用户态,我们有三种方式来写模块参数。

方法1:系统启动阶段通过cmdline传递

一般用于buildin到内核的模块

传参的方式为:module.param=val

例如:

module_param_test.param_charp=hello module_param_test.param_array=1,2,3,4,5

方法2:加载模块时传递

一般用于编译成模块的场景。

传参的方式为:

insmod xxx.ko param=val

例如:

insmod module_param_test.ko param_uint=100

方法3:写sysfs中参数文件节点

传参的方式为:

echo xxx >/sys/module/xxx/parameters/param

例如:

echo 100 > /sys/module/module_param_test/parameters/param_uint

示例代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

/********** case 1: base type **********/
/* bool eg: echo 0/1/n/y/N/Y  > param_bool*/
static bool param_bool;
//module_param(param_bool, bool, 0);//no permission, no file in sysfs
//module_param(param_bool, bool, 0666);//-rwxrwxrwx -> forbit
//module_param(param_bool, bool, 0644);//-rw-rw-rw-
module_param(param_bool, bool, 0600);
MODULE_PARM_DESC(param_bool, "This is a bool parameter!");

/* bool eg: echo 0/1/n/y/N/Y  > param_bool1_named */
static bool param_bool1;
module_param_named(param_bool1_named, param_bool1, bool, 0600);
MODULE_PARM_DESC(param_bool1_named, "This is a bool parameter!");

/* byte  eg:  echo 0-255 > param_char */
static unsigned char param_char;
module_param(param_char, byte, 0600);
MODULE_PARM_DESC(param_char, "This is a char parameter!");

/* short eg: echo -100 > param_short */
static short param_short;
module_param(param_short, short, 0600);
MODULE_PARM_DESC(param_short, "This is a short parameter!");

/* unsigned short eg: echo 100 > param_short */
static unsigned short param_ushort;
module_param(param_ushort, ushort, 0600);
MODULE_PARM_DESC(param_ushort, "This is a ushort parameter!");

/* int eg: echo -100 > param_int */
static int param_int;
module_param(param_int, int, 0600);
MODULE_PARM_DESC(param_int, "This is a int parameter!");

/* unsigned int eg: echo 100 > param_unint */
static unsigned int param_uint;
module_param(param_uint, uint, 0600);
MODULE_PARM_DESC(param_uint, "This is a uint parameter!");

/* long eg: echo -100 > param_long*/
static long param_long;
module_param(param_long, long, 0600);
MODULE_PARM_DESC(param_long, "This is a long parameter!");

/* unsigned long eg: echo 100 > param_ulong */
static unsigned long param_ulong;
module_param(param_ulong, ulong, 0600);
MODULE_PARM_DESC(param_ulong, "This is a ulong parameter!");

/* unsigned long long eg: echo 100 > param_ullong */
static unsigned long long param_ullong;
module_param(param_ullong, ullong, 0600);
MODULE_PARM_DESC(param_ullong, "This is a unsigned long long parameter!");

/* character pointer : eg: echo hello > param_charp */
static char *param_charp;
module_param(param_charp, charp, 0600);
MODULE_PARM_DESC(param_bool, "This is a charp parameter!");

/********** case 2: array **********/
/* array: echo "1,2,3,4,4" > param_array */
static int param_array[5];
static int array_num;
//module_param_array(param_char_array, int, NULL, 0600);
module_param_array(param_array, int, &array_num, 0600);
MODULE_PARM_DESC(param_bool, "This is a array parameter!");

/********** case 3: use call back **********/
static int param_int_cb;
int param_int_cb_store(const char *val, const struct kernel_param *kp)
{
        int value;
        int err;        //把字符串转换为int类型
        err = kstrtoint(val, 0, &value);
        if (err)
                return err;
        if (value > 0)
                pr_info("value:%d\n", value);
        //将用户态传过来的参数值设置到模块参数中,由于这里是基础的int类型,所以可以直接调用param_set_int api
        //param_set_uint_minmax 这个api会在设置时考虑最小和最大值
        //return param_set_int(val, kp);
        return param_set_uint_minmax(val, kp, 0, 1000);
}

int param_int_cb_show(char *buffer, const struct kernel_param *kp)
{
        int value = *((int *)kp->arg);

        //用户态最终通过buffer来获得参数的信息,所以这里通过sprintf 做格式化操作写到buffer中
        if (value > 0)
                return sprintf(buffer, "value:%d > 0\n", value);
        else
                return sprintf(buffer, "value:%d <= 0\n", value);
}

static const struct kernel_param_ops param_int_cb_ops = {
        .set = param_int_cb_store,
        //.get = param_get_int, /* default */
        .get = param_int_cb_show,
};
module_param_cb(param_int_cb, &param_int_cb_ops, &param_int_cb, 0600);
MODULE_PARM_DESC(param_int_cb, "This is param_int_cb\n");


static int __init module_test_init(void)
{
        pr_emerg("module_test_init\n");
        return 0;
}

static void __exit module_test_exit(void)
{
        pr_emerg("module_test_exit\n");
}


module_init(module_test_init);
module_exit(module_test_exit);
MODULE_LICENSE("GPL");

原文链接:https://cloud.tencent.com/developer/article/2423667

标签:int,module,bool,param,Linux,array,parameter,调试,内核
From: https://www.cnblogs.com/dongxb/p/18240596

相关文章

  • KPTI——可以缓解“熔断” (Meltdown) 漏洞的内核新特性
    Linux内核修复办法:内核页表隔离KPTl(kernelpagetableisolation)每个进程一张页表变成两张:运行在内核态和运行在用户态时分别使用各自分离的页表Kernel页表包含了进程用户空间地址的映射和Kernel使用的内存映射用户页表仅仅包含了用户空间的内存映射以及内核跳板的......
  • [linux]mark-tool
    前言在远程连接到linux进行操作时,经常要切换目录,有些目录切换频次较高,因此写了个shell工具,用于收藏目录、切换目录等。也不需要安装,直接添加脚本即可。配置首先声明脚本是基于bashshell,zsh和fish未经测试。编辑文件~/.bash_custom_functions,添加以下内容mark(){loca......
  • Kali Linux 2024.2 发布 (t64, GNOME 46 & Community Packages) - 领先的渗透测试发行
    KaliLinux2024.2发布(t64,GNOME46&CommunityPackages)-领先的渗透测试发行版ThemostadvancedPenetrationTestingDistribution请访问原文链接:https://sysin.org/blog/kali-linux/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgKaliLinux2024.2已......
  • Linux笔记
    (1)常用命令: [1]Ctrl: +a 移动至行首 +e 移动至行尾 +w 清除光标之前一个单词 +k 清除光标到行尾的字符 +h 删除光标前一个字符 +b 光标前移一个字符 +f 光标后移一个字符 +c 取消本次命令运行 +d 退出/登出(vi/vim除外) +l 清屏 (=命令clear) +r 匹配历史......
  • LInux 实战:自己写一个shell
     1.将前面所学的东西融合起来写一个shell  ——可以更好的理解啥叫shell2.以我现阶段的能力只能模拟实现部分//写一个简单的shell#include<stdio.h>#include<string.h>#include<stdlib.h>#include<sys/types.h>#include<unistd.h>#include<sys/wait.h>#define......
  • linux系统安全及应用
    一、账号安全控制用户账号是计算机使用者的身份凭证或标识,每个要访问系统资源的人,必须凭借其用户账号才能进入计算机。在 Linux 系统中,提供了多种机制来确保用户账号的正当、安全使用。1、账号的基本安全措施(1)系统账号清理常见的非登录用户账号包括 bin、daemon、adm、l......
  • Linux c fread/fseek 函数
    函数:freadsize_tfread(void*ptr,size_tsize,size_tnmemb,FILE*stream);参数说明:参数buffer指向要读取的数组中首个对象的指针size每个对象的大小(单位是字节)count要读取的对象个数stream输入流函数:fseekintfseek(FILE*stream,longintoffse......
  • C++系统编程篇——linux编辑器vim
    Linux编辑器vim(1)vim常用模式命令/正常模式控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insertmode下,或者到lastlinemode插入模式只有在Insertmode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。底行模式......
  • 11 深入理解Linux文件系统与日志分析
    目录11.1深入理解Linux文件系统    11.1.1inode与block详解        1.inode和block概述        2.inode的内容        3.inode的号码        4.inode的大小    11.1.2硬链接与......
  • 服务器linux系统 Debian 更新安全补丁命令
    服务器linux系统Debian更新安全补丁命令服务器换成Debian系统了,之前用的是centos系统,不过后续不支持更新还是什么,所以换成Debian系统,拿到系统首先是update更新了,把安全补丁打到最新。收集了一些命令,自用收藏。 Debian使用来自存储库的deb软件包来管理Debian系统上软件......