Linux 內核調試之使用模塊參數
開場白
-
環境:
處理器架構:arm64
內核源碼:linux-6.6.29
ubuntu 版本:20.04.1
代碼閱讀工具:vim+ctags+cscope
本文主要介紹內核開發中常用的模塊傳參手段,通過模塊參數傳遞可以通過用戶態來獲取內核的一些信息,也可以通過用戶態寫入一些值來控制內核相關行爲。一般內核開發者很喜歡使用模塊傳參來調試內核功能,如 damon 模塊(數據訪問監控器)。
主要由以下部分組成:
-
常用內核 API
-
支持的參數數據類型
-
參數文件訪問權限
-
模塊參數的讀寫
-
示例代碼
-
參考資料
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)
是最常規的傳參方式,支持對普通數據類型的參數的讀寫。
-
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
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)
即是數組類型支持。
-
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
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
即是參數的回調函數支持。
-
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, ¶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)
即是參數的重命名支持。
-
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 文件中定義
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).
-
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 的時候內核源碼中有大量的例子,直接搜索即可知道內核開發者是如何使用。我們在實際內核開發中,如何在海量的源碼中獲得我們所需要的東西並在我們的優化代碼中得以使用也是也是內核開發者需要具備的素養。
3. 參數文件訪問權限
常見權限如下:
-
0 :無任何權限 ,在 sysfs 中不顯示這個參數文件
-
0666: -rwxrwxrwx 即是用戶、組、其他 都可讀可寫 會編譯錯誤,權限比較高,禁止使用。權限 0666 意味着任何用戶都可以讀寫該文件。在內核模塊中,通常需要保護模塊的參數不被惡意修改,以避免潛在的安全風險。
-
0444: -r--r--r-- -> 用戶、組、其他都只讀
-
0600:-rw------- 用戶可讀可寫,組、其他無權限
-
0644:-rw-r--r-- 用戶可讀可寫,組、其他只讀
當然也可以使用形如 S_IRUSR 這樣的表示方法。
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