1. 程式人生 > >Linux核心 __setup巨集分析

Linux核心 __setup巨集分析

在解析cmdline時,我們經常會使用到__setup巨集,用來處理kernel的cmdline。

#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_##unique_id[] __initdata __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 }

現在我是用到讀取屏id。

__setup("lcd_id=", lcd_id_get);

將上面巨集展開就是:

__setup_param(“lcd_id=”, lcd_id_get, lcd_id_get, 0)

static char __setup_str_lcd_id_get[] __initdata __aligned(1) = “lcd_id=”; static struct obs_kernel_param __setup_lcd_id_get __used __section(.init.setup) attribute((aligned((sizeof(long))))) = { __setup_str_lcd_id_get, lcd_id_get, 0 }

把巨集裡面的修飾拿掉就是:

static char __setup_str_lcd_id_get[]  = "lcd_id=";
static struct obs_kernel_param __setup_lcd_id_get = { __setup_str_lcd_id_get, lcd_id_get, 0 }

這裡定義了兩個變數:一個字串__setup_str_lcd_id_get,一個obs_kernel_param結構,其結構的原型為:

@/kernel/include/linux/init.h
struct obs_kernel_param {
 const char *str;
 int (*setup_func)(char *);
 int early;
};

str元素為__setup_str_lcd_id_get, setup_func函式指標為lcd_id_get, early為0.

巨集裡面的__section(.init.setup)修飾,執行將__setup_lcd_id_get結構放到對應section。

#define INIT_SETUP(initsetup_align)					\
		. = ALIGN(initsetup_align);				\
	    VMLINUX_SYMBOL(__setup_start) = .;			\
		*(.init.setup)						\
		VMLINUX_SYMBOL(__setup_end) = .;

INIT_SETUP巨集將被vmlinux連線指令碼使用。

/kernel/arch/arm/kernel/vmlinux.lds.S
.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		SECURITY_INITCALL
		INIT_RAM_FS
	}

在編譯是,__setup()巨集將編譯進vmlinux中的.init.setup段中,這個段從__setup_start符號開始,在__setup_end,在核心啟動是如何取出呢。

在start_kernel可以看到:

start_kernel()
    setup_arch()
        parse_early_param()  // @/kernel/init
            strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); // 賦值cmdline
            parse_early_options(tmp_cmdline);
                parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
                    parse_one(param, val, doing, params, num, min_level, max_level,unknown);

其中parse_one用來解析指令,然後do_early_param

/* Check for early params. */
static int __init do_early_param(char *param, char *val, const char *unused)
{
	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)
				pr_warn("Malformed early option '%s'\n", param);
		}
	}
	/* We accept everything at this stage. */
	return 0;
}

do_early_param將取出符號 __setup_start到__setup_end之間的obs_kernel_param結構的變數,呼叫obs_kernel_param中設定的函式指標。

總結:

__setup巨集用來指導建立obs_kernel_param結構,並編譯到核心特定段中。在核心啟動時,將取出obs_kernel_param結構並執行其中的函式。