一 什么是grub
GNU GRUB 是一个多重操作系统启动管理器。GNU GRUB是由GRUB(GRandUnified Bootloader)派生而来。
GRUB最初由Erich Stefan Boleyn 设计和应用;
主流发行版 Fedora、Redhat、Centos、Kylin 等基于RPM包的系统,在最新版本中都默认GRUB引导;
Slackware目前仍采用LILO;而Debian发行版目前最新的版本也是采用GRUB;
Grub在uefi上也是以一个Grub.efi的方式运行的,但是这个efi是放在操作系统下面的boot/EFI/下面的,
uefi在运行过程的后期加载Grub的时候会去这个目录load这个Grub模块,Load到内存之后,就开始执行。
二 grub源码环境的安装
笔者以国产Kylin操作系统为例进行安装:
a) 先拿到grub2 对应的rpm包
https://download.csdn.net/download/qq_33559839/89560518
b) 下载之后,将rpm报进行安装
rpm -ivh grub2-2.12-21.se.02.p01.ky11.src.rpm
c) 将源码展示出来
cd /root/rpmbuild
rpmbuild -bp SPECS/grub2.spec
cd BUILD
grub-2.12 --》 就是grub2的源码
find -name main.c
找到grub2的入口点文件 ---》 ./grub-core/kern/main.c
三 grub源码分析:
3.0 首先是grub的入口点文件:
vi grub-core/kern/loongarch64/efi/startup.S 就是入口点文件
FUNCTION(_start)
/*
* EFI_SYSTEM_TABLE and EFI_HANDLE are passed in $a1/$a0.
*/
la $a2, EXT_C(grub_efi_image_handle)
st.d $a0, $a2, 0
la $a2, EXT_C(grub_efi_system_table)
st.d $a1, $a2, 0
b EXT_C(grub_main) //跳转到grub_main函数,所以gruub_main就是grub的入口函数
3.1 我们找到入口点文件之后,就可以分析grub的源码了
grub_main 是grub执行的main函数,绝大多数grub的主要工作都是在这个函数中完成的
/* The main routine. */
void __attribute__ ((noreturn))
grub_main (void)
{
/* First of all, initialize the machine. */
grub_machine_init ();
grub_boot_time ("After machine init.");
/* This breaks flicker-free boot on EFI systems, so disable it there. */
#ifndef GRUB_MACHINE_EFI
/* Hello. */
grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
grub_printf ("Welcome to GRUB!\n\n");
grub_setcolorstate (GRUB_TERM_COLOR_STANDARD);
#endif
...
}
3.2 先来分析第一个函数:
初始化grub在本平台上要做的各种操作,这个函数与架构是强相关的,x86架构,arm架构,loongarch架构上
的实现都不一样
笔者就以国产loongarch架构为例进行讲解,该函数位于grub-core/kern/loongarch64/efi/init.c中
(loongson架构的位于./grub-core/kern/mips/loongson/init.c文件中)
grub_machine_init 函数的主要工作:
a) 先获取grub要加载的各个模块的基地址
问题:grub要加载哪些模块呢?
b) 初始化控制台
c) 堆区内存的映射及初始化(初始化内存管理系统、grub中有一套自己的内存管理方式)
d) 初始化frame buffer
e)初始化字体及终端
f) ftdbus、网卡、系统定时器、串口等设备的初始化
void
grub_machine_init (void)
{
grub_efi_boot_services_t *b;
grub_efi_init ();
b = grub_efi_system_table->boot_services;
b->create_event (GRUB_EFI_EVT_TIMER | GRUB_EFI_EVT_NOTIFY_SIGNAL,
GRUB_EFI_TPL_CALLBACK, grub_loongson_increment_timer, NULL, &tmr_evt);
b->set_timer (tmr_evt, GRUB_EFI_TIMER_PERIODIC, EFI_TIMER_PERIOD_MILLISECONDS(10));
grub_install_get_time_ms (grub_efi_get_time_ms);
}
3.2.1 grub_efi_init
我们先简单的分析一下这个函数(位于grub-core/kern/loongarch64/efi/init.c中):
void
grub_machine_init (void)
{
grub_efi_boot_services_t *b;
grub_efi_init ();
b = grub_efi_system_table->boot_services;
b->create_event (GRUB_EFI_EVT_TIMER | GRUB_EFI_EVT_NOTIFY_SIGNAL,
GRUB_EFI_TPL_CALLBACK, grub_loongson_increment_timer, NULL, &tmr_evt);
b->set_timer (tmr_evt, GRUB_EFI_TIMER_PERIODIC, EFI_TIMER_PERIOD_MILLISECONDS(10));
grub_install_get_time_ms (grub_efi_get_time_ms);
}
__attribute__ ((__optimize__ ("-fno-stack-protector"))) void
grub_efi_init (void)
{
grub_modbase = grub_efi_section_addr ("mods");
/* First of all, initialize the console so that GRUB can display
messages. */
grub_console_init ();
stack_protector_init ();
/* Initialize the memory management system. */
grub_efi_mm_init ();
/*
* Lockdown the GRUB and register the shim_lock verifier
* if the UEFI Secure Boot is enabled.
*/
if (grub_efi_get_secureboot () == GRUB_EFI_SECUREBOOT_MODE_ENABLED)
{
grub_lockdown ();
grub_shim_lock_verifier_setup ();
}
grub_efi_system_table->boot_services->set_watchdog_timer (0, 0, 0, NULL);
grub_efidisk_init ();
grub_efi_register_debug_commands ();
}
别的先不说,我们先来看看grub的内存是怎么管理的:
void
grub_efi_mm_init (void)
{
/*
#ifdef GRUB_CPU_LOONGARCH64
#define DEFAULT_HEAP_SIZE 0x10000000
#else
#define DEFAULT_HEAP_SIZE 0x2000000
#endif
*/
//如果是loongarch架构,grub会在堆区分配256M的内存空间
if (grub_efi_mm_add_regions (DEFAULT_HEAP_SIZE, GRUB_MM_ADD_REGION_NONE) != GRUB_ERR_NONE)
grub_fatal ("%s", grub_errmsg);
grub_mm_add_region_fn = grub_efi_mm_add_regions;
}
static grub_err_t
grub_efi_mm_add_regions (grub_size_t required_bytes, unsigned int flags)
{
grub_efi_memory_descriptor_t *memory_map;
grub_efi_memory_descriptor_t *memory_map_end;
grub_efi_memory_descriptor_t *filtered_memory_map;
grub_efi_memory_descriptor_t *filtered_memory_map_end;
grub_efi_uintn_t map_size;
grub_efi_uintn_t desc_size;
grub_err_t err;
int mm_status;
/* Prepare a memory region to store two memory maps. */
/*
#define MEMORY_MAP_SIZE 0x3000
void *
grub_efi_allocate_any_pages (grub_efi_uintn_t pages)
{
#ifdef GRUB_CPU_LOONGARCH64
return grub_efi_allocate_pages_real (grub_efi_max_usable_address(),
pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS,
GRUB_EFI_LOADER_DATA);
#else
return grub_efi_allocate_pages_real (GRUB_EFI_MAX_USABLE_ADDRESS,
pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS,
GRUB_EFI_LOADER_DATA);
#endif
}
//可以看出来loongarch架构grub的最高的可用地址是从csr寄存器中读出来的
//而其他架构grub可用的最高地址是0xffffffff
static inline grub_uint64_t grub_efi_max_usable_address(void)
{
grub_uint64_t addr;
asm volatile ("csrrd %0, 0x181" : "=r" (addr));
return addr |= 0xffffffffffUL;
}
所以下面的这个函数,是grub先申请6页的内存保存memory map
*/
memory_map = grub_efi_allocate_any_pages (2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
if (! memory_map)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory for memory map");
/* Obtain descriptors for available memory. */
map_size = MEMORY_MAP_SIZE;
//获取EFI规范中定义的内存映射。如果成功返回1,如果部分返回0,或者如果发生错误返回-1。
mm_status = grub_efi_get_memory_map (&map_size, memory_map, 0, &desc_size, 0);
if (mm_status == 0)
{
grub_efi_free_pages
((grub_efi_physical_address_t) ((grub_addr_t) memory_map),
2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
/* Freeing/allocating operations may increase memory map size. */
map_size += desc_size * 32;
memory_map = grub_efi_allocate_any_pages (2 * BYTES_TO_PAGES (map_size));
if (! memory_map)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory for new memory map");
mm_status = grub_efi_get_memory_map (&map_size, memory_map, 0,
&desc_size, 0);
}
if (mm_status < 0)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "error fetching memory map from EFI");
memory_map_end = NEXT_MEMORY_DESCRIPTOR (memory_map, map_size);
filtered_memory_map = memory_map_end;
filtered_memory_map_end = filter_memory_map (memory_map, filtered_memory_map,
desc_size, memory_map_end);
/* Sort the filtered descriptors, so that GRUB can allocate pages
from smaller regions. */
sort_memory_map (filtered_memory_map, desc_size, filtered_memory_map_end);
/* Allocate memory regions for GRUB's memory management. */
err = add_memory_regions (filtered_memory_map, desc_size,
filtered_memory_map_end,
BYTES_TO_PAGES (required_bytes),
flags);
if (err != GRUB_ERR_NONE)
return err;
#if 0
/* For debug. 该打印可以将grub分配的内存信息打印出来*/
map_size = MEMORY_MAP_SIZE;
if (grub_efi_get_memory_map (&map_size, memory_map, 0, &desc_size, 0) < 0)
grub_fatal ("cannot get memory map");
grub_printf ("printing memory map\n");
print_memory_map (memory_map, desc_size,
NEXT_MEMORY_DESCRIPTOR (memory_map, map_size));
grub_fatal ("Debug. ");
#endif
/* Release the memory maps. */
grub_efi_free_pages ((grub_addr_t) memory_map,
2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
return GRUB_ERR_NONE;
}