菜单

kaoqi
kaoqi
发布于 2026-07-18 / 22 阅读
0
0

UEFI与EFI交互机制

UEFI固件本体向efi文件传输参数时,会向入口函数传递两个核心参数:ImageHandle和SystemTable。

GRUB主要还是与UEFI打配合的,UEFI固件本身需要遵从UEFI协议,从下文的描述中也可以看出UEFI协议的一些核心概念,比如protocol的概念。UEFI固件本身的源代码编辑的话,一般可以参考开源的EDK2项目来进行学习。

由objdump -S 命令可以查看.text代码段的最前面部分为

这与startup.S文件中的描述是一样的:(/grub-core/kern/arm64/efi/startup.S)

这里就可以明确,作为efi文件的开头,实际上是一个用于接收来自UEFI固件两个参数,并跳转到main函数所在位置的一个整体封装的函数。

从代码中可以确认,会给定efi_image_handle_val与efi_system_table_val两个变量。

其中efi image handle起始就是efi这个程序本身的句柄;efi system table这个变量最终会宏展开为如下的一个struct

struct grub_efi_system_table
{
  grub_efi_table_header_t hdr;  // 一个用于UEFI格式表头的数字签名相关字段
  grub_efi_char16_t *firmware_revision;  // 固件供应商名称
  grub_efi_uint32_t firmware_revision;  // 固件版本号
  grub_efi_handle_t console_in_handler;  // 输入控制台
  grub_efi_simple_input_interface_t *con_in;  // 输入指针
  grub_efi_handle_t console_out_handler;  // 输出控制台
  grub_efi_simple_text_output_interface_t *std_err;  // 输出指针
  grub_efi_runtime_services_t *runtime_services;  // 运行时服务表
  grub_efi_boot_services_t *boot_services;  // 启动服务表
  grub_efi_uintn_t num_table_entries;  // 系统配置表数目
  grub_efi_configuration_table_t *configuration_table;  // 系统配置表
}

这里进一步涉及到一系列表项的更具象化的展开。

表头结构体

首先是介绍一下表头的结构:

struct grub_efi_table_header
{
  grub_efi_uint64_t signature;  // 一个64位宽的数字签名
  grub_efi_uint32_t revision;  // 表明这个表所遵循的UEFI版本号
  grub_efi_uint32_t header_size;  // 整固件表的大小
  grub_efi_uint32_t crc32;  // 整表的CRC32校验码
  grub_efi_uint32_t reserved;  // 保留字段
}

grub_efi_handle_t 一般认为是在grub中标识一个void的意思,所以这里的输入控制台应该含义为一个函数的地址。

输入interface结构体

grub_efi_simple_input_interface这个指针的struct为

struct grub_efi_simple_input_interface
{
  grub_efi_status_t (__grub_efi_api *reset) (struct grub_efi_simple_input_interface *this, grub_efi_boolean_t extended_verification);  // 一个将控制台输入设备重置为初始状态的按钮
  grub_eft_status_t (__grub_efi_api *read_key_stroke) (struct grub_efi_simple_input_interface *this, grub_efi_input_key_t *key);  // 从输入队列中读取下一个按键数据
  grub_efi_event_t wait_for_key;  // 这是一个事件对象,当输入队列中有按键可以用时,该事件会触发
}

grub_efi_status_t本身可以被展开成为一个uint64;__grub_efi_api是一个宏定义,这里就是空的,所以这个含义可以写为:

uint64 *reset (struct grub_efi_simple_input_interface this, grub_efi_boolean_t extended_verification);

这样的一个函数定义表达式。

grub_efi_boolean_t 就是一个char

grub_efi_input_key是一个如下的二元结构:

strict grub_efi_input_key
{
  grub_efi_uint16_t scan_code;  // 扫描码,用于标识不可打印字符按键
  grub_efi_char16_t unicode_char;  // Unicode字符,用于表示可打印字符按键
}

这里实际上对应的是 UEFI规范中的EFI INPUT KEY的结构体,这里还可以进一步放一个可以引用的扫描码与Unicode码的表:

// 扫描码:
SCAN_UP          0x0001      # 方向上键
SCAN_DOWN        0x0002      # 方向下键
SCAN_RIGHT       0x0003      # 方向右键
SCAN_LEFT        0x0004      # 方向左键
SCAN_HOME        0x0005      # Home 键
SCAN_END         0x0006      # End 键
SCAN_INSERT      0x0007      # Insert 键
SCAN_DELETE      0x0008      # Delete 键
SCAN_PAGE_UP     0x0009      # Page Up 键
SCAN_PAGE_DOWN   0x000A      # Page Down 键
SCAN_F1          0x000B      # F1 键
SCAN_F2          0x000C      # F2 键
SCAN_F3          0x000D      # F3 键
SCAN_F4          0x000E      # F4 键
SCAN_F5          0x000F      # F5 键
SCAN_F6          0x0010      # F6 键
SCAN_F7          0x0011      # F7 键
SCAN_F8          0x0012      # F8 键
SCAN_F9          0x0013      # F9 键
SCAN_F10         0x0014      # F10 键
SCAN_ESC         0x0017      # ESC 键

// Unicode码
CHAR_NULL              0x0000    # 空字符
CHAR_BACKSPACE         0x0008    # 退格键
CHAR_TAB               0x0009    # Tab 键
CHAR_LINEFEED          0x000A    # 换行符
CHAR_CARRIAGE_RETURN   0x000D    # 回车键

值得注意的事情是,这里的reset、read_key_stroke、wait_for_key共2个函数1个时间,此时并不在efi文件内完成实例化,需要等待UEFI固件实例这几个函数后,由system table表格传递进来的时候,才能正式的使用调用这些函数。

总结一下就是,输入指针对应一个reset函数、一个read key stroke函数,一个wait for key事件。

输出interface结构体

输出interface结构体可以展开为grub_efi_simple_text_output_interface

struct grub_efi_simple_text_output_interface
{
  grub_efi_status_t (__grub_efi_api *reset) (struct grub_efi_simple_text_output_interface *this, grub_efi_boolean_t extended_verification); // reset函数,意思就是执行重启
  grub_efi_status_t (__grub_efi_api *output_string) (struct grub_efi_simple_text_output_interface *this, grub_efi_char16_t *string);  // 输出字符串的函数,需要给定一个具体的char16
  grub_efi_status_t (__grub_efi_api *test_string) (struct grub_efi_simple_text_output_interface *this, grub_efi_char16_t *string);  // 一个用于测试所有字符是否可以被正常显示的函数,如果所有字符都显示正确,返回success
  grub_efi_status_t (__grub_efi_api *query_mode) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t mode_number, grub_efi_uintn_t *columns, grub_efi_uintn_t *rows);  // 查询文本模式信息
  grub_efi_status_t (__grub_efi_api *set_mode) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t mode_number);  // 设置文本模式
  grub_efi_status_t (__grub_efi_api *set_attributes) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t attribute);  // 设置文本属性
  grub_efi_status_t (__grub_efi_api *clear_screen) (struct grub_efi_simple_text_output_interface *this);  // 清屏
  grub_efi_status_t (__grub_efi_api *set_cursor_position) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t column, grub_efi_uintn_t row);  // 设置光标位置
  grub_efi_status_t (__grub_efi_api *enable_cursor) (struct grub_efi_simple_text_output_interface *this, grub_efi_boolean_t visible);  // 控制光标可见性
  grub_efi_simple_text_output_mode_t *mode;  //这是一个指向模式结构体的指针
}

这里output的interface笼统的涵盖reset、output string、test string、query mode、set mode、set attributes、clear screen、set cursor position、enable cursor共8个函数与一个mode的结构体。

这里的mode结构体如下:

struct grub_efi_simple_text_output_mode
{
  grub_efi_int32_t max_mode;  // 最大模式数目
  grub_efi_int32_t mode;  // 当前模式编号
  grub_efi_int32_t attribute;  // 当前使用的文本属性,包含4位的前景颜色与4位的后景颜色
  grub_efi_int32_t cursor_column;  // 当前光标所在列位置
  grub_efi_int32_t cursor_row;  // 当前光标所在行位置
  grub_efi_boolean_t cursor_visible;  // 光标是否可见
};

可以看出,这里由system table给定的一个output结构体,可以在不描述具体内部函数实现的前提下把一个terminal一样的东西抽象一个struct出来了。具体的system table中interface的实现,可以在main函数中随着machine init函数的展开逐步具体的讲一讲。

启动服务表

grub_efi_boot_services,启动服务接口,在GRUB运行操作系统内核时,提供内存管理、设备访问、程序加载等关键服务。

下面是这个表中描述的具体内容:

struct grub_efi_boot_services
{
  grub_efi_table_header_t hdr;
  grub_efi_tpl_t (__grub_efi_api *raise_tpl) (grub_efi_tpl_t new tpl);
  viod (__grub_efi_api *restore_tpl) (grub_efi_tpl_t old_tpl);
  grub_efi_status_t (__grub_efi_api *allocate_pages) (grub_efi_allocate_type_t type, grub_efi_memory_type_t memory_type, grub_efi_uintn_t pages, grub_efi_physical_address_t *memory);
  grub_efi_status_t (__grub_efi_api *free_pages) (grub_efi_physical_address_t memory, grub_efi_uintn_t pages);
  grub_efi_status_t (__grub_efi_api *get_memory_map) (grub_efi_uintn_t *memory_map_size, grub_efi_memory_descriptor_t *memory_map, grub_efi_uintn_t *map_key, grub_efi_uintn_t *descriptor_size, grub_efi_uint32_t *descriptor_version);
  grub_efi_status_t (__grub_efi_api *allocate_pool) (grub_efi_memory_type_t pool_type, grub_efi_uintn_t size, void **buffer);
  grub_efi_status_t (__grub_efi_api *free_pool) (void *buffer);
  grub_efi_status_t (__grub_efi_api *create_event) (grub_efi_uint32_t type, grub_efi_tpl_t notify_tpl, void (__grub_efi_api *notify_function) (grub_efi_event_t, void *context), void *notify_context, grub_efi_event_t *event);
  grub_efi_status_t (__grub_efi_api *set_timer) (grub_efi_event_t event, grub_efi_timer_delay_t type, grub_efi_uint64_t trigger_time);
  grub_efi_status_t (__grub_efi_api *wait_for_event) (grub_efi_uintn_t num_events, grub_efi_event_t *event, grub_efi_uintn_t *index);
  grub_efi_status_t (__grub_efi_api *signal_event) (grub_efi_event_t event);
  grub_efi_status_t (__grub_efi_api *close_event) (grub_efi_event_t event);
  grub_efi_status_t (__grub_efi_api *check_event) (grub_efi_event_t event);
  grub_efi_status_t (__grub_efi_api *install_protocol_interface) (grub_efi_handle_t *handle, grub_guid_t *protocol, grub_efi_interface_type_t protocol_interface_type, void *protocol_interface);
  。。。。。。
}

由于boot services的表中内容过于多了(共有45中方法),这里先简单总结一下启动服务表能干啥,未来在新的文章中再详细解释。

总的来说,boot services表提供了如下内容:

1、对pages的操作方法,允许你占用或释放pages,允许你获取内存地图,并以pool的方式进行内存的占用或者释放

2、提供3中直接方法:设置时间、直接修改内存、直接复制内存

3、提供事件机制,包括等待事件、关闭事件、检查事件等

4、提供了protocol协议的安装方法或者卸载方法,包括单个的方法与多个的方法

5、提供了image相关的操作,比如加载、卸载、开始等

目前主要总结为上述方法。

系统配置表

grub_efi_configuration_table主要用于向操作系统传递固件级别的系统信息。

这里给出GRUB中的struct:

struct grub_efi_configuration_table
{
  grub_packed_guid_t vendor_guid;
  void *vendor_table;
} GRUB_PACKED;

其中vendor guid代表分区的GUID标识,vendor table指向实际数据结构。

GUID本身是一种配置表项的类型描述。

一般认为系统配置表会指向一个类似ACPI表的结构,这一点在日后分析ACPI模块时再展开讲,目前可以理解系统配置表为:一个用于描述硬件信息的配置表。


评论