接上一篇,最後調用的函數是cmain(),這個函數在stage2.c中定義。
在cmain()中主要做以下事情:
- 尋找啓動菜單配置文件。
- 如找不到則嘗試尋找程序中的內置菜單。
- 如果找到則嘗試解析並執行該啓動菜單配置中的腳本。
- 如兩者都沒有找到,則進入命令行模式。
主要函數如下
//void
//reset (void);
static int config_len, menu_len, num_entries;
/*
menu_entries數組存放"title"命令的內容
config_entries數組既存放在menu中的命令也存放title下的命令,它們之間以兩個NULL分割
如:
timeout xxx(NULL)default /default(NULL)(NULL)
[title 0]命令1(NULL)命令2(NULL)(NULL)
[title 1]命令1(NULL)命令2(NULL)(NULL)
*/
static char *config_entries, *menu_entries, *cur_entry;
static void
reset(void)
{
count_lines = -1;
config_len = 0;
menu_len = 0;
num_entries = 0;
config_entries = (char *)mbi.drives_addr + mbi.drives_length;
cur_entry = config_entries;
menu_entries = (char *)MENU_BUF;
init_config();
}
/* 從cmdline中找到下一個詞語並返回它的指針,如果參數after_equal
不等於零, 則字符"="被假設爲一個空格看待. 注意:此假設是爲了向後兼容。*/
extern char * skip_to(int after_equal, char *cmdline);
/* This is the starting function in C. */
void
cmain(void)
{
char *kill_buf = (char *)KILL_BUF; /* #define KILL_BUF (CMDLINE_BUF + CMDLINE_BUFLEN) */
/* grub_setjmp見setjmp.S,這個函數保存棧的內容到restart_env以便將來重啓Stage 2的時候用到。 */
grub_setjmp(restart_env);
/* Initialize the kill buffer. */
*kill_buf = 0;
debug = 1;
/* Never return. */
for (;;)
{
extern int use_config_file;
int is_opened, is_preset;
reset();
/* Here load the configuration file. */
//#ifdef GRUB_UTIL
/* 如果啓用了配置文件則加載它(在啓動過程中如果按下了C鍵會跳過配置文件的加載進入命令行模式,詳見asm.S)*/
if (use_config_file)
//#endif /* GRUB_UTIL */
{
/* 存放默認文件名"/boot/grub/default"的地方.
#define DEFAULT_FILE_BUF (PASSWORD_BUF + PASSWORD_BUFLEN) */
char *default_file = (char *)DEFAULT_FILE_BUF;
int i;
/* Get a saved default entry if possible. */
saved_entryno = 0;
/* 當config_file不爲0時將讀取該處的字符串,否則讀取0x0800處。見asm.S*/
if (*config_file)
{
*default_file = 0; /* initialise default_file */
grub_strncat(default_file, config_file, DEFAULT_FILE_BUFLEN);
/* 截取字符串,丟棄最後一個'/'之後的內容*/
for (i = grub_strlen(default_file); i >= 0; i--)
if (default_file[i] == '/')
{
//i++;
break;
}
default_file[++i] = 0;
/* 然後 + "default" */
grub_strncat(default_file + i, "default", DEFAULT_FILE_BUFLEN - i);
if (debug)
grub_printf("Open %s ... ", default_file);
// i=grub_open (default_file);
// printf("default_file ok=%s\n", default_file);
// for (;;);
/* 打開文件讀取十個字節,然後將之從字符串轉爲數字,保存到saved_entryno。 */
if (grub_open(default_file)) /* grub_open見disk_io.c */
{
char buf[10]; /* This is good enough. */
char *p = buf;
int len;
if (debug)
grub_printf("Read file: ", default_file);
len = grub_read(buf, sizeof(buf));
if (debug)
grub_printf("len=%d\n", len);
if (len > 0)
{
buf[sizeof(buf) - 1] = 0;
safe_parse_maxint(&p, &saved_entryno); /*見char_io.c*/
}
grub_close();
}
else if (debug)
grub_printf("failure.\n", default_file);
}
errnum = ERR_NONE;
#ifdef GRUB_UTIL
do
#endif /* GRUB_UTIL */
{
/* STATE 0: 在title命令前.
STATE 1: 在一個title中.
STATE >1: 在一個title命令後的entry中. */
int state = 0, prev_config_len = 0, prev_menu_len = 0;
char *cmdline;
is_preset = is_opened = 0;
/* 首先嚐試獲取位於0x800處的預置命令行菜單. */
if (preset_menu == (char *)0x0800)
{
is_opened = is_preset = open_preset_menu();
}
/* 再嘗試打開config_file指定的文件 */
if (!is_opened)
{
/* Try config_file */
if (*config_file)
is_opened = grub_open(config_file);
}
errnum = ERR_NONE;
if (!is_opened)
{
/* Try the preset menu. This will succeed at most once,
* because close_preset_menu disables the preset menu. */
is_opened = is_preset = open_preset_menu();
}
/* 找不到預置菜單,也打不開config_file指定的文件,結束程序。*/
if (!is_opened)
break;
/* 重置命令行環境,這是必須的因爲菜單一定被覆蓋了。 */
reset();
/* 讀配置文件或者讀預置菜單(已加載到內存),並逐個解釋 */
cmdline = (char *)CMDLINE_BUF;
/* get_line_from_config函數讀一行到cmdline指向的緩衝區,忽略#開頭的註釋,並自動連接用反斜槓"\"連接的兩行。 */
while (get_line_from_config(cmdline, NEW_HEAPSIZE, !is_preset))
{
/*
(1) 關於builtin結構
// builtin的標誌位
#define BUILTIN_CMDLINE 0x1 /* 可在命令行中運行.
#define BUILTIN_MENU 0x2 /* 可在菜單中運行.
#define BUILTIN_TITLE 0x4 /* 僅用於title命令.
#define BUILTIN_SCRIPT 0x8 /* 可在腳本中運行.
#define BUILTIN_NO_ECHO 0x10 /* 啓動過程中不回顯命令
#define BUILTIN_HELP_LIST 0x20 /* Show help in listing.
// The table for a builtin.
struct builtin
{
char *name; // 命令名.
int(*func) (char *, int); // 回調函數
int flags; //上列標誌位的組合.
char *short_doc; // 文檔的簡短版本.
char *long_doc; // 文檔的長版。
};
// (2) 以下是一個menu.lst的示例
# utility under DOS/Win9x or Linux. 以#開頭的是註釋
color black/cyan yellow/cyan <== 這個命令不在"title"下,屬於在菜單中運行的命令
timeout 30
default /default
title find and load NTLDR of Windows NT/2K/XP <== "title"命令
fallback 1 <== 下面幾行是在title下運行的命令列表,直到下一個"title"爲止
find --set-root /ntldr
chainloader /ntldr
savedefault --wait=2
title find and load CMLDR, the Recovery Console of Windows NT/2K/XP <== "title"命令
fallback 2 <== 下面幾行是在title下運行的命令列表,直到下一個"title"爲止
find --set-root /cmldr
chainloader /cmldr
*/
struct builtin *builtin;
/* 找到參數對應的builtin結構體並返回其實例指針,如未找到則返回0,見command.c.。*/
builtin = find_command(cmdline);
errnum = 0;
if (!builtin)
/* Unknown command. Just skip now. */
continue;
/*
menu_entries數組存放title命令的內容
config_entries數組既存放在menu中的命令也存放title下的命令。
*/
if (builtin->flags & BUILTIN_TITLE)
{
char *ptr;
/* "title"命令特殊對待 */
if (state > 1) /*每次遇到title時state都被設置爲1,然後title下的每條指令都會使state++*/
{
/*
發現了下一個title.我們往config_entries數組中追加填入一個0(兩個零代表個命令塊的結束),
用於表示上一個title下的所有命令已經全部錄入了。 */
num_entries++;
config_entries[config_len++] = 0;
prev_menu_len = menu_len;
prev_config_len = config_len;
}
else
{
/* 發現了第一個title. */
menu_len = prev_menu_len;
config_len = prev_config_len;
}
/* 發現了titile則重設state標誌,便於處理下一個title命令。 */
state = 1;
// skip_to見cmdline.c,作用是返回指向參數cmdline下一個詞的指針,第一個參數如果爲1則表示"="當做空格看待。
ptr = skip_to(1, cmdline);
/* 拷貝這條title的內容到menu_entries數組中(不包含"title"這個詞)*/
while ((menu_entries[menu_len++] = *(ptr++)) != 0)
;
}
else if (!state)
{
/* 遇到title命令時state == 1,後續再遇到title下的命令時state ++。
所以當state == 0時表示當前命令運行在第一條title命令之前,那它
必須是在菜單中可執行的命令。(例如timeout命令) */
if (builtin->flags & BUILTIN_MENU)
{
#if 0
extern int commandline_func(char *arg, int flags);
char *arg = (builtin->func) == commandline_func ? cmdline : skip_to(1, cmdline);
(builtin->func) (arg, BUILTIN_MENU);
errnum = 0;
#endif
char *ptr = cmdline;
/* 拷貝這條在menu下中運行的命令到config_entries數組(包括NULL)。 */
while ((config_entries[config_len++] = *ptr++) != 0)
;
prev_config_len = config_len;
}
else
/* Ignored. */
continue;
}
else //state != 0 && 0 == builtin->flags & BUILTIN_TITLE
{
char *ptr = cmdline;
/* state == 1 意味着這條命令是第一個TITLE之下的命令, 而
num_entries == 0則意味着TITLE是也是第一個(再遇到title命令時num_entries遞增). */
if (num_entries == 0 && state == 1)
{
/* 往config_entries追加一個零以表示上一組命令到此結束 */
config_entries[config_len++] = 0;
}
state++;
/* 拷貝這行title下的命令到config_entries數組中,包括結尾的NULL. */
while ((config_entries[config_len++] = *ptr++) != 0)
;
}
} /* End while(get_line_from_config)*/
/* 這裏必須關閉文件,因爲menu相關的命令也可能會使用GRUB_OPEN命令 */
if (is_preset)
close_preset_menu();
else
grub_close();
/* 爲config_entries列表和menu_entries列表追加結束標記0表示一塊的結束. */
if (state > 1)
{
num_entries++;
config_entries[config_len++] = 0;
}
else// if (state)
{
menu_len = prev_menu_len;
config_len = prev_config_len;
}
//else
///* Finish the menu-specific commands. */
//config_entries[config_len++] = 0;
menu_entries[menu_len++] = 0;
config_entries[config_len++] = 0;
/* 將menu_entries續接到config_entries之後,並重置menu_entries指針的值*/
grub_memmove(config_entries + config_len, menu_entries,
menu_len);
menu_entries = config_entries + config_len;
/* 首先運行menu中可執行的命令 */
///* Run an entry from the script SCRIPT. HEAP is used for the
// command-line buffer. If an error occurs, return non-zero, otherwise
// return zero. */
//int
//run_script (char *script, char *heap)
{
char *old_entry;
/* heap緩衝區緊跟在menu_entries之後 */
char *heap = menu_entries + menu_len;
#if 1
/* 初始化 */
extern void init_cmdline(void);
init_cmdline();
#else
/* 下面這段被預編譯命令禁用了。 */
saved_drive = boot_drive; // 啓動磁盤(啓動設備)
saved_partition = install_partition; // 啓動分區
current_drive = GRUB_INVALID_DRIVE;
errnum = 0;
count_lines = -1;
/* Restore memory probe state. */
mbi.mem_upper = saved_mem_upper;
if (mbi.mmap_length)
mbi.flags |= MB_INFO_MEM_MAP;
/* 爲builtin命令初始化數據. */
init_builtins();
#endif
// 下面這個循環將逐條執行config_entries數組中所有可在menu中執行的命令。
while (1)
{
struct builtin *builtin;
char *arg;
// print_error ();
//
// if (errnum)
// {
// errnum = ERR_NONE;
//
// /* If a fallback entry is defined, don't prompt a user's
// intervention. */
// if (fallback_entryno < 0)
// {
// grub_printf ("\nPress any key to continue...");
// (void) getkey ();
// }
//
// break;//return 1;
// }
/* 從cur_entry拷貝第一條字符串到HEAP。
注:在reset()函數中設定cur_entry = config_entries;
*/
old_entry = cur_entry;
while (*cur_entry++)
;
grub_memmove(heap, old_entry, (int)cur_entry - (int)old_entry);
//printf("errnum=%d, heap=%s, old_entry=%s, cur_entry=%s\n", errnum, heap, old_entry, cur_entry);
/*讀到了兩個零,
在config_entries中前面幾條總是menu的命令且每條以0分割,
再其後又追加一個0表示menu所有命令的結束。。*/
if (!*heap)
{
/* 已經執行完了所有的menu命令. */
/* 如果內核尚未加載則退出執行menu命令的循環。 */
if (kernel_type == KERNEL_TYPE_NONE)
break;//return 0;
/* 否則執行boot命令 */
grub_memmove(heap, "boot", 5);
}
/* 命令行轉builtin. */
builtin = find_command(heap);
if (!builtin)
{
grub_printf("%s\n", old_entry);
continue;
}
if (!(builtin->flags & BUILTIN_NO_ECHO))
grub_printf("%s\n", old_entry);
/* 如果builtin不能在menu中執行則跳過它 */
if (!(builtin->flags & BUILTIN_MENU))
{
// errnum = ERR_UNRECOGNIZED;
continue;
}
#if 0
/* Invalidate the cache, because the user may exchange removable
disks. */
buf_drive = -1;
#endif
/* 執行這條builtin對應的回調函數 */
extern int commandline_func(char *arg1, int flags);
arg = (builtin->func) == commandline_func ? heap : skip_to(1, heap);
(builtin->func) (arg, BUILTIN_MENU);
//printf ("OK *old_entry=%x, %s\n", *old_entry, heap);
//for(i=0;i<0x10000000;i++);
if (!*old_entry)
break;
}
//config_entries = cur_entry; /* config file data begins here */
}
errnum = 0;
/* 已經執行完畢所有的menu命令,接下來需要展示所有title,如果用戶未選擇則在timeout時間到了之後執行默認選項*/
/* 確保所有的回退入口都是有效的,進入下一級菜單後會有個“回到上一級菜單”的入口。 */
if (fallback_entryno >= 0)
{
for (i = 0; i < MAX_FALLBACK_ENTRIES; i++)
{
if (fallback_entries[i] < 0)
break;
if (fallback_entries[i] >= num_entries)
{
grub_memmove(fallback_entries + i,
fallback_entries + i + 1,
((MAX_FALLBACK_ENTRIES - i - 1)
* sizeof(int)));
i--;
}
}
if (fallback_entries[0] < 0)
fallback_entryno = -1;
}
/* 檢查默認選項是否存在 */
if (default_entry >= num_entries)
{
// 如不存在則在“回退”入口存在時將默認設置爲設置爲“回退”
if (fallback_entryno >= 0)
{
default_entry = fallback_entries[0];
fallback_entryno++;
if (fallback_entryno >= MAX_FALLBACK_ENTRIES
|| fallback_entries[fallback_entryno] < 0)
fallback_entryno = -1;
}
else
default_entry = 0; /* 如均不存在則設爲0 */
}
}
#ifdef GRUB_UTIL
while (is_preset);
#endif /* GRUB_UTIL */
}
/*End if (use_config_file), 如果沒有配置文件則會直接進入下一行*/
debug = 0;
/* 繼續並保證終端已建立 */
if (current_term->startup)
(*current_term->startup)();
if (!num_entries)
{
/* 如果沒有可執行的配置文件,那麼進入命令行模式 */
enter_cmdline(config_entries, 1);
}
else
{
/* 執行menu接口 */
run_menu(menu_entries, cur_entry, num_entries,
menu_entries + menu_len, default_entry);
}
} //END for (;;)
}
在cmain的最後,如果沒有菜單或者菜單是無效的,則調用enter_cmdline進入命令行模式,否則會調用run_menu()函數打印前面中解析出來的各個菜單項,如果設置了倒計時還會顯示倒計時信息。也就是以下內容:
Use the ↑ and ↓ keys to select which entry is highlighted.
Press enter to boot the selected OS, \'e\' to edit the
commands before booting, or \'c\' for a command-line.
啓動菜單項1
啓動菜單項2
……
啓動菜單項N
The highlighted entry will be booted automatically in %d seconds.
run_menu除了顯示這些信息外,還響應用戶輸入,例如選擇菜單項,編輯菜單項以及進入命令行模式等。如果用戶在選擇的菜單項上敲下回車鍵,這個函數會定位到這個菜單項下的命令列表(腳本列表)然後調用run_script逐個執行。run_script很簡單,它只是從命令行清單中逐個執行該命令對應函數,注意在舊版grub4dos 0.4.2中這個函數放在cmdline.c且不支持&&和||邏輯操作,所以只能一行一個命令,而在0.4.5中放在了stage2.c,通過對邏輯與和邏輯或的支持,在新版中就可以一行腳本中執行多條命令。