カーネルは、CMDLINE開始時刻、およびコンソールルートとしてこれらのパラメータに従って動作するように構成された解析します。
ブートローダは、このようなubootとして、カーネルによって渡されCMDLINE、カーネルはタグのリストを作るためにラムに渡されたパラメータ、最初のカーネルへのアドレスが必要になります、カーネルは情報などCMDLINEを得るために、タグを解析します。
タグを解析する方法をカーネルとカーネルに引数を渡すUboot以下のリンクで私のブログの別のものを見ることができます:
今日、分析するカーネルがCMDLINEを取得した後、コマンドラインパラメータを解析する方法です。
(終了する方法、起動方法、年代順に)私の考えによると、カーネルの下で登録された2種類のパラメータを初めて目。
最初の一般的なパラメータは、そのようなコンソール= ttyS0,115200ルート= / rdinit /ようなカーネルである INIT など。ここでは、たとえば、コンソールに。
第二は、あなたには、いくつかの起動が必要な場合は、変数パラメータ、ドライバを書き、それを必要とする各ドライバのカーネルパラメータの下にあります。開始時間カーネルコマンドラインパラメータで指定されたパラメータのパラメータ値を登録する)(module_paramドライバーに最後に加えることができます。
ここで(理由は、最近の仮想シリアルポートのデバッグUSBの...この例では、バイアスビットである)の例のようにパラメータドライバー/ USB /ガジェット/ serial.cをuse_acmします
一般的なカーネルパラメータ
これらの共通パラメータについては、カーネルの単一のデータ・セグメントを残して、セグメントを.ini.setupと呼ばれます。アーチ/腕/カーネル/ vmlinux.ldsで:
.init.data : {
*(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_star
. = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start =
__con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
__security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
. = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
}
__Setup_startは、セグメントの開始と終了__setup_endをinit.setup見ることができます。
.init.setupセグメント内の処理パラメータ及び一般的なカーネル関数に対応するマッピングテーブルに格納されています。ではでは/ Linuxの/ init.hが含まれます
これは__setup見およびマクロ定義は、構造、構造および.init.setup部に記憶された対応する格納されたパラメータハンドラをobs_kernel_param定義early_paramすることができます。
リンクのリンク順序でのマクロ定義のコール複数のファイルが.init.setupセグメントに入れて定義obs_kernel_paramに基づいて行われます場合は、想像してみてください。
例コンソールで、/kernel/printk.cに次のように
static int __init console_setup(char *str)
{
.......
}
__setup("console=", console_setup);
__setup宏定义展开,如下:
Static struct obs_kernel_param __setup_console_setup
__used_section(.init.setup) __attribute__((aligned((sizeof(long)))) = {
.name = “console=”,
.setup_func = console_setup,
.early = 0
}
.init.setupは、期間をコンパイルするに__Setup_console_setupはobs_kernel_param名カーネルランタイム段落で.init.setup CMDLINEと対比されますパラメータ名に応じて、リンクします。
マッチがコンソール設定パラメータを解析するために呼び出され、console_setupパラメータの値は、パラメータを解析する一般的なプロセスであるその背後のコンソールコマンドラインパラメータ、です。
2つのドライバカスタムパラメータ
カスタムパラメータドライバ、カーネル脇部rodataセグメント、次のようにアーチ/アーム/カーネル/ vmlinux.ldsで呼び出さ__paramセグメントに対して:
__param : AT(ADDR(__param) - 0) { __start___param = .; *(__param) __stop___param = .; }
セグメントは.rodataのセグメントに配置されています。
データの種類、それに格納されたセグメント?
次のようにマクロ定義を追跡する、パラメータを登録するために使用されるドライバmodule_paramは、それが最終的にある機能__paramオペレーティングセグメントを見つけます。
/* This is the fundamental function for registering boot/module
parameters. */
#define __module_param_call(prefix, name, ops, arg, perm, level) \
/* Default value instead of permissions? */ \
static int __param_perm_check_##name __attribute__((unused)) = \
BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)) \
+ BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN); \
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, perm, level, { arg } }
........
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
#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)
#define module_param_cb(name, ops, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1)
次のように、ドライバ/ USB /ガジェット/ serial.cのuse_acmには、例えば:
static bool use_acm = true;
module_param(use_acm, bool, 0);
Module_param展开到__module_param_call,如下:
Static bool use_acm = true;
Param_check_bool(use_acm, &(use_acm));
__module_param_call(MODULE_PARAM_PREFIX, use_acm, ¶m_ops_bool, &(use_acm, 0, -1));
__MODULE_PARAM_TYPE(use_acm, bool);
次のように__module_param_call開始は、あなたが見ることができ、構造kernel_paramの定義です:
Static struct kernel_param __moduleparam_const __param_use_acm
__used __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) = {
.name = MODULE_PARAM_PREFIX#use_acm,
.ops = ¶m_ops_bool,
.Perm=0,
.level = -1.
.arg = &use_acm
}
明らかに、.init.setupセグメントと同様に、リンクの順序カーネルリンク__paramセグメントで定義kernel_paramに従って配置されます。
Kernel_paramは注意するには、3つのメンバ変数があります。
(1)
OPS = param_ops_bool、次のようにkernel_param_ops構造が定義されています。
struct kernel_param_ops param_ops_bool = {
.set = param_set_bool,
.get = param_get_bool,
};
これらの2つのメンバ関数は、パラメータ値を設定および取得します
カーネル/ param.cでは、あなたは次のようにCHARP(文字列ポインタ)アレイにカーネルドライバのサポートデフォルトパラメータの種類ブールバイトの短いUSHORT int型のuint長いULONG文字列(文字列)を見ることができます。
デフォルトパラメータについてタイプ、パラメータのそれぞれのタイプを処理するために提供さparam.c kernel_param_opsを支持しました。
(2)
Arg =&use_acm、マクロ定義を展開するには、アドレスargの保存use_acmを見ることができます。パラメータ設定関数param_set_bool(CONSTチャー ヴァル、CONST構造体kernel_param KP)
すなわち、パラメータを渡すの目的を達するためにuse_acmの値を変更するには、ヴァルKP-> argのアドレスの値に設定します。
(3)
.nameの= MODULE_PARAM_PREFIX#use_acm、kernel_paramの名前を定義します。
で定義された非常に重要なMODULE_PARAM_PREFIXは、/ linuxの/ moduleparam.h中のものが含まれます。
* You can override this manually, but generally this should match the
module name. */
#ifdef MODULE
#define MODULE_PARAM_PREFIX /* empty */
#else
#define MODULE_PARAM_PREFIX KBUILD_MODNAME "."
#endif
我々は(モジュールを作る)モジュールをコンパイルした場合、MODULE_PARAM_PREFIXは空です。
場合insmodのg_serial.ko use_acm = 0として送信パラメータモジュール、パラメータ命名use_acm、
通常のコンパイルしたカーネル、モジュール名+のためMODULE_PARAM_PREFIX「」
我々は質量参加時のモジュールの名前は、あなたがあなた自身のプリンタドライバに追加することができますかわからない場合は、MODULE_PARAM_PREFIXは独自のドライブを決定するために、モジュールの名前をプリントアウト。
次のようにそこでここで、ドライバ/ USB /ガジェット/ Makefileのに応じて、serial.cカーネルを組み込みました:
g_serial-y := serial.o
....
obj-$(CONFIG_USB_G_SERIAL) += g_serial.o
最終的にg_serial.o、モジュール名g_serial.koを生成します。.nameの= g_serial.use_acm。
場合は、カーネル・パラメータの受け渡し、g_serial.use_acmという名前のパラメータ
このような処理は、カーネルのドライバの同じ名前の多くのパラメータの出現を防ぐことができます。
それは、デフォルトのカーネルタイプによってサポートされている場合、パラメータmodule_param登録のために、見られるように、カーネルパラメータは、処理機能を提供します。
サポートのタイプをカーネルでない場合は、param_ops ##種類を達成するために所有する必要があります。
このパラメータは、(うっかり見つけ...と少し偏った)内のドライバ/ビデオ/ uvesafb.cの登録を確認するためにスクロールすることができます。
パラメータの登録は、カーネルのコンパイラとリンカ(__paramにリンカ.init.setupや構造体の定義)で行われています
次は、カーネルの起動時に、着信コマンドラインパラメータを解析する方法を分析する必要があります。
解析CMDLINEの三つのカーネル
ボーエンが見つかっ前に私が書いたものによると、setup_arch解析タグ内start_kernelはでboot_command_lineにコピーし、コマンドラインパラメータを取得します。私たちは、その後、ダウンstart_kernel見えます。
setup_command_lineを呼び出し、コピーCMDLINEの2部はsaved_command_line static_command_lineを置きました。
以下のように、()以下parse_early_paramを呼び出します。
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
次のようにParse_early_paramは、最終コールparse_argsにtmp_cmdlineするコマンドラインパラメータをコピーします。
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
int parse_args(const char *name,
char *args,
const struct kernel_param *params,
unsigned num,
s16 min_level,
s16 max_level,
int (*unknown)(char *param, char *val))
{
char *param, *val;
pr_debug("Parsing ARGS: %s\n", args);
/* Chew leading spaces */
args = skip_spaces(args);
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ¶m, &val);
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, params, num,
min_level, max_level, unknown);
if (irq_was_disabled && !irqs_disabled()) {
printk(KERN_WARNING "parse_args(): option '%s' enabled "
"irq's!\n", param);
}
switch (ret) {
case -ENOENT:
printk(KERN_ERR "%s: Unknown parameter `%s'\n",
name, param);
return ret;
case -ENOSPC:
printk(KERN_ERR
"%s: `%s' too large for parameter `%s'\n",
name, val ?: "", param);
return ret;
case 0:
break;
default:
printk(KERN_ERR
"%s: `%s' invalid for parameter `%s'\n",
name, val ?: "", param);
return ret;
}
}
/* All parsed OK. */
return 0;
}
.....
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
Parse_argsすべてのパラメータのValコールのトラバースCMDLINE、スペースカットなどのパラメータを得るnext_arg PARAM名の取得パラメータおよびパラメータ値。コンソール= ttyS0,115200、PARAM =コンソール、ヴァル= ttyS0,115200。parse_oneを呼び出します。次のように:
static int parse_one(char *param,
char *val,
const struct kernel_param *params,
unsigned num_params,
s16 min_level,
s16 max_level,
int (*handle_unknown)(char *param, char *val))
{
unsigned int i;
int err;
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
if (params[i].level < min_level
|| params[i].level > max_level)
return 0;
/* No one handled NULL, so do it here. */
if (!val && params[i].ops->set != param_set_bool
&& params[i].ops->set != param_set_bint)
return -EINVAL;
pr_debug("They are equal! Calling %p\n",
params[i].ops->set);
mutex_lock(¶m_lock);
err = params[i].ops->set(val, ¶ms[i]);
mutex_unlock(¶m_lock);
return err;
}
}
if (handle_unknown) {
pr_debug("Unknown argument: calling %p\n", handle_unknown);
return handle_unknown(param, val);
}
pr_debug("Unknown argument `%s'\n", param);
return -ENOENT;
}
そのためparse_early_optionsからの着信num_params = 0、そうで直接行く最後handle_unknown機能をparse_one。この機能は、構文解析early_optionsのdo_early_paramによって渡されます。次のように:
static int __init do_early_param(char *param, char *val)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if ((p->early && parameq(param, p->str)) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
Do_early_param .init.setupトラバースセクション、可能な早期obs_kernel_paramがあれば、そこにコンソールまたはコマンドラインパラメータパラメータおよびobs_kernel_param HAVEセットアップルーチンは、引数を解析するために呼び出されますearlyconパラメータobs_kernel_param。
Do_early_paramは、解決のためのコマンドラインパラメータに高い優先度を持っています。私は例がアーチ/腕/カーネル/ early_printk.cです見つけるために次のカーネルのソースコードを回し、コマンドラインパラメータが早いコンソールのいずれかを登録するearlyprintk使用し、我々が興味を持っているを参照することができます。
あなたは、可能な限り早期のカーネルプリントアウトなどの便利なデバッグを開始したい場合は、STRのobs_kernel_paramのearlyconとして登録することができます。
情報は、通常の印刷のprintkであるように、そのセットアップパラメータハンドラregister_consoleでは、早期のコンソールを登録し、私はこの問題のためにカーネルの印刷機構の裏でこれを要約します。
do_early_paramは、(earlyprintk earlyconなど)CMDLINEカーネル関数を解析しているやっている、できるだけ早く設定する必要があります。
それはここで言うDo_early_param、機能は一般的なカーネルパラメータと、我々は多くの場合、カスタムパラメータを使用してドライバを扱っていません。読んでください。コードは以下の通りであります:
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL);
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption);
Parse_early_param後、start_kernelはparse_argsと呼ばれます。kernel_paramポインタとしてparse_early_paramコールparse_argsは異なりコールは、NULLである、むしろ.__ PARAMセグメント。
バックparse_args関数の上記パラメータ.__ PARAMセグメント開始アドレスパラメータ、numが数kernel_paramあります。
MIN_LEVEL、MAX_LEVEL都为-1.unknown = unknown_bootoption
Parse_argsは以前と同じように、コマンドラインパラメータ、スプリットを横断し、各パラメータのparse_oneを呼び出し、のparamヴァルに各パラメータを取得します。
Parse_one関数ソースを振り返ります:
(1)parse_one最初のセクションは、すべての.__のparam kernel_param、コントラストPARAM名とそのパラメータを通過する、同じ名前のメソッドは、パラメータ値を設定するために呼び出しkernel_param_ops kernel_paramメンバ変数が設定されています。
Lenovoのフロントドライバは、カスタムパラメータ例use_acmを話す、CMDLINEパラメータはg_serial.use_acm = 0、serial.cに登録次いでトラバーサル一致parse_oneの__param_use_acm、それによってuse_acm = 0を設定し、param_ops_boolセット関数を呼び出すを有しています。
(2)カーネルはparse_argsは、コンソール= ttyS0,115200として、parse_one共通パラメータを渡す場合。.__ PARAMセグメントトラバースparse_oneフロントは、マッチングkernel_paramを見つけることができません。私はhandle_unknown呼び出すために戻って行きました。Parse_argsはunknown_bootoptionに来て、次のように、コードは次のとおりです。
/*
* 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)
{
repair_env_string(param, val);
/* 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 = "Too many boot env vars at `%s'";
panic_param = param;
}
if (!strncmp(param, envp_init[i], val - param))
break;
}
envp_init[i] = param;
} else {</span>
<span style="font-size:14px;"> /* Command line option */
unsigned int i;
for (i = 0; argv_init[i]; i++) {
if (i == MAX_INIT_ARGS) {
panic_later = "Too many boot init vars at `%s'";
panic_param = param;
}
}
argv_init[i] = param;
}
return 0;
}
まずrepair_env_stringは、valはPARAM = valのフォームを再構成PARAMます。
、Obsolete_checksetup -init_setup obs_kernel_paramのすべてのセグメントを横断する場合param-> STRのparam試合、コールPARAM_>セットアップの設定パラメータ値を持ちます。
ここで注意すべきことの一つは、のparamのparam = valのフォームに自分自身を再形成するrepair_env_stringされます。マッチング後にトラバース代わりに「PARAM」の「= PARAM」に一致しています。
与えられた分析前の例と一般的なパラメータカーネル、__セットアップ(「コンソール=」、console_setup)。
コンソール= ttyS0,115200、フロントコンソールに一致obsolete_checksetup =、一致がスキップされた場合、コンソール=、ttyS0,115200値、設定されたパラメータ値を解析する特定の呼設定機能を取得しました。
考えられる、parse_one各parse_args CMDLINE .__ PARAMために送信され、パラメータが、対応するセットアップ機能または設定されたパラメータ値の分解能を呼び出すか、設定、-init.setupトラバースセクション、同じ名前またはSTRに一致しています。
Parse_argsエンドでStart_kernel、解析された上でカーネルCMDLINE!
分析の概要カーネルパラメータ:
(1)カーネルは、コンパイルと一緒に格納されたカーネル部.__ PARAM .init.setup所望マッピングパラメータ(ドライバおよび一般)と対応する処理機能(obs_kernel_param kernel_param構造)を使用してリンク。
(2)カーネルの開始、初期使用do_early_paramカーネルプロセスパラメータ(例えばearlyprintk earlycon)
マッチングが成功した(3)各パラメータCMDLINEトラバース__param .init.setupマッチング用parse_argsを、対応するハンドラを解析するために呼び出され、パラメータ値が設定されています。
また、次のカーネルを使用する多くの方法がありますが、このようなマッピングテーブルハンドラの、検討する価値があります。このようubootボーエンは、カーネルに引数を渡す前に、このように、異なるタグのカーネルハンドラもにマップされます。
カーネルドライバの下のコールバックハンドラプライベート構造もこれはすごいと思っています!