当前位置: 首页 > 工具软件 > GRUB > 使用案例 >

Grub调试总结

丁高峯
2023-12-01

最近在龙芯平台调研Grub的实现,在这过程中仔细研究了Grub的代码,就做个总结吧!
首先Grub在uefi上也是以一个Grub.efi的方式运行的,但是这个efi是放在操作系统下面的boot/EFI/下面的,uefi在运行过程总后期加载Grub的时候会去这个目录load这个Grub模块。load到内存之后,就开始执行。这里面不做详细的介绍是如何load以及如何运行的,这部分内容都在uefi中有源码,想了解的可以去看uefi的源码。下面就从load内存之后所执行的第一个函数开始说起。注意这里面一些不重要的函数就不细说了,只介绍重点。

第一个运行的函数

grub_main()

代码路径位于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的功能可以说基本上都是在这个函数中完成的。下面看一下主要的几个函数。

grub_machine_init ()

这个函数是和体系架构相关的,在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);

grub_load_config()

这个函数代码如下:

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_load_modules

这个函数就是去执行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、boot命令

下面看一下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命令执行内核,这里不做详细介绍了,详细看代码都可以看出来。

函数grub_set_prefix_and_root ()

这个函数是设置环境变量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 ();
}

这个函数的实现原理这里不做详细的介绍了,在调试过程中这个函数基本上没有动,公共函数一般是没有问题的。

函数reclaim_module_space ()

这个函数是回收前面运行每个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
}

函数grub_register_core_commands ()

这个函数就是注册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_parser_execute

这个函数的作用就是解析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]),这里才去调用。

grub_load_normal_mode ()

这个函数就是执行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_rescue_run

这个函数是防止如果上面的函数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;
}

这个函数实现的很复杂,其中调用的函数,现在还没有完全弄透,只是明白大致原理。清楚的读者可以下面评论,一起学习!

 类似资料: