<kernel v5.0>
cmdline 인수로 받은 파라메터에 대해 구식 또는 신식 커널 파라메터 블럭에서에 연결된 설정 함수를 호출한다. 모듈(modprobe) 관련 파라메터가 있는 경우는 이 루틴에서 무시하고 매치되지 않은 unknown 파라메터는 값이 있는 경우 envp_init[] 배열에 추가하고 값이 없는 경우 argv_init[] 배열에 추가한다.
setup_kernel() 중간 부분
init/main.c
after_dashes = parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, -1, -1, &unknown_bootoption); if (!IS_ERR_OR_NULL(after_dashes)) parse_args("Setting init args", after_dashes, NULL, 0, -1, -1, set_init_arg);
- after_dashes = parse_args(“Booting kernel”, static_command_line, __start___param, __stop___param – __start___param, -1, -1, &unknown_bootoption);
- static_command_line을 파싱하고 param=value 형태로 다듬고 신형 커널 파라메터 블럭에서(__start___param ~ __stop___param)에서 각 커널파라메터에 매치되는 함수를 호출하고 매치되지 않는 경우 unknown_bootoption() 함수를 호출한다.
- 만일 파라메터가 “–“로 끝나는 경우 after_dashes에 “–” 이후의 문자열이 담긴다.
- if (!IS_ERR_OR_NULL(after_dashes))
- 파싱 하면서 “–“를 발견한 경우
- parse_args(“Setting init args”, after_dashes, NULL, 0, -1, -1, set_init_arg);
- “–” 뒤의 파라메터들을 argv_init[] 배열에 추가한다.
parse_args()
kernel/params.c
char *parse_args(const char *doing, char *args, const struct kernel_param *params, unsigned num, s16 min_level, s16 max_level, int (*unknown)(char *param, char *val, const char *doing))
cmdline을 파싱하여 각 커널파라메터에 대응하는 params 블럭에서 찾아서 매치되는 파라메터의 함수를 호출하고 매치되지 않는 경우 unknown을 호출한다. 또한 파라메터가 “–“로 끝나는 경우 “–” 이후의 문자열을 리턴한다.
- parse_args()를 호출하는 case
- parse_args(“early_options”, , , , , do_early_param);
- init/main.c – parse_early_options() 함수
- 참고: parse_early_param() | 문c
- parse_args(“Booting kernel”, static_command_line, __start___param, __stop___param – start___param, -1, -1, &unknown_bootoption);
- init/main.c – start_kernel() 함수
- parse_args(“Setting init args”, after_dashes, NULL, 0, -1, -1, set_init_arg);
- init/main.c – start_kernel() 함수
- parse_args(“dyndbg params”, cmdline, NULL, 0, 0, 0, &ddebug_dyndbg_boot_param_cb);
- lib/dynamic_debug.c – dynamic_debug_init() 함수
- parse_args(initcall_level_names[level], initcall_command_line, __start___param, __stop___param – __start___param, level, level, &repair_env_string);
- init/main.c – do_initcall_level() 함수
- parse_args(“early_options”, , , , , do_early_param);
unknown_bootoption()
init/main.c
/* * Unknown boot options get handed to init, unless they look like * unused parameters (modprobe will find them in /proc/cmdline). */ static int __init unknown_bootoption(char *param, char *val, const char *unused) { repair_env_string(param, val, unused); /* Handle obsolete-style parameters */ if (obsolete_checksetup(param)) return 0; /* Unused module parameter. */ if (strchr(param, '.') && (!val || strchr(param, '.') < val)) return 0; if (panic_later) return 0; if (val) { /* Environment option */ unsigned int i; for (i = 0; envp_init[i]; i++) { if (i == MAX_INIT_ENVS) { panic_later = "env"; panic_param = param; } if (!strncmp(param, envp_init[i], val - param)) break; } envp_init[i] = param; } else { /* Command line option */ unsigned int i; for (i = 0; argv_init[i]; i++) { if (i == MAX_INIT_ARGS) { panic_later = "init"; panic_param = param; } } argv_init[i] = param; } return 0; }
cmdline 인수로 받은 파라메터를 param=value 형태로 다듬고 구형 커널 파라메터 블럭(__setup_start ~ __setup_end) 에서 매치된 파라메터 중 early가 아닌 경우 해당 파라메터에 연결된 설정 함수를 호출한다. 또한 모듈(modprobe) 관련 파라메터가 있는 경우 일단 무시한다. 그 외에 매치되지 않은 unknown 파라메터는 값이 있는 경우 envp_init[] 배열에 추가하고 값이 없는 경우 argv_init[] 배열에 추가한다.
- repair_env_string(param, val, unused);
- param=val 및 param=”val”과 같은 형태는 ‘=’대신 파라메터 구분을 하기 위해 null이 입력되어 있는데 이를 다시 ‘=’문자로 치환하고 따옴표가 사용된 경우 param=val 형태가 되도록 따옴표를 제거한다.
- rpi2 변경된 예)
- dma.dmachans=0x7f35 bcm2708_fb.fbwidth=592 bcm2708_fb.fbheight=448 bcm2709.boardrev=0xa01041 bcm2709.serial=0x670ebdbf smsc95xx.macaddr=B8:27:EB:0E:BD:BF bcm2708_fb.fbswap=1 bcm2709.disk_led_gpio=47 bcm2709.disk_led_active_low=0 sdhci-bcm2708.emmc_clock_freq=250000000 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p6 rootfstype=ext4 elevator=deadline rootwait
- if (obsolete_checksetup(param)) return 0;
- 구형 파라메터 블럭에서 매치된 파라메터가 있는 경우 해당 파라메터에 연결된 설정함수를 호출하고 리턴한다.
- if (strchr(param, ‘.’) && (!val || strchr(param, ‘.’) < val)) return 0;
- 모듈 파라메터가 사용된 경우 리턴한다.
- rpi2 예) dma.dmachans=0x7f35
- 참고: param: don’t complain about unused module parameters
- 모듈 파라메터가 사용된 경우 리턴한다.
val 값이 있는 경우 environment 옵션으로 등록한다.
- if (val) {
- val 값이 있는 경우
- for (i = 0; envp_init[i]; i++) {
- envp_init[] 배열에 등록된 엔트리 수 만큼 루프를 돈다.
- if (i == MAX_INIT_ENVS) { panic_later = “env”; panic_param = param;
- i가 MAX_INIT_ENVS에 도달하면 panic 관련 변수 설정을 한다.
- if (!strncmp(param, envp_init[i], val – param)) break;
- 파라메터명이 envp_init[]에 등록되어 있는 문자열과 같은 경우 break하여 루틴을 빠져나간다.
- envp_init[i] = param;
- envp_init[] 배열에 param을 추가한다.
val 값이 없는 경우 command line option으로 등록한다.
- for (i = 0; argv_init[i]; i++) {
- argv_init[] 배열에 등록된 엔트리 수 만큼 루프를 돈다.
- if (i == MAX_INIT_ARGS) { panic_later = “init”; panic_param = param; }
- i가 MAX_INIT_ARGS에 도달하면 panic 관련 변수 설정을 한다.
- argv_init[i] = param;
- argv_init[] 배열에 param을 추가한다.
repair_env_string()
init/main.c
static int __init repair_env_string(char *param, char *val, const char *unused) { if (val) { /* param=val or param="val"? */ if (val == param+strlen(param)+1) val[-1] = '='; else if (val == param+strlen(param)+2) { val[-2] = '='; memmove(val-1, val, strlen(val)+1); val--; } else BUG(); } return 0; }
param=val 및 param=”val”과 같은 형태는 ‘=’대신 파라메터 구분을 하기 위해 null이 입력되어 있는데 이를 다시 ‘=’문자로 치환하고 따옴표가 사용된 경우 param=val 형태가 되도록 따옴표를 제거한다.
- if (val == param+strlen(param)+1) val[-1] = ‘=’;
- param<null>val 과 같이 따옴표를 사용하지 않은 경우 val[-1]에 ‘=’를 대입한다.
- else if (val == param+strlen(param)+2) { val[-2] = ‘=’; memmove(val-1, val, strlen(val)+1); val–;
- param<null>”val”과 같이 따옴표를 사용한 경우 val[2]에 ‘=’를 대입하고 val 문자열을 1칸 앞으로 당긴다.
obsolete_checksetup()
init/main.c
static int __init obsolete_checksetup(char *line) { const struct obs_kernel_param *p; int had_early_param = 0; 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 = 1; } else if (!p->setup_func) { pr_warn("Parameter %s is obsolete, ignored\n", p->str); return 1; } else if (p->setup_func(line + n)) return 1; } p++; } while (p < __setup_end); return had_early_param; }
__setup_start ~ __setup_end 까지의 구식 커널 파라메터 블럭에서 매치된 파라메터 중 early가 아닌 경우 해당 파라메터에 연결된 설정 함수를 호출한다.
- if (parameqn(line, p->str, n)) {
- 인수 문자열과 파라메터 블럭의 문자열을 n 바이트만큼 비교하여 같으면
- if (p->early) { if (line[n] == ‘\0’ || line[n] == ‘=’) had_early_param = 1;
- early 파라메터이면 had_early_param에 1을 대입한다.
- } else if (!p->setup_func) { pr_warn(“Parameter %s is obsolete, ignored\n”, p->str); return 1;
- 매치된 파라메터의 setup_func이 등록되어 있지 않은 경우 경고 메시지를 출력하고 1을 리턴한다.
- } else if (p->setup_func(line + n)) return 1;
- 매치된 파라메터의 setup_func()에 인수 val을 준비하여 호출하고 1을 리턴한다.
- } while (p < __setup_end);
- 파라메터 블럭의 끝까지 루프를 돈다.
init/main.c
const char *envp_init[MAX_INIT_ENVS+2] = { "HOME=/", "TERM=linux", NULL, };
init/main.c
static const char *argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };
set_init_arg()
init/main.c
/* Anything after -- gets handed straight to init. */ static int __init set_init_arg(char *param, char *val, const char *unused) { unsigned int i; if (panic_later) return 0; repair_env_string(param, val, unused); for (i = 0; argv_init[i]; i++) { if (i == MAX_INIT_ARGS) { panic_later = "init"; panic_param = param; return 0; } } argv_init[i] = param; return 0; }
- if (panic_later) return 0;
- panic_later가 지정된 경우 함수를 빠져나간다.
- repair_env_string(param, val, unused);
- param=val 및 param=”val”과 같은 형태는 ‘=’대신 파라메터 구분을 하기 위해 null이 입력되어 있는데 이를 다시 ‘=’문자로 치환하고 따옴표가 사용된 경우 param=val 형태가 되도록 따옴표를 제거한다.
- for (i = 0; argv_init[i]; i++) {
- argv_init[] 배열에 등록된 엔트리 수 만큼 루프를 돈다.
- if (i == MAX_INIT_ARGS) { panic_later = “init”; panic_param = param; return 0; }
- i가 MAX_INIT_ARGS에 도달하면 panic 관련 변수 설정을 하고 루틴을 빠져나온다.
- argv_init[i] = param;
- argv_init[] 배열에 param을 추가한다.
모듈(커널) 파라메터 등록
커널(모듈) 파라메터는 다음의 매크로로 등록한다.
- __setup()
- 구형(obs_kernel_param) 커널 파라메터를 사용함
- 참고: parse_early_param() | 문c
- core_param()
- module_param()
모듈 파라메터는 다음 두 가지의 방법 중 하나로 전달될 수 있다.
- 커널 cmdline
- 예) usbcore.blinkenlights=1
- modprobe 명령
- 예) $ modprobe usbcore blinkenlights=1
module_param()
include/linux/moduleparam.h
/** * module_param - typesafe helper for a module/cmdline parameter * @value: the variable to alter, and exposed parameter name. * @type: the type of the parameter * @perm: visibility in sysfs. * * @value 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 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 kparam_block_sysfs_write() 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, 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)
모듈(커널) 파라메터를 등록할 때 사용하는 매크로인다.
- module_param_unsafe() 함수를 사용하는 경우 커널에 위험성이 존재할 때 사용한다.
module_param_named()
include/linux/moduleparam.h
/** * 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)
타입 체크 후 module_param_cb()를 호출한다.
module_param_call()
include/linux/moduleparam.h
/* Obsolete - use module_param_cb() */ #define module_param_call(name, set, get, arg, perm) \ static struct kernel_param_ops __param_ops_##name = \ { .flags = 0, (void *)set, (void *)get }; \ __module_param_call(MODULE_PARAM_PREFIX, \ name, &__param_ops_##name, arg, \ (perm) + sizeof(__check_old_set_param(set))*0, -1, 0)
set, get 핸들러를 연결한 __param_ops_XXX 객체를 만들고 __module_param_call()을 호출한다.
- 예) module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy, NULL, 0644);
- static struct kernel_param_ops __param_ops_policy = { .flags = 0, (void *) pcie_aspm_set_policy, (void *) pcie_aspm_get_policy };
- __module_param_call(“pcie_aspm.”, policy, ¶m_ops_policy, NULL, 0644 + sizeof(__check_old_set_param(set))*0, -1, 0)
module_param_cb()
include/linux/moduleparam.h
/** * 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. * @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)
module_param_cb() 매크로 함수를 사용한 곳의 모듈명이 MODULE_PARAM_PREFIX에 담겨 빌드되고 이를 가지고 __module_param_call() 매크로를 호출한다.
- 예) module_param_cb(skip_txen_test, ¶m_ops_uint, &skip_txen_test, 0644);
- __modul_param_call(“8250_core”, skip_txen_test, ¶m_ops_uint, &skip_txen_test, 0644, -1, 0)
__module_param_call()
include/linux/moduleparam.h
/* This is the fundamental function for registering boot/module parameters. */ #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, ops, VERIFY_OCTAL_PERMISSIONS(perm), \ level, flags, { arg } }
모듈(커널) 파라메터 블럭에 파라메터와 핸들러들이 등록된다.
- 예) __modul_param_call(“8250_core”, skip_txen_test, ¶m_ops_uint, &skip_txen_test, 0644, -1, 0)
- static const char __param_str_skip_txen_test[] = “8250_core” skip_txen_test;
- static struct kernel_param const __param_skip_txen_test = { __param_str_skip_txen_test, ¶m_ops_uint, 0644, -1, 0, { &skip_txen_test } }
- __moduleparam_const
- ALPHA, IA64, PPC64 아키텍처는 아무일도 하지 않고 그 밖의 아키텍처는 const 이다.
구조체
kernel_param 구조체
struct kernel_param { const char *name; const struct kernel_param_ops *ops; u16 perm; s8 level; u8 flags; union { void *arg; const struct kparam_string *str; const struct kparam_array *arr; }; };
- flags
- KERNEL_PARAM_FL_UNSAFE(1)을 사용하는 경우 커널에 문제를 일으킬 수 있는 위험한 파라메터라는 것을 의미한다.
kernel_param_ops 구조체
include/linux/moduleparam.h
struct kernel_param_ops { /* How the ops should behave */ unsigned int flags; /* Returns 0, or -errno. arg is in kp->arg. */ int (*set)(const char *val, const struct kernel_param *kp); /* Returns length written or -errno. Buffer is 4k (ie. be short!) */ int (*get)(char *buffer, const struct kernel_param *kp); /* Optional function to free kp->arg when module unloaded. */ void (*free)(void *arg); };
- flags
- KERNEL_PARAM_OPS_FL_NOARG(1)을 사용하는 경우 value 값 없는 param값만 허용한다.
kparam_string 구조체
include/linux/moduleparam.h
/* Special one for strings we want to copy into */ struct kparam_string { unsigned int maxlen; char *string; };
kparam_array 구조체
include/linux/moduleparam.h
/* Special one for arrays */ struct kparam_array { unsigned int max; unsigned int elemsize; unsigned int *num; const struct kernel_param_ops *ops; void *elem; };
참고
- Earlycon & Earlyprintk
- parse_early_param() | 문c
- parse_args() | 문c – 현재 글
- Kernel Parameters | kernel.org
오류발견했어요 ㅎㅎ
그 외에 매치되지 않은 unknown 파라메터는 값이 없는 경우 envp_init[] 배열에 추가하고 값이 있는 경우 argv_init[] 배열에 추가한다.
->
그 외에 매치되지 않은 unknown 파라메터는 값이 있는 경우 envp_init[] 배열에 추가하고 값이 없는 경우 argv_init[] 배열에 추가한다.
안녕하세요? 문영일입니다.
아이쿠~~ 제가 거꾸로 표현했군요.
오류를 잡아주셔서 감사합니다.