目录
menuconfig原理详解
参考博客menuconfig原理分析
1、menuconfig内核解析过程
menuconfig的解析主要依赖scripts/目录下的kconfig:
(1)mconf.c文件的过程
该文件是一个包含main函数的c文件,主要作用是解析内核配置文件并生产.config文件,5.12.1内核版本的main函数实现如下,内核在这部分的变化很少:
int main(int ac, char **av)
{
char *mode;
int res;
signal(SIGINT, sig_handler);
if (ac > 1 && strcmp(av[1], "-s") == 0) {
silent = 1;
/* Silence conf_read() until the real callback is set up */
conf_set_message_callback(NULL);
av++;
}
conf_parse(av[1]);//解析配置参数,该参数实质上就是Kconfig,是由scripts/kconfig/Makefile文件中传入,该函数也是menuconfig的核心函数
conf_read(NULL);
mode = getenv("MENUCONFIG_MODE");
if (mode) {
if (!strcasecmp(mode, "single_menu"))
single_menu_mode = 1;
}
if (init_dialog(NULL)) {//这里实质上是调用curses库函数,对应的第三方库为 ncurses-dev,是一种图形化库
fprintf(stderr, "Your display is too small to run Menuconfig!\n");
fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
return 1;
}
set_config_filename(conf_get_configname());//将配置文件(经过图形化处理后的)名称存放在全局变量中
conf_set_message_callback(conf_message_callback);//设置配置文件msg处理回调函数,该函数主要是格式化处理配置文件。
do {
conf(&rootmenu, NULL);//真正意义上的设置配置的过程,实质上就是讲配置文件的内容解析到struct menu结构体变量中,并记录下图形化处理过的内容,这个内容以一种双向链表的形式存在。
res = handle_exit();
} while (res == KEY_ESC);
return res;
}
(2)conf_parse对Kconfig的解析过程
该函数在内核中的实现有变化,在5.12.1版本,该函数定义在scripts/kconfig/parser.y中,4.19.125版本定义在zconf.y中,4.4.58版本定义在zconf.tab.c中,详细实现如下:
void conf_parse(const char *name)
{
struct symbol *sym;
int i;
zconf_initscan(name);//Kconfig文件的初始化扫描
_menu_init();//初始化struct menu全局变量
if (getenv("ZCONF_DEBUG"))
yydebug = 1;
yyparse();//在老一点的内核版本中使用的是zconfparse函数解析
/* Variables are expanded in the parse phase. We can free them here. */
variable_all_del();
if (yynerrs)
exit(1);
if (!modules_sym)
modules_sym = sym_find( "n" );
if (!menu_has_prompt(&rootmenu)) {
current_entry = &rootmenu;
menu_add_prompt(P_MENU, "Main menu", NULL);
}
menu_finalize(&rootmenu);
for_all_symbols(i, sym) {
if (sym_check_deps(sym))
yynerrs++;
}
if (yynerrs)
exit(1);
sym_set_change_count(1);
}
(3)conf函数实现内核配置
这里直接贴代码,不再详述,主要作用就是解析出从Kconfig读取的内容,并逐个记录到menu队列中,在解析玩配置文件以及从menuconfig图形界面读取到修改之后,会读取图形界面的各种选择,判断是否保存修改,并提示通过ESC推出界面,这里主要通过conf_get_changed来判断是否在图形界面有修改(与原来的.config比较,如果没有.config文件,则直接保存配置),在保存文件之后,结束图形界面,同事讲配置内容写到.config中。
static void conf(struct menu *menu, struct menu *active_menu)
{
struct menu *submenu;
const char *prompt = menu_get_prompt(menu);
struct subtitle_part stpart;
struct symbol *sym;
int res;
int s_scroll = 0;
if (menu != &rootmenu)
stpart.text = menu_get_prompt(menu);
else
stpart.text = NULL;
list_add_tail(&stpart.entries, &trail);//加入到队尾
while (1) {
item_reset();
current_menu = menu;
build_conf(menu);
if (!child_count)
break;
set_subtitle();
dialog_clear();
res = dialog_menu(prompt ? prompt : "Main Menu",
menu_instructions,
active_menu, &s_scroll);
if (res == 1 || res == KEY_ESC || res == -ERRDISPLAYTOOSMALL)
break;
if (item_count() != 0) {
if (!item_activate_selected())
continue;
if (!item_tag())
continue;
}
submenu = item_data();
active_menu = item_data();
if (submenu)
sym = submenu->sym;
else
sym = NULL;
switch (res) {
case 0:
switch (item_tag()) {
case 'm':
if (single_menu_mode)
submenu->data = (void *) (long) !submenu->data;
else
conf(submenu, NULL);
break;
case 't':
if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)
conf_choice(submenu);
else if (submenu->prompt->type == P_MENU)
conf(submenu, NULL);
break;
case 's':
conf_string(submenu);
break;
}
break;
case 2:
if (sym)
show_help(submenu);
else {
reset_subtitle();
show_helptext("README", mconf_readme);
}
break;
case 3:
reset_subtitle();
conf_save();
break;
case 4:
reset_subtitle();
conf_load();
break;
case 5:
if (item_is_tag('t')) {
if (sym_set_tristate_value(sym, yes))
break;
if (sym_set_tristate_value(sym, mod))
show_textbox(NULL, setmod_text, 6, 74);
}
break;
case 6:
if (item_is_tag('t'))
sym_set_tristate_value(sym, no);
break;
case 7:
if (item_is_tag('t'))
sym_set_tristate_value(sym, mod);
break;
case 8:
if (item_is_tag('t'))
sym_toggle_tristate_value(sym);
else if (item_is_tag('m'))
conf(submenu, NULL);
break;
case 9:
search_conf();
break;
case 10:
show_all_options = !show_all_options;
break;
}
}
list_del(trail.prev);
}
//保存配置文件并退出图形界面
static int handle_exit(void)
{
int res;
save_and_exit = 1;
reset_subtitle();
dialog_clear();
if (conf_get_changed())
res = dialog_yesno(NULL,
"Do you wish to save your new configuration?\n"
"(Press <ESC><ESC> to continue kernel configuration.)",
6, 60);
else
res = -1;
end_dialog(saved_x, saved_y);
switch (res) {
case 0:
if (conf_write(filename)) {
fprintf(stderr, "\n\n"
"Error while writing of the configuration.\n"
"Your configuration changes were NOT saved."
"\n\n");
return 1;
}
conf_write_autoconf(0);
/* fall through */
case -1:
if (!silent)
printf("\n\n"
"*** End of the configuration.\n"
"*** Execute 'make' to start the build or try 'make help'."
"\n\n");
res = 0;
break;
default:
if (!silent)
fprintf(stderr, "\n\n"
"Your configuration changes were NOT saved."
"\n\n");
if (res != KEY_ESC)
res = 0;
}
return res;
}
2、内核配置文件依赖
配置文件主要包括下面部分的解析:
config-prog := conf
menuconfig-prog := mconf //这里主要解析的是Kconfig
nconfig-prog := nconf
gconfig-prog := gconf
xconfig-prog := qconf
而内核最终的配置文件(.config)主要来源于Kconfig解析、arch/$ARCH/configs下的配置文件读取。
内核根目录新增配置
如果想新增一个原始内核不存在的一级配置,只需要在内核根目录下的Kconfig添加(source “…”)新增一级配置的Kconifg,若要能够成功编译新增模块,只需要改写Makefile即可。