Linux 內核調試之使用模塊參數

開場白

本文主要介紹內核開發中常用的模塊傳參手段,通過模塊參數傳遞可以通過用戶態來獲取內核的一些信息,也可以通過用戶態寫入一些值來控制內核相關行爲。一般內核開發者很喜歡使用模塊傳參來調試內核功能,如 damon 模塊(數據訪問監控器)。

主要由以下部分組成: 

  1. 常用內核 API

  2. 支持的參數數據類型

  3. 參數文件訪問權限

  4. 模塊參數的讀寫

  5. 示例代碼 

  6. 參考資料

1. 常用內核 API

1.1 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)

是最常規的傳參方式,支持對普通數據類型的參數的讀寫。

例如:

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

100

1.2 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)

即是數組類型支持。

例如:

/* 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

1,2,3,4,4

1.3 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

即是參數的回調函數支持。

例如:

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, m_int_cb_ops, m_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,使得我們能有機會攔截參數來做一些操作。

1.4 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, ¶m_ops_##type, &value, perm);            \
        __MODULE_PARM_TYPE(name, #type)

即是參數的重命名支持。

例如:

/* 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 文件中定義

2. 支持的參數數據類型

內核支持的參數數據類型在定義 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).

注:這些 api 的時候內核源碼中有大量的例子,直接搜索即可知道內核開發者是如何使用。我們在實際內核開發中,如何在海量的源碼中獲得我們所需要的東西並在我們的優化代碼中得以使用也是也是內核開發者需要具備的素養。

3. 參數文件訪問權限

常見權限如下:

4. 模塊參數的讀寫

4.1 讀

對於內核態,直接讀取定義的模塊參數即可。

而對於用戶態,是通過 sysfs 來讀取它的。

讀取格式:cat  /sys/module/xxx/parameters/param

xxx 表示想讀取的模塊 param 表示具體的參數

例如:示例中的 module_param_test 模塊,讀模塊參數如下:cat  /sys/module/module_param_test/parameters/param_uint 100

4.2 寫

對於內核態,直接讀取定義的模塊參數即可。

而對於用戶態,我們有三種方式來寫模塊參數。

方法 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

5. 示例代碼

#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, m_int_cb_ops, m_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");

6. 參考資料

include/linux/moduleparam.h
kernel/params.c
mm/damon/reclaim.c
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/d0wRgle_4ARrAO-B--DVAA