cmdline parameter parsing principle linux kernel analysis [turn]

Kernel parses cmdline start time, and configured to operate in accordance with these parameters as console root.

Cmdline the bootloader is passed by the kernel, such as the uboot, the kernel will require parameters passed on the ram to make a list of tags, the address to the first kernel, kernel parses tags to obtain information such cmdline.

Uboot pass arguments to the kernel and kernel how to parse tags can see another one of my blog at the following link:

Today kernel to be analyzed is how to parse the cmdline after obtaining cmdline.

According to my ideas (in chronological order, how to start, how to end), first look at two kinds of parameters registered under the kernel.
The first general parameter is the kernel, such as console = ttyS0,115200 root = / rdinit / init like. Here to console, for example.
The second is under kernel parameters of each driver in need, write driver, the variable parameters if you need some startup. Can be added last in module_param Driver () to register a parameter value of the parameter specified by the start time kernel cmdline.

Here to use_acm parameters drivers / usb / gadget / serial.c as an example (this example is a bit biased .. because of the recent virtual serial port debugging usb)

A common kernel parameters

For these common parameters, kernel leaving a single data segment, called .ini.setup segment. In arch / arm / kernel / vmlinux.lds in:

.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 can see init.setup segment starting and ending __setup_end.

In .init.setup segment is stored in the mapping table corresponding to the processing parameters and general kernel functions. In include / linux / init.h in

It can be seen __setup and early_param macro definition defines obs_kernel_param structure, the structure and the corresponding stored parameters handler stored in the .init.setup section.

Imagine, if the macro definition call multiple files at link link order will be based on the definition obs_kernel_param put in .init.setup segment.

In an example console, in /kernel/printk.c as follows:

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
}

__Setup_console_setup will link into .init.setup compile time period, according to the parameter name will contrast with .init.setup cmdline in paragraph obs_kernel_param the name kernel runtime.

Match is invoked to parse the console-setup parameters, console_setup parameter value is the console cmdline, behind which is the general process of parsing the parameters.

Two driver custom parameters

For custom parameters driver, kernel aside part rodata segment, called __param segments in arch / arm / kernel / vmlinux.lds as follows:

__param : AT(ADDR(__param) - 0) { __start___param = .; *(__param) __stop___param = .; }

The segment is placed in .rodata segment.

The segment that is stored in what kind of data it?

Driver module_param used to register parameters, tracking the macro definition, it will eventually find function __param operating segment are as follows:

/* 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)

At driver / usb / gadget / serial.c use_acm in, for example, as follows:

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);

The __module_param_call start, you can see is the definition of a structure kernel_param, as follows:

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
}

Clearly, like .init.setup segment, will be placed according to the order of the links kernel_param defined at kernel link __param segment.

Kernel_param has three member variables to note:

(1)

ops = param_ops_bool, kernel_param_ops structure is defined as follows:

struct kernel_param_ops param_ops_bool = {
    .set = param_set_bool,
    .get = param_get_bool,
};

These two member functions are to set and retrieve the parameter values

In kernel / param.c you can see the kernel driver support default parameter types bool byte short ushort int uint long ulong string (character string) in CHARP (string pointer) Array like.

For the default parameters supported type, param.c kernel_param_ops provided to handle respective types of parameters.

(2)

Arg = & use_acm, expand the macro definition, you can see the address arg stored use_acm. Parameter setting function param_set_bool (const char  Val, const struct kernel_param  KP)

Set to the value of the val kp-> arg address, i.e. to change the value of use_acm to reach the purpose of passing parameters.

(3)

.name = MODULE_PARAM_PREFIX # use_acm, defines the kernel_param's name.

MODULE_PARAM_PREFIX very important, defined in include / linux / moduleparam.h in:

* 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

If we compile modules (make modules), the MODULE_PARAM_PREFIX is empty.

When transmission parameter module, the parameter named use_acm, as insmod g_serial.ko use_acm = 0

Normal compiled kernel, MODULE_PARAM_PREFIX for the module name + "."

If we do not know what the name of the module at the time of mass participation, you can add in your own print driver, the MODULE_PARAM_PREFIX print out the name of the module to determine its own drive.

So here incorporated serial.c kernel, according to the driver / usb / gadget / Makefile, as follows:

g_serial-y          := serial.o
....
obj-$(CONFIG_USB_G_SERIAL)  += g_serial.o

Ultimately generating g_serial.o, module name g_serial.ko. .name = g_serial.use_acm.

When the kernel parameter passing, the parameter named g_serial.use_acm

Such treatment prevents the emergence of many parameters of the same name of the driver at kernel.

As can be seen, for parameters module_param registration, if it is supported by the default kernel type, kernel parameters will provide processing functions.

If not kernel support type, you need to own to achieve the param_ops ## type.

This parameter can scroll to see the registration of drivers / video / uvesafb.c in (and a little biased ... inadvertently found).

 

Parameter registration is done at the kernel compiler and linker (linker .init.setup or definition of the structure into the __param)

Next you need to analyze how to analyze incoming cmdline when the kernel starts.

Three kernel of parsing cmdline

According to what I wrote before Bowen found, start_kernel in setup_arch parse tags get cmdline, copied to boot_command_line in. We then look start_kernel down.

Call setup_command_line, 2 parts of the copy cmdline placed saved_command_line static_command_line.

Call parse_early_param below (), as follows:

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 copy cmdline to tmp_cmdline in a final call parse_args, as follows:

/* 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 traverse cmdline, obtaining parameters as spaces cut, next_arg param name acquisition parameters and parameter values ​​for all parameters val calls. The console = ttyS0,115200, the param = console, val = ttyS0,115200. Call parse_one. as follows:

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;
}

Because from parse_early_options incoming num_params = 0, so go directly parse_one last handle_unknown function. This function is passed by the parse-early_options do_early_param. as follows:

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 traverse section, if there is a possible early obs_kernel_param, there console or cmdline parameters and obs_kernel_param have earlycon parameters obs_kernel_param the setup routines will be called to parse the arguments.

Do_early_param have a higher priority in cmdline parameters for resolution. I turned the next kernel source code to find an example is the arch / arm / kernel / early_printk.c, use cmdline parameters earlyprintk to register one of the earliest console, we are interested can refer to.

If you want to start as early as possible kernel printout, convenient debugging, you can register as obs_kernel_param earlycon of str.

In its setup parameter handler register_console, registering an early console, so that information is printk normal printing, I will summarize this in the back of a kernel printing mechanism for this issue.

do_early_param is doing is parsing cmdline kernel functions (such as earlyprintk earlycon) needs to be configured as soon as possible.

Do_early_param it says here, the function does not deal with generic kernel parameters and driver we often use custom parameters. Read on. code show as below:

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);

After Parse_early_param, start_kernel called parse_args. The call, unlike parse_early_param call parse_args as kernel_param pointer is NULL, but rather a .__ param segment.

Back above of parse_args function, params parameter .__ param segment start address, num is the number kernel_param.

Min_level,max_level都为-1.unknown=unknown_bootoption

Parse_args still as before, traversing cmdline, split and get param val each parameter, call parse_one for each parameter.

Looking back at Parse_one function Source:

(1) parse_one first section goes through all the .__ param kernel_param, the contrast param name and parameters thereof, the method of the same name is set kernel_param member variable kernel_param_ops calls to set the parameter value.

Lenovo front driver speaks custom parameter example use_acm, cmdline parameters have g_serial.use_acm = 0, then the traversal match parse_one __param_use_acm registered in serial.c, call param_ops_bool set function, thereby setting use_acm = 0.

(2) If the kernel is parse_args pass parse_one common parameters, such as the console = ttyS0,115200. The parse_one front traverse .__ param segment will not find a matching kernel_param. I went back to call handle_unknown. Parse_args is coming unknown_bootoption, code is as follows: 

/*
 * 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;
}

First repair_env_string will param val reassembles param = val form.

Traversing all segments of the Obsolete_checksetup -init_setup obs_kernel_param, if param-> str param with a match, the call param_> setup configuration parameter values.

One thing to note here is repair_env_string will re-form themselves into a param param = val form. Traversed after matching are matching "param =" instead of "param".

The general parameter kernel prior to analysis examples given, __ setup ( "console =", console_setup).

Console = ttyS0,115200, obsolete_checksetup matching front console =, if the match is skipped console =, ttyS0,115200 acquired value, the specific call setup function to parse the set parameter values.

Conceivable, parse_one transmitted for each parse_args cmdline .__ param and the parameters are -init.setup traverse section, the matching to the same name or str, then call or set the corresponding setup function or set parameter value resolution.

Start_kernel in Parse_args end, kernel cmdline on the parsed!

 

Summary kernel parameters of analysis:

(. 1) kernel compiled and linked using the kernel section .__ param .init.setup desired mapping parameters (driver and general) and the corresponding processing function (obs_kernel_param kernel_param structure) stored together.

(2) Kernel start, do_early_param kernel process parameters (e.g. earlyprintk earlycon) using early

(3) parse_args for each parameter cmdline traverse __param .init.setup matching, the matching is successful, the corresponding handler is invoked to parse and set parameter values.

It is also worth considering, for such a mapping table handlers There are many ways to use the next kernel. Before such uboot Bowen pass arguments to the kernel, kernel handler for different tags in this manner is also mapped to.

Callback handler private structure under kernel driver also has this thought wow!

Guess you like

Origin www.cnblogs.com/linhaostudy/p/11716931.html