文章目录
EFIVARFS 实现原理
efivarfs 是一种特殊的文件系统,用于挂载和访问 EFI 变量。EFI(Extensible Firmware Interface)是一个标准,提供了操作系统和平台固件之间的接口。efivarfs 提供了一种直接访问 EFI 变量的方法,使操作系统能够读取和写入这些变量。
基本原理
1、文件系统类型:
- efivarfs 是一个内存中的虚拟文件系统,类似于 proc 和 sysfs。
2、EFI 变量:
- 每个 EFI 变量在 efivarfs 中表示为一个文件,文件名是变量的名称。文件内容是变量的值。
- EFI 变量的元数据(如 GUID、属性)通过文件的元数据进行存储和访问。
3、挂载 efivarfs:
- 在 Linux 系统中,可以通过以下命令挂载 efivarfs:
sudo mount -t efivarfs efivarfs /sys/firmware/efi/efivars
- 挂载后,EFI 变量可以作为常规文件进行访问。
内核支持:
- efivarfs 的实现位于 Linux 内核中,代码在 drivers/firmware/efi/efivarfs.c 文件中。
- 通过 CONFIG_EFIVAR_FS 内核配置选项来启用 efivarfs 支持。
工作流程
1、初始化:
- 在内核引导过程中,如果检测到 EFI 环境,内核会初始化 efivarfs 文件系统。
- efivarfs 挂载点通常是 /sys/firmware/efi/efivars。
2、文件操作:
- 每个 EFI 变量在 efivarfs 中表示为一个文件,文件名是变量的名称。
- 读取文件内容等同于读取 EFI 变量的值,写入文件内容则会更新 EFI 变量的值。
- 文件元数据(如权限、所有者)可以表示 EFI 变量的属性。
3、用户空间交互:
- 用户空间工具(如 efibootmgr)可以通过标准文件操作(如 open、read、write、ioctl)来管理 EFI 变量。
- 例如,efibootmgr 工具使用 efivarfs 文件系统来读取和修改引导顺序等 EFI 设置。
实现细节
1、挂载函数:
- efivarfs_mount 函数用于挂载 efivarfs 文件系统,它会创建并初始化挂载点的 super_block 结构。
2、文件系统操作:
- efivarfs 文件系统定义了一组文件系统操作,如 read, write, open, release。
- 这些操作通过调用 EFI 固件接口来完成实际的 EFI 变量读取和写入。
3、内核接口:
- 内核通过 efi.get_variable 和 efi.set_variable 函数与 EFI 固件交互。
- 这些函数使用 EFI 固件接口读取和写入 EFI 变量。
示例
以下是一个简化的示例代码片段,展示了如何使用 efivarfs 读取 EFI 变量:
#include <linux/efi.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
static int __init efivarfs_demo_init(void)
{
// struct efi_variable efi_var;
efi_char16_t var_name[] = L"BootOrder";
efi_guid_t guid = EFI_GLOBAL_VARIABLE_GUID;
unsigned long data_size = 0;
u8 *data = NULL;
efi_status_t status;
int i;
/* 获取变量大小 */
status = efi.get_variable(var_name, &guid, NULL, &data_size, NULL);
if (status != EFI_BUFFER_TOO_SMALL) {
pr_err("Failed to get variable size: %lx\n", status);
return -1;
}
data = kzalloc(data_size, GFP_KERNEL);
if (!data) {
pr_err("Failed to allocate memory for variable data\n");
return -ENOMEM;
}
/* 读取变量 */
status = efi.get_variable(var_name, &guid, NULL, &data_size, data);
if (status != EFI_SUCCESS) {
pr_err("Failed to read variable: %lx\n", status);
kfree(data);
return -1;
}
pr_info("data:\n");
for (i = 0; i < data_size; i++) {
printk("%02x ", data[i]);
}
printk("\n");
pr_info("Successfully read EFI variable\n");
kfree(data);
return 0;
}
static void __exit efivarfs_demo_exit(void)
{
pr_info("Exiting efivarfs demo\n");
}
module_init(efivarfs_demo_init);
module_exit(efivarfs_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("efivarfs demo module");
Makefile
obj-m += efi.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
nvidia@tegra-ubuntu:~$ sudo insmod efi.ko
nvidia@tegra-ubuntu:~$ sudo dmesg | tail -n 21
[ 5449.967521] data:
[ 5449.967529] 08
[ 5449.967531] 00
[ 5449.967532] 01
[ 5449.967533] 00
[ 5449.967533] 00
[ 5449.967534] 00
[ 5449.967534] 02
[ 5449.967535] 00
[ 5449.967535] 03
[ 5449.967536] 00
[ 5449.967536] 04
[ 5449.967536] 00
[ 5449.967537] 05
[ 5449.967538] 00
[ 5449.967538] 06
[ 5449.967539] 00
[ 5449.967539] 07
[ 5449.967540] 00
[ 5449.967541] Successfully read EFI variable
此模块初始化时会尝试读取 EFI 变量 BootOrder 的内容并打印结果。代码展示了如何通过内核接口访问 EFI 变量。实际生产环境中,通常不会直接编写这样的内核模块,而是使用现有的用户空间工具(如 efibootmgr)来管理 EFI 变量。