最近在龙芯平台调研Grub的实现,在这过程中仔细研究了Grub的代码,就做个总结吧!
首先Grub在uefi上也是以一个Grub.efi的方式运行的,但是这个efi是放在操作系统下面的boot/EFI/下面的,uefi在运行过程总后期加载Grub的时候会去这个目录load这个Grub模块。load到内存之后,就开始执行。这里面不做详细的介绍是如何load以及如何运行的,这部分内容都在uefi中有源码,想了解的可以去看uefi的源码。下面就从load内存之后所执行的第一个函数开始说起。注意这里面一些不重要的函数就不细说了,只介绍重点。
代码路径位于grub/grub-core/kern/main.c中,详细代码如下
grub_main (void)
{
/* First of all, initialize the machine. */
grub_machine_init ();
grub_boot_time ("After machine init.");
/* Hello. */
grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
grub_printf ("Welcome to GRUB!\n\n");
grub_setcolorstate (GRUB_TERM_COLOR_STANDARD);
grub_load_config ();
grub_boot_time ("Before loading embedded modules.");
/* Load pre-loaded modules and free the space. */
grub_register_exported_symbols ();
#ifdef GRUB_LINKER_HAVE_INIT
grub_arch_dl_init_linker ();
#endif
grub_load_modules ();
grub_boot_time ("After loading embedded modules.");
/* It is better to set the root device as soon as possible,
for convenience. */
grub_set_prefix_and_root ();
grub_env_export ("root");
grub_env_export ("prefix");
/* Reclaim space used for modules. */
reclaim_module_space ();
grub_boot_time ("After reclaiming module space.");
grub_register_core_commands ();
grub_boot_time ("Before execution of embedded config.");
if (load_config)
grub_parser_execute (load_config);
grub_boot_time ("After execution of embedded config. Attempt to go to normal mode");
grub_load_normal_mode ();
grub_rescue_run ();
}
上面这个函数就是grub开始执行的主函数。整个grub的功能可以说基本上都是在这个函数中完成的。下面看一下主要的几个函数。
这个函数是和体系架构相关的,在Arm、MIPS、x86上的实现都不同,这个函数主要的作用就是初始化在本平台上需要做的必要的操作,例如获取后面grub要执行的每个moudule的基地址,初始化控制台,初始化内存管理系统,grub内部有自己的一套机制来管理内存。社会看门狗,挂载硬盘设备等操作都是在这个函数内做的。
下面的代码是MIPS上龙芯平台上的实现:
void
grub_machine_init (void)
{
grub_efi_init ();
if (grub_efi_is_loongson ())
grub_efi_loongson_init ();
else
/* FIXME: Get cpuclock from EFI. */
grub_timer_init (1000000000U);
}
void
grub_efi_init (void)
{
grub_modbase = grub_efi_modules_addr ();
/* First of all, initialize the console so that GRUB can display
messages. */
grub_console_init ();
/* Initialize the memory management system. */
grub_efi_mm_init ();
efi_call_4 (grub_efi_system_table->boot_services->set_watchdog_timer,
__ 0, 0, 0, NULL);
grub_efidisk_init ();
}
void
grub_efi_loongson_init (void)
{
grub_efi_loongson_smbios_table *smbios_table;
grub_efi_loongson_cpu_info *cpu_info;
smbios_table = grub_efi_loongson_get_smbios_table ();
if (!smbios_table)
grub_fatal ("cannot found Loongson SMBIOS!");
cpu_info = (grub_efi_loongson_cpu_info *) smbios_table->lp.cpu_offset;
grub_dprintf ("loongson", "cpu clock %u\n", cpu_info->cpu_clock_freq);
grub_timer_init (cpu_info->cpu_clock_freq);
grub_efi_loongson_alloc_boot_params ();
}
从上面的代码可以看出,grub_efi_init ()是通用的函数,就是完成上面刚刚提到的那些功能的函数。函数实现这里不再仔细讲解了。龙芯平台主要的就是下面的grub_efi_loongson_init (),只要上面的条件grub_efi_is_loongson ()为真,就会执行下面的函数,当然在MIPS平台这个条件肯定是为真的。在grub_efi_loongson_init ()函数主要的作用就是获取了龙芯内核和固件传参结构中的SMBIOS的那个表,然后通过函数grub_efi_loongson_alloc_boot_params ();为后面需要传递的整个参数申请内存空间,来存放这数据。下面是grub_efi_loongson_alloc_boot_params ();的函数实现:
void
grub_efi_loongson_alloc_boot_params (void)
{
grub_efi_memory_descriptor_t *mmap_buf;
grub_efi_memory_descriptor_t *mmap_end;
grub_efi_memory_descriptor_t *desc;
grub_efi_uintn_t mmap_size;
grub_efi_uintn_t desc_size;
grub_efi_physical_address_t address;
grub_efi_allocate_type_t type;
grub_efi_uintn_t pages;
grub_efi_status_t status;
grub_efi_boot_services_t *b;
int mm_status;
type = GRUB_EFI_ALLOCATE_ADDRESS;
pages = BYTES_TO_PAGES (loongson_boot_params_size + loongson_reset_code_size);
mmap_size = (1 << 12);
mmap_buf = grub_malloc (mmap_size);
if (!mmap_buf)
grub_fatal ("out of memory!");
mm_status = grub_efi_get_memory_map (&mmap_size, mmap_buf, 0, &desc_size, 0);
if (mm_status == 0)
{
grub_free (mmap_buf);
mmap_size += desc_size * 32;
mmap_buf = grub_malloc (mmap_size);
if (!mmap_buf)
grub_fatal ("out of memory!");
mm_status = grub_efi_get_memory_map (&mmap_size, mmap_buf, 0, &desc_size, 0);
}
if (mm_status < 0)
grub_fatal ("cannot get memory map!");
mmap_end = ADD_MEMORY_DESCRIPTOR (mmap_buf, mmap_size);
for (desc = SUB_MEMORY_DESCRIPTOR (mmap_end, desc_size);
desc >= mmap_buf;
desc = SUB_MEMORY_DESCRIPTOR (desc, desc_size))
{
if (desc->type != GRUB_EFI_CONVENTIONAL_MEMORY)
continue;
if (desc->physical_start >= GRUB_EFI_MAX_USABLE_ADDRESS)
continue;
if (desc->num_pages < pages)
continue;
address = desc->physical_start;
break;
}
grub_free (mmap_buf);
b = grub_efi_system_table->boot_services;
status = efi_call_4 (b->allocate_pages, type, GRUB_EFI_RUNTIME_SERVICES_DATA, pages, &address);
if (status != GRUB_EFI_SUCCESS)
grub_fatal ("cannot allocate Loongson boot parameters!");
loongson_boot_params = (void *) ((grub_addr_t) address);
}
这个函数的主要作用就是获取当前BIOS下内存的使用情况,然后申请一段GRUB_EFI_RUNTIME_SERVICES_DATA阶段的内存来存放这些数据。我们知道这个类型的数据是在操作系统运行起来后,不会回收的一段内存,这就是这个内存操作系统是不会使用的。然后申请之后的内存地址赋值给了全局指针变量 loongson_boot_params,这个变量包含了龙芯平台所有BIOS下面需要传给内核的参数。其结构这里不做详细的介绍了。
下面这几个函数都是和架构无关的grub的公共函数,主要作用就是完成Grub的基本配置。这里不做详细的介绍,关系的可以自己去看代码。
grub_boot_time ("After machine init.");
grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
grub_printf ("Welcome to GRUB!\n\n");
grub_setcolorstate (GRUB_TERM_COLOR_STANDARD);
这个函数代码如下:
static void
grub_load_config (void)
{
struct grub_module_header *header;
FOR_MODULES (header)
{
/* Not an embedded config, skip. */
if (header->type != OBJ_TYPE_CONFIG)
continue;
load_config = grub_malloc (header->size - sizeof (struct grub_module_header) + 1);
if (!load_config)
{
__grub_print_error ();
__break;
}
grub_memcpy (load_config, (char *) header +
____ sizeof (struct grub_module_header),
____ header->size - sizeof (struct grub_module_header));
load_config[header->size - sizeof (struct grub_module_header)] = 0;
break;
}
}
这个函数的作用就是找到后面要执行的其moudule类型为OBJ_TYPE_CONFIG的moudule,然后将这个moudule拷贝到这里申请的一段内存中,这个类型的moudule具体是做什么的,现在我还没弄明白,了解的读者,可以下面评论。
这个函数就是去执行Grub中的命令对应的moudule,的入口函数。每一个moudule函数是通过GRUB_MOD_INIT(linux)类似与内核编译的形式编译成每一个section中。然后当运行的时候,就运行对应的这里编译的代码,这里面都是注册的命令的代码,这里面的命令包含Linux、initrd两个命令。这两个命令是在grub.cfg中使用的。当运行这两个命令的时候,就会调用这里面注册的命令的回调函数去运行。这里面只是去注册对应的命令,最后通过boot命令来真正的运行内核。linux命令就是去加载内核,initrd就是其加载initrd。所以grub_load_modules()的作用就是去调用注册的命令的函数,为后面使用的命令做准备。
grub_load_modules()代码如下:
static void
grub_load_modules (void)
{
struct grub_module_header *header;
FOR_MODULES (header)
{
/* Not an ELF module, skip. */
if (header->type != OBJ_TYPE_ELF)
continue;
if (! grub_dl_load_core ((char *) header + sizeof (struct grub_module_header),
______ (header->size - sizeof (struct grub_module_header))))
grub_fatal ("%s", grub_errmsg);
if (grub_errno)
grub_print_error ();
}
}
grub_dl_t
grub_dl_load_core (void *addr, grub_size_t size)
{
grub_dl_t mod;
grub_boot_time ("Parsing module");
mod = grub_dl_load_core_noinit (addr, size);
if (!mod)
return NULL;
grub_boot_time ("Initing module %s", mod->name);
grub_dl_init (mod);
grub_boot_time ("Module %s inited", mod->name);
return mod;
}
static inline void
grub_dl_init (grub_dl_t mod)
{
if (mod->init)
(mod->init) (mod);
mod->next = grub_dl_head;
grub_dl_head = mod;
}
这里面 (mod->init) (mod);这行代码才是真正去执行上面提到的GRUB_MOD_INIT(linux)函数。
下面看一下linux 和initrd命令是如何注册的
其代码如下:
GRUB_MOD_INIT(linux)
{
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
________ 0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
________ 0, N_("Load initrd."));
my_mod = mod;
}
static inline grub_command_t
grub_register_command (const char *name,
____ grub_command_func_t func,
____ const char *summary,
____ const char *description)
{
return grub_register_command_prio (name, func, summary, description, 0);
}
grub_command_t grub_command_list;
grub_command_t
grub_register_command_prio (const char *name,
______ grub_command_func_t func,
______ const char *summary,
______ const char *description,
______ int prio)
{
grub_command_t cmd;
int inactive = 0;
grub_command_t *p, q;
cmd = (grub_command_t) grub_zalloc (sizeof (*cmd));
if (! cmd)
return 0;
cmd->name = name;
cmd->func = func;
cmd->summary = (summary) ? summary : "";
cmd->description = description;
cmd->flags = 0;
cmd->prio = prio;
for (p = &grub_command_list, q = *p; q; p = &(q->next), q = q->next)
{
int r;
r = grub_strcmp (cmd->name, q->name);
if (r < 0)
__break;
if (r > 0)
__continue;
if (cmd->prio >= (q->prio & GRUB_COMMAND_PRIO_MASK))
__{
__ q->prio &= ~GRUB_COMMAND_FLAG_ACTIVE;
__ break;
__}
inactive = 1;
}
*p = cmd;
cmd->next = q;
if (q)
q->prev = &cmd->next;
cmd->prev = p;
if (! inactive)
cmd->prio |= GRUB_COMMAND_FLAG_ACTIVE;
return cmd;
}
根据上面代码的调用关系可知,函数grub_register_command_prio才是最终将命令注册到全局变量grub_command_t grub_command_list这个链表中的。linux命令注册的时候对应的回调函数就是grub_cmd_linux,而initrd对应的回调函数就是grub_cmd_initrd,这就是在使用这两个命令的时候,去执行的函数就是这两个函数,下面看一下这两个函数的代码实现。
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
____int argc, char *argv[])
{
grub_elf_t elf = 0;
int size;
int i;
grub_uint32_t *linux_argv;
char *linux_args;
grub_err_t err;
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
elf = grub_elf_open (argv[0]);
if (! elf)
return grub_errno;
if (elf->ehdr.ehdr32.e_type != ET_EXEC)
{
grub_elf_close (elf);
return grub_error (GRUB_ERR_UNKNOWN_OS,
______ N_("this ELF file is not of the right type"));
}
/* Release the previously used memory. */
grub_loader_unset ();
loaded = 0;
/* For arguments. */
linux_argc = argc;
/* Main arguments. */
size = (linux_argc) * sizeof (grub_uint32_t);
/* Initrd address and size. */
size += 2 * sizeof (grub_uint32_t);
/* NULL terminator. */
size += sizeof (grub_uint32_t);
/* First argument is always "a0". */
size += ALIGN_UP (sizeof ("a0"), 4);
/* Normal arguments. */
for (i = 1; i < argc; i++)
size += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
/* rd arguments. */
size += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
size += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
size = ALIGN_UP (size, 8);
if (grub_elf_is_elf32 (elf))
err = grub_linux_load32 (elf, argv[0]);
else
if (grub_elf_is_elf64 (elf))
err = grub_linux_load64 (elf, argv[0]);
else
err = grub_error (GRUB_ERR_BAD_OS, N_("invalid arch-dependent ELF magic"));
grub_elf_close (elf);
if (err)
return err;
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch,0, (0xffffffff - size) + 1,size, 8,GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
if (err)
return err;
linux_args_addr = get_virtual_current_address (ch);
}
linux_argv = (grub_uint32_t *) linux_args_addr;
linux_args = (char *) (linux_argv + (linux_argc + 1 + 2));
grub_memcpy (linux_args, "a0", sizeof ("a0"));
*linux_argv = (grub_uint32_t) (grub_addr_t) linux_args;
linux_argv++;
linux_args += ALIGN_UP (sizeof ("a0"), 4);
for (i = 1; i < argc; i++)
{
grub_memcpy (linux_args, argv[i], grub_strlen (argv[i]) + 1);
*linux_argv = (grub_uint32_t) (grub_addr_t) linux_args;
linux_argv++;
linux_args += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
}
/* Reserve space for rd arguments. */
rd_addr_arg_off = (grub_uint8_t *) linux_args - linux_args_addr;
linux_args += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
*linux_argv = 0;
linux_argv++;
rd_size_arg_off = (grub_uint8_t *) linux_args - linux_args_addr;
linux_args += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
*linux_argv = 0;
linux_argv++;
*linux_argv = 0;
//wake up other core
{
__ __asm__(
______ "dli $8, 0x900000003ff01000\n\t"
______ "dli $11, 0 \n\t"
______ "dsll $11, 8 \n\t"
______ "or $8, $8,$11 \n\t"
______ "li $9, 0x5a5a \n\t"
______ "sw $9, 32($8) \n\t"
______ "nop \n\t"
______ :
______ :
______ );
}
grub_loader_set (grub_linux_boot, grub_linux_unload, 0);
initrd_loaded = 0;
loaded = 1;
grub_dl_ref (my_mod);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
____ int argc, char *argv[])
{
grub_size_t size = 0;
void *initrd_dest;
grub_err_t err;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (!loaded)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
if (initrd_loaded)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "only one initrd command can be issued.");
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch,0, (0xffffffff - size) + 1,size, 0x10000,GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
if (err)
goto fail;
initrd_dest = get_virtual_current_address (ch);
}
if (grub_initrd_load (&initrd_ctx, argv, initrd_dest))
goto fail;
grub_snprintf ((char *) linux_args_addr + rd_addr_arg_off,
____ sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), "rd_start=0x%lx",
____(grub_uint64_t) initrd_dest);
((grub_uint32_t *) linux_args_addr)[linux_argc]
= (grub_uint32_t) ((grub_addr_t) linux_args_addr + rd_addr_arg_off);
linux_argc++;
grub_snprintf ((char *) linux_args_addr + rd_size_arg_off,
____sizeof ("rd_size=0xXXXXXXXXXXXXXXXXX"), "rd_size=0x%lx",
____(grub_uint64_t) size);
((grub_uint32_t *) linux_args_addr)[linux_argc]
= (grub_uint32_t) ((grub_addr_t) linux_args_addr + rd_size_arg_off);
linux_argc++;
initrd_loaded = 1;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
boot命令注册的代码如下:
static grub_command_t cmd_boot;
GRUB_MOD_INIT(boot)
{
cmd_boot =
grub_register_command ("boot", grub_cmd_boot,
______ 0, N_("Boot an operating system."));
}
/* boot */
static grub_err_t
grub_cmd_boot (struct grub_command *cmd __attribute__ ((unused)),
____ int argc __attribute__ ((unused)),
____ char *argv[] __attribute__ ((unused)))
{
return grub_loader_boot ();
}
grub_err_t
grub_loader_boot (void)
{
grub_err_t err = GRUB_ERR_NONE;
struct grub_preboot *cur;
if (! grub_loader_loaded)
return grub_error (GRUB_ERR_NO_KERNEL,
____ N_("you need to load the kernel first"));
grub_machine_fini (grub_loader_flags);
for (cur = preboots_head; cur; cur = cur->next)
{
err = cur->preboot_func (grub_loader_flags);
if (err)
__{
__ for (cur = cur->prev; cur; cur = cur->prev)
__ cur->preboot_rest_func ();
__ return err;
__}
}
err = (grub_loader_boot_func) ();
for (cur = preboots_tail; cur; cur = cur->prev)
if (! err)
err = cur->preboot_rest_func ();
else
cur->preboot_rest_func ();
return err;
}
从上面的代码可知,这三个命令的回调函数就这样注册进去了。这里面需要注意boot命令的执行的err = (grub_loader_boot_func) ();这句代码开始真正执行的,grub_loader_boot_func的地址是谁是在grub_loader_set (grub_linux_boot, grub_linux_unload, 0);中设置的,这个函数的实现如下:
void
grub_loader_set (grub_err_t (*boot) (void),
____ grub_err_t (*unload) (void),
____ int flags)
{
if (grub_loader_loaded && grub_loader_unload_func)
grub_loader_unload_func ();
grub_loader_boot_func = boot;
grub_loader_unload_func = unload;
grub_loader_flags = flags;
grub_loader_loaded = 1;
}
所以boot命令执行的回调函数是grub_linux_boot这个函数。这个函数的代码如下:
static grub_err_t
grub_linux_boot (void)
{
struct grub_relocator64_state state;
grub_efi_loongson_boot_params *boot_params;
grub_memset (&state, 0, sizeof (state));
/* Boot the kernel. */
state.gpr[1] = entry_addr;
grub_dprintf("loongson", "entry_addr is %p\n", __func__,state.gpr[1]);
state.gpr[4] = linux_argc;
grub_dprintf("loongson", "linux_argc is %d\n", __func__,state.gpr[4]);
state.gpr[5] = (grub_addr_t) linux_args_addr;
grub_dprintf("loongson", "args_addr is %p\n", __func__,state.gpr[5]);
boot_params = grub_efi_loongson_get_boot_params();
state.gpr[6] = (grub_uint64_t) boot_params;
grub_dprintf("loongson", "boot_params is %p\n", __func__,state.gpr[6]);
if (grub_efi_is_loongson ())
{
grub_efi_uintn_t mmap_size;
grub_efi_uintn_t desc_size;
grub_efi_memory_descriptor_t *mmap_buf;
grub_err_t err;
mmap_size = find_mmap_size ();
if (! mmap_size)
return grub_errno;
mmap_buf = grub_efi_allocate_any_pages (page_align (mmap_size) >> 12);
if (! mmap_buf)
return grub_error (GRUB_ERR_IO, "cannot allocate memory map");
err = grub_efi_finish_boot_services (&mmap_size, mmap_buf, NULL,
________ &desc_size, NULL);
if (err)
return err;
}
state.jumpreg = 1;
grub_relocator64_boot (relocator, state);
return GRUB_ERR_NONE;
}
linux内核也是从这个函数中的 grub_relocator64_boot (relocator, state);开始执行完之后开始运行的。到这里我们必不可少的三个命令就这样注册完了,对应的执行的回调函数也给大家介绍了,具体是如何load内核,loadinitrd以及去执行boot命令执行内核,这里不做详细介绍了,详细看代码都可以看出来。
这个函数是设置环境变量prefix和boot这两个环境变量的,其代码如下:
static void
grub_set_prefix_and_root (void)
{
char *device = NULL;
char *path = NULL;
char *fwdevice = NULL;
char *fwpath = NULL;
char *prefix = NULL;
struct grub_module_header *header;
FOR_MODULES (header)
if (header->type == OBJ_TYPE_PREFIX)
prefix = (char *) header + sizeof (struct grub_module_header);
grub_register_variable_hook ("root", 0, grub_env_write_root);
grub_machine_get_bootlocation (&fwdevice, &fwpath);
if (fwdevice)
{
char *cmdpath;
cmdpath = grub_xasprintf ("(%s)%s", fwdevice, fwpath ? : "");
if (cmdpath)
__{
__ grub_env_set ("cmdpath", cmdpath);
__ grub_env_export ("cmdpath");
__ grub_free (cmdpath);
__}
}
if (prefix)
{
char *pptr = NULL;
if (prefix[0] == '(')
__{
__ pptr = grub_strrchr (prefix, ')');
__ if (pptr)
__ {
__ device = grub_strndup (prefix + 1, pptr - prefix - 1);
__ pptr++;
__ }
__}
if (!pptr)
__pptr = prefix;
if (pptr[0])
__path = grub_strdup (pptr);
}
if (!device && fwdevice)
device = fwdevice;
else if (fwdevice && (device[0] == ',' || !device[0]))
{
/* We have a partition, but still need to fill in the drive. */
char *comma, *new_device;
for (comma = fwdevice; *comma; )
__{
__ if (comma[0] == '\\' && comma[1] == ',')
__ {
__ comma += 2;
__ continue;
__ }
__ if (*comma == ',')
__ break;
__ comma++;
__}
if (*comma)
__{
__ char *drive = grub_strndup (fwdevice, comma - fwdevice);
__ new_device = grub_xasprintf ("%s%s", drive, device);
__ grub_free (drive);
__}
else
__new_device = grub_xasprintf ("%s%s", fwdevice, device);
grub_free (fwdevice);
grub_free (device);
device = new_device;
}
else
grub_free (fwdevice);
if (fwpath && !path)
{
grub_size_t len = grub_strlen (fwpath);
while (len > 1 && fwpath[len - 1] == '/')
__fwpath[--len] = 0;
if (len >= sizeof (GRUB_TARGET_CPU "-" GRUB_PLATFORM) - 1
__ && grub_memcmp (fwpath + len - (sizeof (GRUB_TARGET_CPU "-" GRUB_PLATFORM) - 1), GRUB_TARGET_CPU "-" GRUB_PLATFORM,
______ sizeof (GRUB_TARGET_CPU "-" GRUB_PLATFORM) - 1) == 0)
__fwpath[len - (sizeof (GRUB_TARGET_CPU "-" GRUB_PLATFORM) - 1)] = 0;
path = fwpath;
}
else
grub_free (fwpath);
if (device)
{
char *prefix_set;
prefix_set = grub_xasprintf ("(%s)%s", device, path ? : "");
if (prefix_set)
__{
__ grub_env_set ("prefix", prefix_set);
__ grub_free (prefix_set);
__}
grub_env_set ("root", device);
}
grub_free (device);
grub_free (path);
grub_print_error ();
}
这个函数的实现原理这里不做详细的介绍了,在调试过程中这个函数基本上没有动,公共函数一般是没有问题的。
这个函数是回收前面运行每个moudule的时候,将moudule加载到内存所占用的内存的地址。其代码如下:
static void
reclaim_module_space (void)
{
grub_addr_t modstart, modend;
if (!grub_modbase)
return;
#ifdef GRUB_MACHINE_PCBIOS
modstart = GRUB_MEMORY_MACHINE_DECOMPRESSION_ADDR;
#else
modstart = grub_modbase;
#endif
modend = grub_modules_get_end ();
grub_modbase = 0;
#if GRUB_KERNEL_PRELOAD_SPACE_REUSABLE
grub_mm_init_region ((void *) modstart, modend - modstart);
#else
(void) modstart;
(void) modend;
#endif
}
这个函数就是注册set命令ls命令等常用的命令的回调函数,其详细的代码实现如下:
void
grub_register_core_commands (void)
{
grub_command_t cmd;
cmd = grub_register_command ("set", grub_core_cmd_set,
______ N_("[ENVVAR=VALUE]"),
______ N_("Set an environment variable."));
if (cmd)
cmd->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
grub_register_command ("unset", grub_core_cmd_unset,
______ N_("ENVVAR"),
______ N_("Remove an environment variable."));
grub_register_command ("ls", grub_core_cmd_ls,
______ N_("[ARG]"), N_("List devices or files."));
grub_register_command ("insmod", grub_core_cmd_insmod,
______ N_("MODULE"), N_("Insert a module."));
}
这里面注册的set命令的回调函数的命令就是函数grub_core_cmd_set,用于在命令行下写一个环境变量。同理unset命令的回调函数为grub_core_cmd_unset,ls命令的回调函数为grub_core_cmd_ls,insmod命令的回调函数为grub_core_cmd_insmod,每个函数的具体实现这里不再详细的介绍。具体可以看源码实现。
这个函数的作用就是解析grub.cfg中的内容,并运行对应的命令。这个函数是被if语句包含着的,其代码实现如下:
if (load_config)
grub_parser_execute (load_config);
也就是说只有load_config非0才会调用,如果这里为0,那么就不会调用这个函数,继续向下执行。
grub_err_t
grub_parser_execute (char *source)
{
while (source)
{
char *line;
grub_parser_execute_getline (&line, 0, &source);
grub_rescue_parse_line (line, grub_parser_execute_getline, &source);
grub_free (line);
grub_print_error ();
}
return grub_errno;
}
grub_err_t
grub_rescue_parse_line (char *line,
______grub_reader_getline_t getline, void *getline_data)
{
char *name;
int n;
grub_command_t cmd;
char **args;
if (grub_parser_split_cmdline (line, getline, getline_data, &n, &args)
|| n < 0)
return grub_errno;
if (n == 0)
return GRUB_ERR_NONE;
/* In case of an assignment set the environment accordingly
instead of calling a function. */
if (n == 1)
{
char *val = grub_strchr (args[0], '=');
if (val)
__{
__ val[0] = 0;
__ grub_env_set (args[0], val + 1);
__ val[0] = '=';
__ goto quit;
__}
}
/* Get the command name. */
name = args[0];
/* If nothing is specified, restart. */
if (*name == '\0')
goto quit;
cmd = grub_command_find (name);
if (cmd)
{
(cmd->func) (cmd, n - 1, &args[1]);
}
else
{
grub_printf_ (N_("Unknown command `%s'.\n"), name);
if (grub_command_find ("help"))
__grub_printf ("Try `help' for usage\n");
}
quit:
/* Arguments are returned in single memory chunk separated by zeroes */
grub_free (args[0]);
grub_free (args);
return grub_errno;
}
真正去执行对应命令的回调函数的代码是(cmd->func) (cmd, n - 1, &args[1]),这里才去调用。
这个函数就是执行normal这个命令对应的mod,其代码如下:
/* Load the normal mode module and execute the normal mode if possible. */
static void
grub_load_normal_mode (void)
{
/* Load the module. */
grub_dl_load ("normal");
/* Print errors if any. */
grub_print_error ();
grub_errno = 0;
grub_command_execute ("normal", 0, 0);
}
static inline grub_err_t
grub_command_execute (const char *name, int argc, char **argv)
{
grub_command_t cmd;
cmd = grub_command_find (name);
return (cmd) ? cmd->func (cmd, argc, argv) : GRUB_ERR_FILE_NOT_FOUND;
}
这个函数是防止如果上面的函数grub_parser_execute没有执行,那么就在这里去解析grub.cfg中的命令并执行其代码如下:
void __attribute__ ((noreturn))
grub_rescue_run (void)
{
grub_printf ("Entering rescue mode...\n");
while (1)
{
char *line;
/* Print an error, if any. */
grub_print_error ();
grub_errno = GRUB_ERR_NONE;
grub_rescue_read_line (&line, 0, NULL);
if (! line || line[0] == '\0')
__continue;
grub_rescue_parse_line (line, grub_rescue_read_line, NULL);
grub_free (line);
}
}
到这里整个grub的执行流程就基本完事了,其主要的代码原理是注册一些命令和对应的回调函数,然后解析grub.cfg去执行对应的命令,一次执行其中的mod。在执行linux命令的时候去加载内核,将内核加载到一个内存地址上,执行initrd命令的时候去加载initrd,将initrd也加载到一个内存地址上,然后执行boot命令,开始解压并解析内核,这个时候拿到内核运行的基地址,并将整个内核拷贝到对应的地址上,并开始运行,这样就开时了操作系统的启动过程。
下面详细介绍一下grub_linux_boot()这个函数,这个函数很重要,主要完成了BIOS向内核传递参数和开始运行内核的功能。
下面是其代码:
static grub_err_t
grub_linux_boot (void)
{
struct grub_relocator64_state state;
grub_efi_loongson_boot_params *boot_params;
grub_memset (&state, 0, sizeof (state));
/* Boot the kernel. */
state.gpr[1] = entry_addr;
grub_dprintf("loongson", "entry_addr is %p\n", __func__,state.gpr[1]);
state.gpr[4] = linux_argc;
grub_dprintf("loongson", "linux_argc is %d\n", __func__,state.gpr[4]);
state.gpr[5] = (grub_addr_t) linux_args_addr;
grub_dprintf("loongson", "args_addr is %p\n", __func__,state.gpr[5]);
boot_params = grub_efi_loongson_get_boot_params();
state.gpr[6] = (grub_uint64_t) boot_params;
grub_dprintf("loongson", "boot_params is %p\n", __func__,state.gpr[6]);
if (grub_efi_is_loongson ())
{
grub_efi_uintn_t mmap_size;
grub_efi_uintn_t desc_size;
grub_efi_memory_descriptor_t *mmap_buf;
grub_err_t err;
mmap_size = find_mmap_size ();
if (! mmap_size)
return grub_errno;
mmap_buf = grub_efi_allocate_any_pages (page_align (mmap_size) >> 12);
if (! mmap_buf)
return grub_error (GRUB_ERR_IO, "cannot allocate memory map");
err = grub_efi_finish_boot_services (&mmap_size, mmap_buf, NULL,
________ &desc_size, NULL);
if (err)
return err;
}
state.jumpreg = 1;
grub_relocator64_boot (relocator, state);
return GRUB_ERR_NONE;
}
这个函数中grub_relocator64_boot (relocator, state);才是真正的去跳转到内核开始执行内核代码的函数,前面的几个函数主要是完成给内核传递参数,和执行uefi中的exitbootservice函数,回收BIOS下不适用的内存和资源。
下面看一下 grub_relocator64_boot 的具体代码实现:
grub_err_t
grub_relocator64_boot (struct grub_relocator *rel,
____ struct grub_relocator64_state state)
{
grub_relocator_chunk_t ch;
void *ptr;
grub_err_t err;
void *relst;
grub_size_t relsize;
grub_size_t stateset_size = 31 * REGW_SIZEOF + JUMP_SIZEOF;
unsigned i;
grub_addr_t vtarget;
err = grub_relocator_alloc_chunk_align (rel, &ch, 0,
__________ (0xffffffff - stateset_size)
__________ + 1, stateset_size,
__________ grub_relocator_align,
__________ GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err)
return err;
ptr = get_virtual_current_address (ch);
for (i = 1; i < 32; i++)
write_reg (i, state.gpr[i], &ptr);
write_jump (state.jumpreg, &ptr);
vtarget = (grub_addr_t) grub_map_memory (get_physical_target_address (ch),
__________ stateset_size);
err = grub_relocator_prepare_relocs (rel, vtarget, &relst, &relsize);
if (err)
return err;
grub_arch_sync_caches ((void *) relst, relsize);
((void (*) (void)) relst) ();
/* Not reached. */
return GRUB_ERR_NONE;
}
这个函数实现的很复杂,其中调用的函数,现在还没有完全弄透,只是明白大致原理。清楚的读者可以下面评论,一起学习!