- 環境:
- 處理器架構: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, ¶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
,使得我們能有機會攔截引數來做一些操作。
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
檔案中定義
支援的引數資料型別
核心支援的引數資料型別在定義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, ¶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");
原文連結:https://cloud.tencent.com/developer/article/2423667