本章开始分析grub-install的源码,该命令最终会将grub的映像存入引导扇区中,最简单的命令如下,
grub2-install /dev/sda
下面从main函数开始看,有一部分代码已经在《grub-mkimage源码分析》中分析了,本章包括后面的章节对这些代码就直接略过了。
grub2-install第一部分
util/grub-install.c
int main (int argc, char *argv[]){
int is_efi = 0;
const char *efi_distributor = NULL;
const char *efi_file = NULL;
char **grub_devices;
grub_fs_t grub_fs;
grub_device_t grub_dev = NULL;
enum grub_install_plat platform;
char *grubdir, *device_map;
char **curdev, **curdrive;
char **grub_drives;
char *relative_grubdir;
char **efidir_device_names = NULL;
grub_device_t efidir_grub_dev = NULL;
char *efidir_grub_devname;
int efidir_is_mac = 0;
int is_prep = 0;
const char *pkgdatadir;
grub_util_host_init (&argc, &argv);
product_version = xstrdup (PACKAGE_VERSION);
pkgdatadir = grub_util_get_pkgdatadir ();
label_font = grub_util_path_concat (2, pkgdatadir, "unicode.pf2");
argp_parse (&argp, argc, argv, 0, 0, 0);
grub_util_load_config (&config);
...
grub_util_host_init设置全局的应用程序名,product_version表示grub的版本,本文使用的为2.02版本,grub_util_get_pkgdatadir函数默认返回的路径”/usr/local/share/grub”,该目录下存放了grub的数据文件,例如编码文件unicode.pf2。
argp_parse函数用于解析命令行的参数,和《grub-mkimage源码分析》中的分析的不同之处在于,其解析结果主要保存在一些全局变量中。
最后的grub_util_load_config函数用于解析配置文件,将结果保存在参数config中。
grub2-install->grub_util_load_config
grub-core/osdep/unix/config.c
void grub_util_load_config (struct grub_util_config *cfg) {
pid_t pid;
const char *argv[4];
char *script, *ptr;
const char *cfgfile, *iptr;
FILE *f = NULL;
int fd;
const char *v;
memset (cfg, 0, sizeof (*cfg));
v = getenv ("GRUB_ENABLE_CRYPTODISK");
if (v && v[0] == 'y' && v[1] == '\0')
cfg->is_cryptodisk_enabled = 1;
v = getenv ("GRUB_DISTRIBUTOR");
if (v)
cfg->grub_distributor = xstrdup (v);
cfgfile = grub_util_get_config_filename ();
argv[0] = "sh";
argv[1] = "-c";
script = xmalloc (4 * strlen (cfgfile) + 300);
ptr = script;
memcpy (ptr, ". '", 3);
ptr += 3;
for (iptr = cfgfile; *iptr; iptr++) {
if (*iptr == '\\') {
memcpy (ptr, "'\\''", 4);
ptr += 4;
continue;
}
*ptr++ = *iptr;
}
strcpy (ptr, "'; printf \"GRUB_ENABLE_CRYPTODISK=%s\\nGRUB_DISTRIBUTOR=%s\\n\" "
"\"$GRUB_ENABLE_CRYPTODISK\" \"$GRUB_DISTRIBUTOR\"");
argv[2] = script;
argv[3] = '\0';
pid = grub_util_exec_pipe (argv, &fd);
if (pid)
f = fdopen (fd, "r");
if (f) {
grub_util_parse_config (f, cfg, 1);
fclose (f);
}
if (pid) {
close (fd);
waitpid (pid, NULL, 0);
}
if (f)
return;
f = grub_util_fopen (cfgfile, "r");
if (f) {
grub_util_parse_config (f, cfg, 0);
fclose (f);
}
}
首先读取环境变量GRUB_ENABLE_CRYPTODISK和GRUB_DISTRIBUTOR设置返回参数cfg相应的值。grub_util_get_config_filename获得默认的配置文件路径”/usr/local/etc/default/grub”。
接下来分配内存空间script,用于存放脚本命令。接下来首先存入的”.”表示会逐行执行文件中的每行脚本命令。然后通过for循环拷贝配置文件路径cfgfile到script中,再将GRUB_ENABLE_CRYPTODISK和GRUB_DISTRIBUTOR的信息也添加到脚本命令中。完成后,就通过grub_util_exec_pipe函数执行刚刚编写的脚本script,参数fd指向的文件用于保存返回结果。
执行完成后以只读方式打开该文件,调用grub_util_parse_config函数对执行结果进行解析并将结果写入cfg中。
grub_util_parse_config是主要的解析函数。其中waitpid用于等待grub_util_exec_pipe函数中子进程的返回。
如果前面的方法失败了,则可能配置文件中内容并不是脚本,而是可读的配置条目,此时打开该文件,并直接通过grub_util_parse_config函数解析其中的配置条目。
grub2-install->grub_util_load_config->grub_util_exec_pipe
grub-core/osdep/unix/exec.c
pid_t grub_util_exec_pipe (const char *const *argv, int *fd) {
int pipe_fd[2];
pid_t pid;
*fd = 0;
pipe (pipe_fd);
pid = fork ();
if (pid == 0) {
setenv ("LC_ALL", "C", 1);
close (pipe_fd[0]);
dup2 (pipe_fd[1], STDOUT_FILENO);
close (pipe_fd[1]);
execvp ((char *) argv[0], (char **) argv);
exit (127);
} else {
close (pipe_fd[1]);
*fd = pipe_fd[0];
return pid;
}
}
传入的参数argv[0]为sh,argv[1]为-c,表示后续的字符串为sh可以执行的脚本指令。首先创建管道pipe_fd,调用fork函数创建子进程,在子进程中首先关闭管道的读端pipe_fd[0],复制写端到标准输出描述符STDOUT_FILENO上,再关闭写端pipe_fd[1](这里未真正关闭),然后通过execvp执行sh脚本,也即argv[2]中字符串表示的脚本命令,其结果会写入标准输出描述符上,也即pipe_fd[1],执行成功后,退出子进程。父进程首先关闭管道的写端pipe_fd[1],将参数fd指向管道的读端,最后返回父进程ID。
grub2-install->grub_util_load_config->grub_util_parse_config
util/config.c
void grub_util_parse_config (FILE *f, struct grub_util_config *cfg, int simple) {
char *buffer = NULL;
size_t sz = 0;
while (getline (&buffer, &sz, f) >= 0) {
const char *ptr;
for (ptr = buffer; *ptr && grub_isspace (*ptr); ptr++);
if (grub_strncmp (ptr, "GRUB_ENABLE_CRYPTODISK=",
sizeof ("GRUB_ENABLE_CRYPTODISK=") - 1) == 0) {
ptr += sizeof ("GRUB_ENABLE_CRYPTODISK=") - 1;
if (*ptr == '"' || *ptr == '\'')
ptr++;
if (*ptr == 'y')
cfg->is_cryptodisk_enabled = 1;
continue;
}
if (grub_strncmp (ptr, "GRUB_DISTRIBUTOR=", sizeof ("GRUB_DISTRIBUTOR=") - 1) == 0) {
char *optr;
enum { NONE, SNGLQUOT, DBLQUOT } state;
ptr += sizeof ("GRUB_DISTRIBUTOR=") - 1;
if (simple) {
char *ptr2;
free (cfg->grub_distributor);
cfg->grub_distributor = xstrdup (ptr);
for (ptr2 = cfg->grub_distributor + grub_strlen (cfg->grub_distributor) - 1;
ptr2 >= cfg->grub_distributor && (*ptr2 == '\r' || *ptr2 == '\n'); ptr2--);
ptr2[1] = '\0';
continue;
}
...
}
}
}
首先通过while循环逐行读取文件中的内容至buffer中,第一个for循环利用grub_isspace函数删除该行中的无效内容,例如空格,换行符等等。再往下的第一个if语句判断如果该行是例如GRUB_ENABLE_CRYPTODISK=”y”,则覆盖之前环境变量的配置,将is_cryptodisk_enabled设为1。第二个if语句类似,将”GRUB_DISTRIBUTOR=”后面跟着的字符串拷贝到grub_distributor中。假设传入的simple为1,内部的for循环用于将结尾的换行符替换成结束符’\0’,如果传入的simple为0,表示解析过程较为复杂,需要通过脚本命令格式解析最终的内容,这里省略了这部分代码。
grub2-install第二部分
util/grub-install.c
...
if (!bootloader_id && config.grub_distributor) {
char *ptr;
bootloader_id = xstrdup (config.grub_distributor);
for (ptr = bootloader_id; *ptr && *ptr != ' '; ptr++)
if (*ptr >= 'A' && *ptr <= 'Z')
*ptr = *ptr - 'A' + 'a';
*ptr = '\0';
}
if (!bootloader_id || bootloader_id[0] == '\0') {
free (bootloader_id);
bootloader_id = xstrdup ("grub");
}
if (!grub_install_source_directory) {
if (!target) {
const char * t;
t = get_default_platform ();
target = xstrdup (t);
}
grub_install_source_directory = grub_util_path_concat (2,
grub_util_get_pkglibdir (), target);
}
platform = grub_install_get_target (grub_install_source_directory);
switch (platform) {
case GRUB_INSTALL_PLATFORM_I386_PC:
if (!disk_module)
disk_module = xstrdup ("biosdisk");
break;
...
}
switch (platform) {
case GRUB_INSTALL_PLATFORM_I386_PC:
case GRUB_INSTALL_PLATFORM_SPARC64_IEEE1275:
if (!install_device)
grub_util_error ("%s", _("install device isn't specified"));
break;
...
}
...
如果config中的grub_distributor存在,就将其设置为bootloader_id,并将大写转换为小写,否则设置默认值为grub。bootloader_id和efi接口相关,这里不过多关心。
接下里假设grub_install_source_directory和target输入参数都未提供,此时通过get_default_platform返回平台信息,对于i386体系,返回”i386-pc”字符串。grub_util_get_pkglibdir函数最终返回”/usr/local/lib/grub”,因此最后的grub_install_source_directory被设置为”/usr/local/lib/grub/i386-pc”。
grub_install_get_target获得cpu和platform对应的索引,对于i386,返回GRUB_INSTALL_PLATFORM_I386_PC。再往下,如果参数未提供,就设置disk_module为”biosdisk”,install_device默认由参数提供,例如”/dev/sda”。
grub2-install->grub_install_get_target
util/grub-install-common.c
enum grub_install_plat grub_install_get_target (const char *src) {
char *fn;
grub_util_fd_t f;
char buf[8192];
ssize_t r;
char *c, *pl, *p;
size_t i;
fn = grub_util_path_concat (2, src, "modinfo.sh");
f = grub_util_fd_open (fn, GRUB_UTIL_FD_O_RDONLY);
r = grub_util_fd_read (f, buf, sizeof (buf) - 1);
grub_util_fd_close (f);
buf[r] = '\0';
c = strstr (buf, "grub_modinfo_target_cpu=");
pl = strstr (buf, "grub_modinfo_platform=");
c += sizeof ("grub_modinfo_target_cpu=") - 1;
pl += sizeof ("grub_modinfo_platform=") - 1;
for (p = c; *p && !grub_isspace (*p); p++);
*p = '\0';
for (p = pl; *p && !grub_isspace (*p); p++);
*p = '\0';
for (i = 0; i < ARRAY_SIZE (platforms); i++)
if (strcmp (platforms[i].cpu, c) == 0 && strcmp (platforms[i].platform, pl) == 0) {
free (fn);
return i;
}
}
对于i386而言,这里传入的参数src为”/usr/local/lib/grub/i386-pc”,接下来通过grub_util_fd_open函数打开文件”/usr/local/lib/grub/i386-pc/modinfo.sh”,并通过grub_util_fd_read函数将其读入内存buf中。然后通过strstr函数查找”grub_modinfo_target_cpu=”和”grub_modinfo_platform=”所在位置,进而获得等号后面跟着的值,分别赋值给变量c和pl,最后通过for循环在platforms数组中比较两者的值,如果相等,就返回数组的索引。platforms的定义如下,
static struct
{
const char *cpu;
const char *platform;
} platforms[GRUB_INSTALL_PLATFORM_MAX] =
{
[GRUB_INSTALL_PLATFORM_I386_PC] = { "i386", "pc" },
[GRUB_INSTALL_PLATFORM_I386_EFI] = { "i386", "efi" },
[GRUB_INSTALL_PLATFORM_I386_QEMU] = { "i386", "qemu" },
[GRUB_INSTALL_PLATFORM_I386_COREBOOT] = { "i386", "coreboot" },
[GRUB_INSTALL_PLATFORM_I386_MULTIBOOT] = { "i386", "multiboot" },
[GRUB_INSTALL_PLATFORM_I386_IEEE1275] = { "i386", "ieee1275" },
[GRUB_INSTALL_PLATFORM_X86_64_EFI] = { "x86_64", "efi" },
[GRUB_INSTALL_PLATFORM_I386_XEN] = { "i386", "xen" },
[GRUB_INSTALL_PLATFORM_X86_64_XEN] = { "x86_64", "xen" },
[GRUB_INSTALL_PLATFORM_MIPSEL_LOONGSON] = { "mipsel", "loongson" },
[GRUB_INSTALL_PLATFORM_MIPSEL_QEMU_MIPS] = { "mipsel", "qemu_mips" },
[GRUB_INSTALL_PLATFORM_MIPS_QEMU_MIPS] = { "mips", "qemu_mips" },
[GRUB_INSTALL_PLATFORM_MIPSEL_ARC] = { "mipsel", "arc" },
[GRUB_INSTALL_PLATFORM_MIPS_ARC] = { "mips", "arc" },
[GRUB_INSTALL_PLATFORM_SPARC64_IEEE1275] = { "sparc64", "ieee1275" },
[GRUB_INSTALL_PLATFORM_POWERPC_IEEE1275] = { "powerpc", "ieee1275" },
[GRUB_INSTALL_PLATFORM_IA64_EFI] = { "ia64", "efi" },
[GRUB_INSTALL_PLATFORM_ARM_EFI] = { "arm", "efi" },
[GRUB_INSTALL_PLATFORM_ARM64_EFI] = { "arm64", "efi" },
[GRUB_INSTALL_PLATFORM_ARM_UBOOT] = { "arm", "uboot" },
};
因此对于i386-pc而言,最后返回的数组索引为GRUB_INSTALL_PLATFORM_I386_PC。
grub2-install第三部分
util/grub-install.c
...
if (!bootdir)
bootdir = grub_util_path_concat (3, "/", rootdir, GRUB_BOOT_DIR_NAME);
char * t = grub_util_path_concat (2, bootdir, GRUB_DIR_NAME);
grub_install_mkdir_p (t);
grubdir = grub_canonicalize_file_name (t);
free (t);
device_map = grub_util_path_concat (2, grubdir, "device.map");
device_map_check_duplicates (device_map);
grub_util_biosdisk_init (device_map);
...
grub_install_copy_files (grub_install_source_directory,
grubdir, platform);
char *envfile = grub_util_path_concat (2, grubdir, "grubenv");
if (!grub_util_is_regular (envfile))
grub_util_create_envblk_file (envfile);
...
首先,如果rootdir未提供,则默认的grub根路径bootdir为”/boot”。根路径下grub文件所在的目录t默认为”/boot/grub”,grub_install_mkdir_p检查”/boot/grub”文件夹是否存在,如果不存在则创建。
接下来获取grub目录”/boot/grub”下的device_map文件,该文件存储了设备信息,然后通过device_map_check_duplicates函数检查device.map文件中是否有重复定义,该函数读取device.map文件,并按字母排序,比较前后两个信息是否相同,删除多余的信息。检查完毕后,通过grub_util_biosdisk_init函数再次读取device_map文件,将信息保存到全局变量中。
往下省略了部分初始化函数。
根据前面的分析,对于i386体系而言,接下来的grub_install_source_directory被赋值为”/usr/local/lib/grub/i386-pc”,grub目录grubdir为”/boot/grub”,platform是在grub_install_get_target函数中获得的数组索引GRUB_INSTALL_PLATFORM_I386_PC,grub_install_copy_files函数传入这些参数,将”/usr/local”目录下的相关文件拷贝到”/boot/grub”目录中。
最后通过grub_util_create_envblk_file函数创建”/boot/grub/grubenv.new”文件,该文件包含1024个字节,对其进行简单数据的初始化,其实就是加了头部的注释在该文件中。
grub2-install->grub_util_biosdisk_init
grub-core/kern/emu/hostdisk.c
void grub_util_biosdisk_init (const char *dev_map) {
read_device_map (dev_map);
grub_disk_dev_register (&grub_util_biosdisk_dev);
}
首先通过read_device_map函数读取device.map文件dev_map,将驱动号和设备号存入全局的map数组中。然后通过grub_disk_dev_register函数将grub_util_biosdisk_dev插入到全局的grub_disk_dev_list链表中。
grub2-install->grub_util_biosdisk_init->read_device_map
grub-core/kern/emu/hostdisk.c
static void read_device_map (const char *dev_map) {
FILE *fp;
char buf[1024];
int lineno = 0;
fp = grub_util_fopen (dev_map, "r");
while (fgets (buf, sizeof (buf), fp)) {
char *p = buf;
char *e;
char *drive_e, *drive_p;
int drive;
lineno++;
while (*p && grub_isspace (*p))
p++;
if (*p == '\0' || *p == '#')
continue;
p++;
drive = find_free_slot ();
e = p;
p = strchr (p, ')');
map[drive].drive = 0;
if ((e[0] == 'f' || e[0] == 'h' || e[0] == 'c') && e[1] == 'd') {
char *ptr;
for (ptr = e + 2; ptr < p; ptr++)
if (!grub_isdigit (*ptr))
break;
if (ptr == p) {
map[drive].drive = xmalloc (p - e + sizeof ('\0'));
strncpy (map[drive].drive, e, p - e + sizeof ('\0'));
map[drive].drive[p - e] = '\0';
}
}
drive_e = e;
drive_p = p;
map[drive].device_map = 1;
p++;
while (*p && grub_isspace (*p))
p++;
e = p;
while (*e && ! grub_isspace (*e))
e++;
*e = '\0';
map[drive].device = grub_canonicalize_file_name (p);
if (! map[drive].device)
map[drive].device = xstrdup (p);
...
}
fclose (fp);
}
为了方便后面的分析,这里举个device.map文件的例子,如下所示,
(fd0) /dev/fd0
(hd0) /dev/hda
(hd1) /dev/hdb
(hd2) /dev/sda
(hd3) /dev/sdb
打开该文件后,通过while循环逐行读取文件中的内容,然后通过grub_isspace函数找到第一个有效字符,第一个有效字符为左括号,因此需要将p指针递增一个位置。再通过find_free_slot函数在全局的map数组中找到空闲位置并返回对应的索引drive,find_free_slot函数从左到右遍历map数组,返回数组中第一个drive字段不为0的元素下标。
如果字符串e以”fd”、”hd”、”cd”开头,并紧跟着以数字结尾,则将该硬盘号存入全局数组map中的drive字段中,例如第一块硬盘hd0。其中grub_isdigit函数用于判断字符是否为数字。
设置完设备号后,继续读取文件,找到右扩号后的第一个有效字符,拷贝后续的文件名到map数组的device字段中,该文件名就是设备文件名,例如”/dev/hda”。最后省略的代码和hostdisk有关,这里不关心。
grub2-install->grub_install_copy_files
util/grub-install-common.c
void grub_install_copy_files (const char *src, const char *dst, enum grub_install_plat platid) {
char *dst_platform, *dst_locale, *dst_fonts;
const char *pkgdatadir = grub_util_get_pkgdatadir ();
char *themes_dir;
char *platform;
platform = xasprintf ("%s-%s", platforms[platid].cpu,
platforms[platid].platform);
dst_platform = grub_util_path_concat (2, dst, platform);
free (platform);
dst_locale = grub_util_path_concat (2, dst, "locale");
dst_fonts = grub_util_path_concat (2, dst, "fonts");
grub_install_mkdir_p (dst_platform);
grub_install_mkdir_p (dst_locale);
clean_grub_dir (dst);
clean_grub_dir (dst_platform);
clean_grub_dir (dst_locale);
if (install_modules.is_default)
copy_by_ext (src, dst_platform, ".mod", 1);
else{
...
}
const char *pkglib_DATA[] = {"efiemu32.o", "efiemu64.o", "moddep.lst", "command.lst",
"fs.lst", "partmap.lst", "parttool.lst", "video.lst",
"crypto.lst", "terminal.lst", "modinfo.sh" };
size_t i;
for (i = 0; i < ARRAY_SIZE (pkglib_DATA); i++) {
char *srcf = grub_util_path_concat (2, src, pkglib_DATA[i]);
char *dstf = grub_util_path_concat (2, dst_platform, pkglib_DATA[i]);
if (i == 0 || i == 1)
grub_install_compress_file (srcf, dstf, 0);
else
grub_install_compress_file (srcf, dstf, 1);
free (srcf);
free (dstf);
}
if (install_locales.is_default) {
char *srcd = grub_util_path_concat (2, src, "po");
copy_by_ext (srcd, dst_locale, ".mo", 0);
copy_locales (dst_locale);
free (srcd);
} else {
...
}
if (install_themes.is_default){
install_themes.is_default = 0;
install_themes.n_entries = 1;
install_themes.entries = xmalloc (2 * sizeof (install_themes.entries[0]));
install_themes.entries[0] = xstrdup ("starfield");
install_themes.entries[1] = NULL;
}
themes_dir = grub_util_path_concat (2, grub_util_get_pkgdatadir (),
"themes");
for (i = 0; i < install_themes.n_entries; i++) {
char *srcf = grub_util_path_concat (3, themes_dir, install_themes.entries[i], "theme.txt");
if (grub_util_is_regular (srcf)) {
char *srcd = grub_util_path_concat (2, themes_dir, install_themes.entries[i]);
char *dstd = grub_util_path_concat (3, dst, "themes", install_themes.entries[i]);
grub_install_mkdir_p (dstd);
copy_all (srcd, dstd);
free (srcd);
free (dstd);
}
free (srcf);
}
free (themes_dir);
if (install_fonts.is_default) {
install_fonts.is_default = 0;
install_fonts.n_entries = 1;
install_fonts.entries = xmalloc (2 * sizeof (install_fonts.entries[0]));
install_fonts.entries[0] = xstrdup ("unicode");
install_fonts.entries[1] = NULL;
}
grub_install_mkdir_p (dst_fonts);
for (i = 0; i < install_fonts.n_entries; i++) {
char *srcf = grub_util_path_concat_ext (2, pkgdatadir, install_fonts.entries[i], ".pf2");
char *dstf = grub_util_path_concat_ext (2, dst_fonts, install_fonts.entries[i], ".pf2");
grub_install_compress_file (srcf, dstf, 0);
free (srcf);
free (dstf);
}
free (dst_platform);
free (dst_locale);
free (dst_fonts);
}
grub_util_get_pkgdatadir函数默认返回的路径”/usr/local/share/grub”,传入的参数platid为数组platforms的索引值,由前面的grub_install_get_target函数计算为GRUB_INSTALL_PLATFORM_I386_PC,因此这里最终计算的platform就为i386-pc。获得platform路径后,再拼接成目标地址dst_platform为”/boot/grub/i386-pc”。依次类推,dst_locale为”/boot/grub/locale”,dst_fonts为”/boot/grub/fonts”。然后通过grub_install_mkdir_p函数检查并创建这些文件目录,通过clean_grub_dir清空文件夹下带特定拓展名的文件,例如.img、.mod文件等等。
接下来的install_modules可以在命令行中改变,默认定义如下,
struct install_list install_modules = { 1, 0, 0, 0 };
struct install_list
{
int is_default;
char **entries;
size_t n_entries;
size_t n_alloc;
};
其is_default字段默认值为1,因此通过copy_by_ext函数将src也即”/usr/local/lib/grub/i386-pc”目录下的以”.mod”为拓展名的文件拷贝到dst_platform也即”/boot/grub/i386-pc”目录中。然后遍历pkglib_DATA数组中的文件名,依次通过grub_install_compress_file函数将文件从src目录拷贝到dst_platform目录中,其内部通过grub_install_copy_file函数进行单个文件的拷贝操作,传入的最后一个参数为1表示如果存在压缩函数,需要对数据进行压缩,并且必须拷贝。
再往下的install_locales定义和install_modules一样,is_default字段默认值为1,因此接下来将”usr/local/lib/grub/i386-pc/po”目录下的所有以”.mo”为拓展名的文件拷贝到”/boot/grub/locale”目录中,最后调用的copy_locales函数会将”/boot/grub/locale”中的文件更名再拷贝到”/usr/local/share/locale”目录中。
install_themes和install_modules的定义也类似,字段is_default默认值为1,因此接下来初始化该结构,目录themes_dir最终拼接为”/usr/local/share/grub/themes”,然后将该目录下的starfield目录中的所有内容拷贝到”/boot/grub/themes/starfield”中。
install_fonts和install_themes也类似,最终将”/usr/local/share/grub/unicode/.pf2”文件拷贝到”/boot/grub/fonts/.ps2”文件中。