一 什么是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, 0la $a2, EXT_C(grub_efi_system_table)st.d $a1, $a2, 0b 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 displaymessages. */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_LOONGARCH64return grub_efi_allocate_pages_real (grub_efi_max_usable_address(),pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS,GRUB_EFI_LOADER_DATA);#elsereturn 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可用的最高地址是0xffffffffstatic 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 pagesfrom 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;
}
...
--------------------------------------------------------
--------------------------------------------------------
...
//接下来我们重点分析一下grub加载vmliux和initrd的逻辑:
vi grub-core/loader/loongarch64/linux-elf.c
struct grub_relocator_chunk
{struct grub_relocator_chunk *next;grub_phys_addr_t src;void *srcv;grub_phys_addr_t target;grub_size_t size;struct grub_relocator_subchunk *subchunks;unsigned nsubchunks;
};
typedef const struct grub_relocator_chunk *grub_relocator_chunk_t; grub_err_t
grub_relocator_alloc_chunk_align (struct grub_relocator *rel,grub_relocator_chunk_t *out,grub_phys_addr_t min_addr,grub_phys_addr_t max_addr,grub_size_t size, grub_size_t align,int preference,int avoid_efi_boot_services);//想要申请size个字节的空间,按照align进行对齐,错误信息回填到err中
void*
grub_linux_loongarch_alloc_virtual_mem_align (grub_size_t size,grub_size_t align,grub_err_t *err)
{grub_relocator_chunk_t ch;*err = grub_relocator_alloc_chunk_align (relocator, &ch,0, (0xffffffff - size) + 1,size, align,//从低地址向高地址进行申请GRUB_RELOCATOR_PREFERENCE_LOW, 0);return get_virtual_current_address (ch);
}