GRUB(五)stage2.c註釋

接上一篇,最後調用的函數是cmain(),這個函數在stage2.c中定義。

在cmain()中主要做以下事情:

  1. 尋找啓動菜單配置文件。
  2. 如找不到則嘗試尋找程序中的內置菜單。
  3. 如果找到則嘗試解析並執行該啓動菜單配置中的腳本。
  4. 如兩者都沒有找到,則進入命令行模式。

主要函數如下


//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,通過對邏輯與和邏輯或的支持,在新版中就可以一行腳本中執行多條命令。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章