基于上節內核啟動參數實現原理內容, 其中對
early_param
的實現流程做了分析, 已基本清晰. 但有不少的參數是在內核模塊中聲明的, 具體賦值流程也值得一探究竟.
nomodeset
裝過Linux
系統的同學可能多少有看到過nomodeset
這個參數, 解決一些顯卡點不亮Linux
的問題.
那么這個nomodeset
是怎么生效的呢?
在內核代碼中查找一下:
[mxd@5 linux-4.19-loongson]$ grep nomodeset -rnIw drivers/video/
drivers/video/console/vgacon.c:116: pr_warning("You have booted with nomodeset. This means your GPU drivers are DISABLED\n");
drivers/video/console/vgacon.c:118: pr_warning("Unless you actually understand what nomodeset does, you should reboot without enabling it\n");
drivers/video/console/vgacon.c:124:__setup("nomodeset", text_mode);
drivers/video/fbdev/aty/radeon_base.c:263:static bool nomodeset = 0;
drivers/video/fbdev/aty/radeon_base.c:1472: if (nomodeset)
drivers/video/fbdev/aty/radeon_base.c:2624: } else if (!strncmp(this_opt, "nomodeset", 9)) {
drivers/video/fbdev/aty/radeon_base.c:2625: nomodeset = 1;
drivers/video/fbdev/aty/radeon_base.c:2671:module_param(nomodeset, bool, 0);
drivers/video/fbdev/aty/radeon_base.c:2672:MODULE_PARM_DESC(nomodeset, "bool: disable actual setting of video mode");
在drivers/video/console/vgacon.c
中有相關聲明:
static int __init text_mode(char *str)
{vgacon_text_mode_force = true;pr_warning("You have booted with nomodeset. This means your GPU drivers are DISABLED\n");pr_warning("Any video related functionality will be severely degraded, and you may not even be able to suspend the system properly\n");pr_warning("Unless you actually understand what nomodeset does, you should reboot without enabling it\n");return 1;
}/* force text mode - used by kernel modesetting */
__setup("nomodeset", text_mode);
而關于__setup
的定義, 在上節中已經看到過了:
#ifndef MODULE
#define __setup_param(str, unique_id, fn, early) \static const char __setup_str_##unique_id[] __initconst \__aligned(1) = str; \static struct obs_kernel_param __setup_##unique_id \__used __section(.init.setup) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }
#else /* MODULE */
#define __setup_param(str, unique_id, fn) /* nothing */
#endif#define __setup(str, fn) \__setup_param(str, fn, fn, 0)
上節中的early_param
是只在內核中用, 這個可就不好判斷了呀.
看一下編譯內容:
[mxd@5 linux-4.19-loongson]$ grep vgacon.o -rn drivers/video/console/Makefile
9:obj-$(CONFIG_VGA_CONSOLE) += vgacon.o
[mxd@5 linux-4.19-loongson]$ cat .config | grep CONFIG_VGA_CONSOLE
CONFIG_VGA_CONSOLE=y
所以, 這個參數還是給內核用的, 但與early_param
不同, 他的并不滿足early_param
的條件:
- 如果成員是
early
類型, 且成員中的變量名與傳入的參數名一致- 如果參數名是
console
, 且成員中的變量名是earlycon
時.
還記得有一個obsolete_checksetup
嗎? 它是在參數未知的情況下注冊的:
static int __init unknown_bootoption(char *param, char *val,const char *unused, void *arg)
{repair_env_string(param, val, unused, NULL);/* Handle obsolete-style parameters */if (obsolete_checksetup(param))return 0;............
}asmlinkage __visible void __init start_kernel(void)
{char *command_line;char *after_dashes;............after_dashes = parse_args("Booting kernel",static_command_line, __start___param,__stop___param - __start___param,-1, -1, NULL, &unknown_bootoption);............
}static bool __init obsolete_checksetup(char *line)
{const struct obs_kernel_param *p;bool had_early_param = false;p = __setup_start;do {int n = strlen(p->str);if (parameqn(line, p->str, n)) {if (p->early) {/* Already done in parse_early_param?* (Needs exact match on param part).* Keep iterating, as we can have early* params and __setups of same names 8( */if (line[n] == '\0' || line[n] == '=')had_early_param = true;} else if (!p->setup_func) {pr_warn("Parameter %s is obsolete, ignored\n",p->str);return true;} else if (p->setup_func(line + n))return true;}p++;} while (p < __setup_end);return had_early_param;
}
所以nomodeset
是在start_kernel
時初始化并調用的. 仍然屬于內核參數.
radeon.modeset
與nomodeset
類似, radeon.modeset
也是在搭配顯卡時安裝Linux
黑屏的解決方案之一.
在radeon
的驅動中查找一下看看:
[mxd@5 linux-4.19-loongson]$ grep modeset -rnwI drivers/gpu/drm/radeon/ | grep -v include
drivers/gpu/drm/radeon/trinity_dpm.c:1983: pi->voltage_drop_in_dce = false; /* need to restructure dpm/modeset interaction */
drivers/gpu/drm/radeon/radeon_kms.c:144: dev_err(&dev->pdev->dev, "Fatal error during modeset init\n");
drivers/gpu/drm/radeon/radeon_kms.c:150: /* Call ACPI methods: require modeset init
drivers/gpu/drm/radeon/radeon_connectors.c:195: /* mode_clock is clock in kHz for mode to be modeset on this connector */
drivers/gpu/drm/radeon/radeon_drv.c:204:MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
drivers/gpu/drm/radeon/radeon_drv.c:205:module_param_named(modeset, radeon_modeset, int, 0400);
drivers/gpu/drm/radeon/radeon_pm.c:281: /* This can fail if a modeset is in progress */
drivers/gpu/drm/radeon/radeon_pm.c:1565: * a modeset to call this.
drivers/gpu/drm/radeon/radeon_display.c:175: /* XXX match this to the depth of the crtc fmt block, move to modeset? */
可見在drivers/gpu/drm/radeon/radeon_drv.c
中通過module_param_named
和MODULE_PARM_DESC
聲明和描述了這個參數.
看看代碼:
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \__used __attribute__((section(".modinfo"), unused, aligned(1))) \= __stringify(tag) "=" info#define __MODULE_PARM_TYPE(name, _type) \__MODULE_INFO(parmtype, name##type, #name ":" _type)/* One for each parameter, describing how to use it. Some files domultiple of these per line, so can't just use MODULE_INFO. */
#define MODULE_PARM_DESC(_parm, desc) \__MODULE_INFO(parm, _parm, #_parm ":" desc)#define module_param_cb(name, ops, arg, perm) \__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)#define __module_param_call(prefix, name, ops, arg, perm, level, flags) \/* Default value instead of permissions? */ \static const char __param_str_##name[] = prefix #name; \static struct kernel_param __moduleparam_const __param_##name \__used \__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \= { __param_str_##name, THIS_MODULE, ops, \VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }#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)
所以radeon.modeset
的聲明實體就是:
param_check_int(modeset, &(radeon_modeset));
static const char __param_str_modeset[] = radeon.modeset;
static struct kernel_param __moduleparam_const __param_modeset__used __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) = {__param_str_modeset,radeon,¶m_ops_int,VERIFY_OCTAL_PERMISSIONS(0400),-1,0,{&(radeon_modeset)}};
static const char __UNIQUE_ID(modesettype)[]__used __attribute__((section(".modinfo"), unused, aligned(1))) = "parmtype=modeset:int";
類似的, 也是將相關的參數存入指定的section
中, 在加載modules
時進行初始化及調用. 原理同early_param
類似, 所以不再細說.
AI
學習的時候, 沒必要把代碼全都看得這么細, 除非對算法有研究. 在AI
時代, 借助AI
分析, 并掌握一個大項目的實現策略的優先級是更高的. 看看AI
的說法:
MODULE_PARM_DESC有什么用?
MODULE_PARM_DESC 是 Linux 內核模塊編程中的一個宏,用于描述模塊參數的意義和用途。當你在內核模塊中定義了一個可以由用戶空間程序修改的參數時,使用 MODULE_PARM_DESC 宏可以幫助記錄這個參數的描述性信息。
當你在模塊中定義一個參數時,通常會使用 module_param 或 module_param_array 宏來聲明這個參數,并指定它的類型和權限。接著,你可以在同一地方使用 MODULE_PARM_DESC 宏來添加對該參數的描述。
例如:
#include <linux/module.h> #include <linux/kernel.h>int myparam = 10; module_param(myparam, int, 0644); MODULE_PARM_DESC(myparam, "This is a description of the parameter myparam.");
在這個例子中,myparam 參數被聲明為整型,權限為 0644(讀寫權限對所有用戶開放),并且有一個描述,告訴用戶這個參數的含義。
當你使用 modinfo 命令查詢模塊信息時,MODULE_PARM_DESC 提供的描述會被顯示出來,幫助用戶理解參數的作用。這對于調試和配置模塊時非常有用,因為用戶可以更容易地理解每個參數的功能。
例如,如果你有一個名為 example.ko 的模塊,你可以使用以下命令來查看參數信息:
$ modinfo example.ko filename: /lib/modules/$(uname -r)/kernel/drivers/example.ko license: GPL description: Example kernel module author: Your Name srcversion: 1234567890abcdef... depends: retpoline: Y name: example vermagic: 4.19.0-xyz SMP mod_unload parm: myparam:This is a description of the parameter myparam. (int)
在 parm: 行中,你會看到 myparam 參數的描述。這樣,當用戶使用 modprobe 加載模塊或使用 insmod 命令手動加載模塊并設置參數時,他們可以根據描述來正確地設置參數值。
module_param_named和模塊初始化代碼的調用順序是怎樣的
在Linux內核模塊中,module_param_named宏的使用和模塊初始化函數的調用順序是非常關鍵的,因為它們決定了模塊參數如何被內核識別和處理。以下是典型的調用順序和工作流程:
模塊參數聲明與注冊:
在模塊的源文件中,你需要使用module_param_named(或其他module_param_*宏)來聲明模塊參數。這些宏會在編譯時生成相應的代碼,注冊參數,并將其關聯到一個特定的回調函數,該函數用于處理模塊加載或卸載時的參數設置。
這些宏的執行是在編譯階段完成的,它們生成的代碼會被鏈接到模塊的.init.text和.data段中。
模塊初始化:
模塊初始化函數(如init_module或自定義命名的初始化函數)會在模塊加載到內核時由內核自動調用。在這之前,內核已經解析了模塊信息段(.modinfo),包括所有由module_param_*宏注冊的參數信息。
初始化函數可以訪問通過module_param_*宏注冊的所有參數。這意味著你可以在這個函數中使用這些參數來初始化模塊的狀態或者配置模塊的行為。
參數解析與應用:
當模塊加載時,內核會讀取傳遞給模塊的命令行參數,并嘗試匹配這些參數與模塊參數。如果找到了匹配,內核會調用相應的回調函數來設置參數值。
這一過程發生在模塊初始化函數被調用之前。因此,當你的模塊初始化代碼執行時,所有通過命令行傳遞的參數值已經被設置好了。
模塊清理:
類似地,當模塊被卸載時,模塊的清理函數(如cleanup_module)會被調用。此時,module_param_*宏所注冊的參數仍然可用,直到模塊完全從內核中移除。
總結來說,module_param_named宏的調用應該在模塊初始化函數的定義之前,這樣在模塊加載時,內核就能正確地解析和設置參數。而模塊初始化函數則會在參數設置完成后被調用,此時就可以使用這些參數來配置模塊了。
使用
借助modinfo
命令:
[root@mxd ~]# modinfo /lib/modules/6.8.6-2/kernel/drivers/gpu/drm/radeon/radeon.ko.zst
......
name: radeon
vermagic: 6.8.6-2 SMP preempt mod_unload modversions LOONGARCH 64BIT
parm: no_wb:Disable AGP writeback for scratch registers (int)
parm: modeset:Disable/Enable modesetting (int)
parm: dynclks:Disable/Enable dynamic clocks (int)
parm: r4xx_atom:Enable ATOMBIOS modesetting for R4xx (int)
parm: vramlimit:Restrict VRAM for testing, in megabytes (int)
......
所以在內核啟動時, 可以通過radeon.modeset=0
的方式禁用radeon
驅動的modesetting
.
龍芯的GPU
也有相似的功能:
root@loongson-pc:/home/loongson# modinfo /lib/modules/4.19.0-19-loongson-3/kernel/drivers/gpu/drm/gsgpu/gpu/gsgpu.ko
filename: /lib/modules/4.19.0-19-loongson-3/kernel/drivers/gpu/drm/gsgpu/gpu/gsgpu.ko
license: GPL and additional rights
description: GS GPU Driver
author: Loongson graphics driver team
firmware: loongson/lg100_cp.bin
alias: pci:v00000014d00007A25sv*sd*bc*sc*i*
depends: gpu-sched
intree: Y
name: gsgpu
vermagic: 4.19.0-19-loongson-3 SMP mod_unload modversions LOONGARCH 64BIT
parm: LG100_support:LG100 support (1 = enabled (default), 0 = disabled (int)
parm: vramlimit:Restrict VRAM for testing, in megabytes (int)
parm: vis_vramlimit:Restrict visible VRAM for testing, in megabytes (int)
parm: gartsize:Size of GART to setup in megabytes (32, 64, etc., -1=auto) (uint)
parm: gttsize:Size of the GTT domain in megabytes (-1 = auto) (int)
parm: moverate:Maximum buffer migration rate in MB/s. (32, 64, etc., -1=auto, 0=1=disabled) (int)
parm: benchmark:Run benchmark (int)
所以當龍芯的板卡出問題時, 可以通過gsgpu.LG100_support=0
來禁用gsgpu
功能.
參考文獻
- 內核源碼
- 通義千問