Featured image of post 多系统启动项配置

多系统启动项配置

系统启动相关知识,包括启动项配置、启动加载器设置、EFI/Grub/rEFInd等、定制多系统启动器主题

缘起

我之前在电脑上装过双系统或者多系统,但对于多个系统的启动项一直云里雾里,经常出现启动项丢失、启动项重复等问题。这次我打算好好研究一下多系统启动项的配置,包括启动加载器的设置、EFI/Grub/rEFInd等,以及如何定制多系统启动器主题。

操作系统启动流程

计算机的启动过程主要分为4步:1. 上电;2. BIOS/UEFI启动;3. 加载启动项;4. 操作系统启动。第1步和第4步一般不需要用户干预,而第2步和第3步需要用户配置。所以这里主要介绍第2步和第3步的内容。

计算机启动流程

BIOS/UEFI启动

BIOS(Basic Input/Output System)和UEFI(Unified Extensible Firmware Interface)是计算机的固件,负责启动计算机并加载操作系统。BIOS是旧的启动方式,UEFI是新的启动方式,UEFI相对于BIOS有更多的功能和更好的性能。无论是UEFI还是BIOS,它们都是主板上的固件。

BIOS启动流程

BIOS一般在比较旧的计算机上使用,其启动流程比较固定,用户定制的自由度比较低。主要分为以下步骤:

  1. BIOS POST(Power-On Self-Test)自检

  2. BIOS读取MBR(Master Boot Record)中的引导程序

  3. 引导程序读取分区表,找到活动分区

  4. 引导程序读取活动分区的引导扇区,加载操作系统

MBR的位置是固定的,一般在硬盘的第一个扇区,大小为512字节。MBR中包含了分区表和引导程序,分区表记录了硬盘的分区信息,引导程序负责加载操作系统。

UEFI启动流程

UEFI也是BIOS的一种,一般在比较新的计算机上使用。其启动流程比较灵活,包含的功能更多,用户定制的自由度比较高。尽管UEFI的功能更多,但其流程更为统一,主要分为以下步骤:

  1. UEFI POST(Power-On Self-Test)自检

  2. UEFI读取EFI分区中的引导程序

  3. 引导程序读取EFI分区中的引导文件,加载操作系统

华硕主板的UEFI界面

相比BIOS,UEFI使用的EFI分区的位置并不固定,甚至一块硬盘上可以有多个EFI分区。

UEFI的引导程序和引导文件都存储在EFI分区中,包含了引导程序、引导文件、驱动程序等。只要引导文件符合UEFI的规范,无论是Windows、Linux还是macOS的引导程序,也无论引导文件放在哪个EFI分区,UEFI都可以找到并加载。

由于UEFI更灵活也更新,所以接下来讲的多系统启动项配置都是基于UEFI的。

概念辨析:启动加载器、启动项、启动文件

在讲多系统启动项配置之前,我们先来梳理一下启动加载器、启动项和启动文件的概念。

  1. 启动加载器(Boot Loader):负责加载操作系统的启动引导程序,每个启动引导程序就是一个启动项(Boot Entry)。常用的启动加载器有Grub(Grub2)、rEFInd、Clover等。

  2. 启动项(Boot Entry):启动加载器加载的每个操作系统就是一个启动项,每个启动项对应一个操作系统。启动项包含了操作系统的启动文件、内核、驱动程序等。

  3. 启动文件(Boot File):启动项中的启动文件,是操作系统的引导程序,负责加载操作系统。Windows的启动文件是bootmgfw.efi,Linux的启动文件是vmlinuz

在此基础上,我们再来看UEFI是如何启动操作系统的。

  1. 每个启动加载器都对应着一个.efi文件,这个.efi文件就是启动加载器的启动文件,负责加载启动加载器。在UEFI完成自检后,会读取EFI分区中的所有.efi文件,如果你在此时进入BIOS设置,看到的启动项就是EFI分区中的.efi文件。这些启动项是有优先顺序的,其顺序可以在BIOS设置中调整。

    例如下图中最高优先级的启动项是rEFInd,其次是Windows Boot Manager,最后是ubuntu。电脑启动时会先试图加载rEFInd,如果rEFInd不存在,再尝试加载Windows Boot Manager,以此类推。

    UEFI启动项

  2. 如果rEFInd成功加载,rEFInd会读取EFI分区中的refind.conf配置文件,根据配置文件生成启动菜单,显示在屏幕上。其中每个菜单项对应一个启动项,启动项一般有两类:一类是操作系统内核镜像,另一类是启动加载器。

    • 操作系统内核镜像:对应的是操作系统的内核文件,例如Linux的vmlinuz文件,Windows的bootmgfw.efi文件。
    • 启动加载器:对应的是启动加载器的.efi文件,例如Grub2的grubx64.efi文件。

    rEFInd加载的启动项

  3. 用户选择一个菜单项后,rEFInd会加载对应的启动项,如果启动项是操作系统内核镜像,rEFInd会直接加载操作系统;如果启动项是启动加载器,rEFInd会加载启动加载器,启动加载器再加载操作系统。

  4. 如果启动加载器要启动一个Linux系统,那么首先会加载Linux的内核文件vmlinuz-xxx,然后加载Linux的初始化文件系统文件initrd-xxx.img或者initramfs-xxx.img,最后加载Linux系统。这两个文件都在Linux系统的/boot目录下。

多系统安装时的启动项配置

上面说了UEFI的引导程序和引导文件都存储在EFI分区中,所以在安装多个系统时,每个系统都会在EFI分区中创建一个引导文件,这样就会有多个引导文件,每个引导文件对应一个系统。这样就可以通过选择不同的引导文件来启动不同的系统。

在安装多个操作系统时,我们可以把各个系统的引导文件放入同一个EFI分区中,也可以把各个系统的引导文件放入不同的EFI分区中。两种方法各有利弊,具体选择哪种方法取决于个人喜好。

  1. 同一个EFI分区

    这种情况下的硬盘分区结构如下:

    同一个EFI分区

    优点:方便管理,只需要一个EFI分区,不需要多个EFI分区。更易于配置启动加载器。

    缺点:所有的引导文件都在一个EFI分区中,万一EFI分区损坏,所有的系统都无法启动。

    安装多个系统的具体操作如下:

    1. 硬盘分区:在硬盘上分出一个EFI分区,大小最好为1GB以上,格式为FAT32。其他分区可以按照各个操作系统的需求而定,例如Windows一般需要至少52GB的空间,Linux一般需要至少20GB的空间。
    2. 安装Windows:如果安装的多个操作系统既有Windows又有Linux,建议先安装Windows。因为Windows的引导程序会覆盖其他已安装操作系统的引导程序,导致先前安装的Linux无法启动。安装Windows时,选择给Windows分配的分区,Windows会自动在EFI分区中创建引导文件。
    3. 依次安装各个Linux:对于每个Linux系统,安装时一般选择自定义分区,手动挂载EFI分区到/boot/efi,挂载为Linux准备的分区到/,可选择创建交换分区和/home分区。安装完成后,Linux会在EFI分区中创建引导文件。
    4. 配置启动加载器:多个操作系统依次安装完成后,我们需要配置启动加载器,让我们可以选择要启动的操作系统。这些内容会在后面详细介绍。
  2. 不同的EFI分区

    这种情况下的硬盘分区结构如下:

    多个EFI分区

    优点:每个系统有自己的EFI分区,互不干扰。一个系统的EFI分区损坏,其他系统不受影响。

    缺点:需要多个EFI分区,管理起来比较麻烦。每个系统的引导文件都在不同的EFI分区中,不易于配置启动加载器。

    安装多个系统的具体操作如下:

    1. 硬盘分区:在硬盘为每个系统分别分出一块空间。暂时不需要创建EFI分区,安装系统时会在每个系统各自的分区中分别创建EFI分区。
    2. 安装Windows:安装Windows时,选择给Windows分配的分区,Windows会自动在该分区中创建EFI分区。
    3. 安装Linux:对于每个Linux系统,安装时选择为这个系统分配的分区,在这个分区中创建大小为200MB左右的EFI分区,挂载点为/boot/efi;剩余的空间挂载为Linux准备的分区,挂载点为/;可选择创建交换分区和/home分区。
    4. 配置启动加载器:多个操作系统依次安装完成后,我们需要配置启动加载器,让我们可以选择要启动的操作系统。这些内容会在后面详细介绍。

启动目录结构

我们来看一下启动目录的结构,以及安装一个操作系统时,这个操作系统会创建哪些文件。这里假设多个操作系统的引导文件都放在同一个EFI分区中,我们安装了3个操作系统:Windows、Ubuntu、Arch Linux。其中Ubuntu系统使用Grub2作为启动加载器,Arch Linux系统使用systemd-boot作为启动加载器。我们还在Ubuntu系统下安装了rEFInd作为启动加载器。

  1. EFI分区

    EFI分区是一个FAT32格式的独立的分区,一般大小为1GB左右。EFI分区中主要包含各个启动加载器的引导文件。

    EFI分区的目录结构如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    EFI
    ├── Boot
    │   └── bootx64.efi
    ├── Microsoft
    │   └── Boot
    │       ├── BCD
    │       ├── BCD.LOG
    │       ├── BCD.LOG1
    │       ├── BCD.LOG2
    │       ├── bootmgfw.efi
    │       ├── bootmgr.efi
    │       └── boot.stl
    ├── systemd
    │   └── systemd-bootx64.efi
    └── ubuntu
        ├── grub.cfg
        ├── grubx64.efi
        ├── mmx64.efi
        └── shimx64.efi
    
    • EFI/Boot/bootx64.efi:UEFI的默认引导文件,一般指向Windows的引导文件EFI/Microsoft/Boot/bootmgfw.efi
    • EFI/Microsoft/Boot/bootmgfw.efi:Windows的引导文件,负责加载Windows操作系统。
    • EFI/systemd/systemd-bootx64.efi:systemd-boot的引导文件,负责加载Linux操作系统。
    • EFI/ubuntu/grubx64.efi:Grub2的引导文件,负责加载Linux操作系统。
    • EFI/ubuntu/shimx64.efi:Secure Boot的引导文件,用于加载Grub2。
    • EFI/ubuntu/mmx64.efi:Grub2的引导文件,用于加载Grub2。
    • EFI/ubuntu/grub.cfg:Grub2的配置文件,包含了各个启动项的配置。
  2. 安装操作系统

    安装Ubuntu时,选择自定义分区,手动挂载EFI分区到/boot/efi,挂载为Linux准备的分区到/。安装完成后,Ubuntu会在EFI分区中创建EFI/ubuntu目录,其中包含了Grub2的引导文件和配置文件。Ubuntu会在/boot目录中创建两个文件:vmlinuzinitrd.img,这两个文件是Linux的内核文件和初始化文件系统文件。Grub2的引导文件会识别这两个文件,并创建启动项,在启动时选择相应的启动项,就可以加载Ubuntu系统。

    在Ubuntu系统中,可以进入/boot/efi/EFI中查看EFI分区的目录结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    /boot
    ├── efi
    │   └── EFI
    │       ├── Boot
    │       ├── Microsoft
    │       ├── systemd
    │       └── ubuntu
    ├── vmlinuz
    └── initrd.img
    

    注意,虽然/boot/efi/EFI/boot下,但/boot/efi/EFI中的文件在EFI分区里,但/boot下的其他文件在Ubuntu系统的分区里。/boot下的vmlinuzinitrd.img文件在其他系统中是无法访问的,而若其他系统也挂载了EFI分区,那么其他系统也可以访问/boot/efi/EFI中的文件。

    如果继续安装更多的操作系统,一般操作系统都会让你选择启动加载器,你可以选择使用Grub2,也可以选择systemd-boot,或者其他启动加载器。下面介绍一下启动加载器的配置。

启动加载器

启动加载器(Boot Loader)负责加载操作系统的启动引导程序,每个启动引导程序就是一个启动项(Boot Entry)。常用的启动加载器有Grub(Grub2)、rEFInd、Clover等。这里主要介绍Grub2和rEFInd。

Grub2

Grub2(GNU GRand Unified Bootloader 2)是Linux系统上常用的启动加载器,功能强大,支持多系统启动。Grub2的配置文件是/boot/grub/grub.cfg,一般由grub-mkconfig命令生成。

Grub2的配置文件比较复杂,但功能强大,可以定制各种启动项。Grub2的主题也可以定制,但相对比较复杂。

Grub2的配置

  1. 配置文件

    Grub2的主配置文件是/boot/grub/grub.cfg,可以通过编辑这个文件来配置Grub2的启动项。

    1
    
    sudo vim /boot/grub/grub.cfg
    

    不过一般不建议直接编辑grub.cfg文件,因为这个文件是由grub-mkconfig命令生成的,如果直接编辑grub.cfg文件,下次更新Grub2时会被覆盖。建议通过配置/etc/default/grub文件,以及/etc/grub.d/目录下的配置文件来配置Grub2。

  2. 启动项的配置

    Grub2的启动项配置比较复杂,例如:

    1
    2
    3
    4
    5
    
    GRUB_DEFAULT=0
    GRUB_TIMEOUT=5
    GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
    GRUB_CMDLINE_LINUX=""
    
    • GRUB_DEFAULT:默认启动项的编号,从0开始。
    • GRUB_TIMEOUT:启动菜单显示的时间,单位为秒。
    • GRUB_DISTRIBUTOR:系统的发行商。
    • GRUB_CMDLINE_LINUX_DEFAULT:Linux内核的默认启动参数。
    • GRUB_CMDLINE_LINUX:Linux内核的启动参数。
  3. 定制主题

    Grub2的主题可以定制,可以把想要的主题文件放在/boot/grub/themes目录下。主题目录下包含了主题文件、字体文件、背景图片等,而且有一个名为theme.txt的配置文件,可以配置主题的各种属性。

    在配置文件/etc/default/grub中可以指定主题文件:

    1
    
    GRUB_THEME="/boot/grub/themes/theme-name/theme.txt"
    

更多的Grub2配置可以参考Grub2配置

rEFInd

rEFInd是一个基于UEFI的启动加载器,功能强大,支持多系统启动。rEFInd的配置文件是/boot/efi/EFI/refind/refind.conf,一般由refind-install命令生成。

rEFInd的配置文件比较简单,但功能强大,可以定制各种启动项。rEFInd的主题也可以定制,相对比较简单。

rEFInd安装

rEFInd的安装比较简单,可以直接下载rEFInd的安装包,解压到EFI分区中。也可以使用refind-install命令安装rEFInd。

1
refind-install

安装完成后,rEFInd会在EFI分区中创建EFI/refind目录,其中包含了rEFInd的引导文件和配置文件。

rEFInd配置

rEFInd的配置文件是/boot/efi/EFI/refind/refind.conf,可以通过编辑这个文件来配置rEFInd的启动项。

1
sudo vim /boot/efi/EFI/refind/refind.conf
  1. 启动项的配置

    rEFInd中启动项的配置比较简单,例如:

    1
    2
    3
    
    timeout 5
    hideui singleuser
    scanfor manual,external
    
    • timeout:启动菜单显示的时间,单位为秒。
    • hideui:隐藏启动菜单,只显示单用户模式。
    • scanfor:扫描启动项的方式,可以扫描手动配置的启动项,也可以扫描外部设备的启动项。
  2. 定制主题

    rEFInd的主题可以定制,可以把想要的主题文件放在/boot/efi/EFI/refind/theme目录下。主题文件是一个CSS文件,可以修改颜色、字体、背景等。

    在配置文件/boot/efi/EFI/refind/refind.conf中可以指定主题文件:

    1
    
    include themes/theme-name/theme.conf
    
  3. 屏蔽启动项

    有时候rEFInd会扫描到很多不需要的启动项,例如如果你在安装系统时选择了使用Grub2作为启动加载器(Deepin, Fedora等系统会默认安装Grub2,而且似乎没法在安装时取消),那么rEFInd会扫描到Grub2的启动项。你可以通过配置文件/boot/efi/EFI/refind/refind.conf来屏蔽这些启动项。rEFInd可以选择忽略某些文件(dont_scan_files)、工具(dont_scan_tools)、目录(dont_scan_dirs)和磁盘(dont_scan_volumes)。

    1
    2
    
    dont_scan_files shim.efi,mmx64.efi
    dont_scan_dirs ESP:/EFI/ubuntu,EFI/deepin,EFI/fedora
    

问题解决

  1. 在SD卡或U盘上安装系统

    • 如果你的电脑有SD卡插槽,你可以选择把系统安装在SD卡上。
    • 跟上面讲的安装多个操作系统的方法相似,在SD卡上安装操作系统也有两种EFI分区方式,一种是在SD卡上创建一个EFI分区,另一种是在安装系统时把硬盘上的EFI分区挂载到/boot/efi
    • 对于/boot分区,可以选择直接在SD卡上创建,也可以先在硬盘上创建一个大小约为500M的ext4分区,然后在安装系统时把它挂载到/boot/boot里主要存放内核文件和引导文件,通常200M左右的空间就够了,但是如果你选择备份多个内核,那么就需要更大的空间。Fedora系统会限制/boot分区不小于512M。
    • 如果你选择把/boot分区放在SD卡上,你可能会遇到一些安全方面的限制,例如Secure Boot可能会阻止从SD卡启动。你需要注册注册密钥,或者关闭Secure Boot。
    • 我一般选择把EFI分区和/boot分区都放在硬盘上。

    我还没试过在U盘上安装系统,不过原理应该和在SD卡上安装是一样的。

  2. Fedora无法启动

    在SD卡上安装了Fedora系统后,我遇到了系统无法启动的问题。rEFInd可以扫描到Fedora的内核文件vmlinuz-xxxinitramfs-xxx.img,但是启动到一半会卡住,报错:

    1
    2
    3
    4
    5
    6
    7
    
    Failed to switch root: Specified switch root path /sysroot does not seem to be an OS tree. os-release file is missing.
    initrd-switch-root.service: Main process exited, code=exited, status=1/FAILURE
    initrd-switch-root.service: Failed with result 'exit-code'.
    Failed to start initrd-switch-root.service: Switch Root.
    Startup finished in 1.073s (kernel) + 1.000s (initrd) + 1.000s (userspace) = 3.073s.
    initrd-switch-root.service: Triggering OnFailure= dependencies.
    Started emergency.service - Emergency Shell.
    

    在Emergency Shell中查看后发现,/sysroot目录为空,没有任何文件。而正常情况下,加载内核和文件系统初始化后,/sysroot目录应该包含操作系统的文件。

    所以这个问题是因为在启动内核和初始化文件系统时,SD卡没有被正确挂载。你可以在Emergency Shell中手动挂载SD卡,然后exit退出Emergency Shell,系统就可以正常启动了。

    1
    2
    
    mount /dev/sda1 /sysroot
    exit
    

    但显然这不是一个好的解决方案,因为每次启动都要手动挂载SD卡。所以正确的解决方案是让rEFInd在启动内核时正确挂载SD卡,这需要给启动选项添加一个root参数,指定根目录。你可以在rEFInd的配置文件/boot/efi/EFI/refind/refind.conf中添加一个带有options参数的菜单项。

    1
    2
    3
    4
    5
    6
    
    menuentry "Fedora" {
        volume "SD-boot"
        loader /vmlinuz-xxx
        initrd /initramfs-xxx.img
        options "root=/dev/sda1"
    }
    

    然而,这个解决方案也不是很好,因为

    • 在每次更新内核后,你都需要手动修改rEFInd的配置文件。
    • 如果你有多个内核,这个菜单项不能自动生成子菜单,你需要手动添加每个内核的菜单项。

    所以,更好的解决方案是让整个boot分区里的内核文件和引导文件都使用整个选项。做法是在/boot分区的根目录下创建一个refind_linux.conf文件,然后在rEFInd的配置文件/boot/efi/EFI/refind/refind.conf中添加一个root参数。

    1
    
    "Boot with standard options" "root=/dev/sda1"
    

    问题终于解决了!

comments powered by Disqus