Linux基础知识 ·

Boot Loader之Grub、Grub2

[隐藏]

 

在 BIOS 完成自检后,引导系统的任务就交给 boot loader 了。所以没有 boot loader 的话,那么 kernel 就无法载入,系统也就不能正常启动了。

而在 Linux 中,使用的最多了就是 grub 了,较老的发行版还是在使用 grub ,而较新的发行版大多都使用了 grub2 。

boot loader 的两个 stage

在 BIOS 读完信息后,接下来就会到第一个启动装置的 MBR 去读取 boot loader 了。这个 boot loader 可以具有菜单功能、直接加载核心文件以及控制权移交的功能等, 系统必须要有 loader 才有办法加载该操作系统的核心。

但是, MBR 是整个硬盘的第一个 sector 内的一个区块,充整个大小也才 446 bytes 而已。 我们的 loader 功能这么强,光是程序代码与配置数据远远大于 446 bytes 的容量!

为了解决这个问题,所以 Linux 将 boot loader 的程序码运行与配置值加载分成两个阶段 (stage) 来运行:

  • Stage 1:运行 boot loader 主程序:
    第一阶段为运行 boot loader 的主程序,这个主程序必须要被安装在启动区,亦即是 MBR 或者是 boot sector 。但如前所述,因为 MBR 实在太小了,所以,MBR 或 boot sector 通常仅安装 boot loader 的最小主程序, 并没有安装 loader 的相关配置文件;
  • Stage 2:主程序加载配置文件:
    第二阶段为通过 boot loader 加载所有配置文件与相关的环境参数文件 (包括文件系统定义与主要配置文件menu.lst), 一般来说,配置文件都在 /boot 底下。

这些与 grub 有关的文件都放置到 /boot/grub 中,grub2 便是放在/boot/grub2中,那我们就来看看有哪些文件吧!

从上面的说明可以知道/boot/grub2/ 目录下最重要的就是配置文件(grub2.cfg) 以及各种系统的定义文件!我们的loader 读取了这些系统定义数据后,就能够认识文件系统并读取在该系统内的内核文件了。

而在 grub 的配置文件中,其主配置文件应该是 menu.lst 的,只是在 Red Hat 系列里面被定义成为 /boot/grub.conf 而已。

grub 与 grub2 的差异虽然是非常大的,不过 grub2 为了兼容性,其配置方法与文件定义与 grub 其实是非常相似的。

grub配置文件/boot/grub/grub.cfg

grub 的优点挺多的,包括有:

  • 认识与支持较多的文件系统,并且可以使用 grub 的主程序直接在文件系统中搜寻内核文件名。
  • 开机的时候,可以『自行编辑与修改开机设定项目』,类似 bash 的指令模式。
  • 可以动态搜寻配置文件,而不需要在修改配置文件后重新安装grub 。亦即是我们只要修改完 /boot/grub/grub.cfg 里头的设定后,下次开机就生效了!

上面第三点其实就是Stage 1, Stage 2 分别安装在MBR (主程序) 与文件系统当中(配置文件与环境定义文件) 的原因!

磁盘与分区在grub中的代号

安装在 MBR 的 grub 主程序,最重要的任务之一就是从磁盘当中加载内核文件, 让内核能够顺利的驱动整个系统的硬件。

所以, grub 必须要认识硬盘才行!而 grub 对硬盘的代号配置与传统的 Linux 磁盘代号可完全是不同的!grub 对硬盘的识别使用的是如下的代号:

  • 硬盘代号以小括号 ( ) 包起来;
  • 硬盘以 hd 表示,后面会接一组数字;
  • 以『搜寻顺序』做为硬盘的编号,而不是依照硬盘排线的排序!(这个重要!)
  • 第一个搜寻到的硬盘为 0 号,第二个为 1 号,以此类推;
  • 每个硬盘的第一个 partition 代号为 0 (在 grub2 中第一个 partition 代号为 1),依序类推。

所以说,第一个『搜寻到的硬盘』代号为:『(hd0)』,而该硬盘的第一个分区为『(hd0,0)』,而在 grub2 当中,其写法就是『(hd0,1)』。

在 grub2 中,为了区分不同的文件系统格式,因此磁盘后面的分区号码可以使用类似 msdos1 与 gpt1 的方式来调整!不过也可以不用注明文件系统格式,grub 可以自动识别其文件系统格式的。

最终要记得的是,磁盘的号码是由 0 开始编号,分区的号码在 grub 中是以 0 开始编号,而在 grub2 当中是由1 号开始编号!两者不同!

而且,磁盘的启动顺序在主板 BIOS 中也是可调的,所以其磁盘代号也是会变化的!

grub的grub.cfg 配置文件

在 title 以前的四行,都是属于 grub 的整体配置,包括默认的等待时间与默认的启动项目, 还有显示的画面特性等等。至于 title 后面才是指定启动的核心文件或者是 boot loader 控制权。 在整体配置方面的项目主要常见的有:

  • default=0
    这个必须要与 title 作为对照,在配置文件里面有几个 title ,启动的时候就会有几个菜单可以选择。 由于 grub 启始号码为 0 号,因此 default=0 代表使用『第一个 title 项目』来启动的意思。 default 的意思是,如果在读秒时间结束前都没有动到键盘, grub 默认使用此 title 项目 (在此为 0 号) 来启动。
  • timeout=5
    启动时会进行读秒,如果在 5 秒钟内没有按下任何按键,就会使用上面提到的 default 后面接的那个 title 项目来启动的意思。如果你觉得 5 秒太短,那可以将这个数值调大 (例如 30 秒) 即可。此外,如果 timeout=0 代表直接使用 default 值进行启动而不读秒,timeout=-1 则代表直接进入菜单不读秒了!
  • splashimage=(hd0,0)/grub/splash.xpm.gz
    有没有发现你的 CentOS 在启动的时候背景不是黑白而是有色彩变化的呢?那就是这个文件提供的背景图示!不过这个文件的实际路径写法怎么会是这样?很简单~上述的意思是:在 (hd0,0) 这个分区内的最顶层目录中,底下的 grub/splash.xpm.gz 那个文件的意思。
  • hiddenmenu
    这个说的是,启动时是否要显示菜单?目前 CentOS 默认是不要显示菜单, 如果您想要显示菜单,那就将这个配置值注解掉!

整体配置的地方大概是这样,而底下那个 title 则是显示启动的配置项目。

启动时可以选择直接指定核心文件启动或将 boot loader 控制权转移到下个 loader (此过程称为 chain-loader)。每个 title 后面接的是『该启动项目名称的显示』,亦即是在菜单出现时,菜单上面的名称而已。 那么这两种方式的配置有啥不同呢?

直接指定核心启动

既然要指定内核启动,所以当然要找到内核文件啦!此外,有可能还需要用到 initrd 的 RAM Disk 配置文件。但是如前说的, 尚未启动完成,所以我们必须要以 grub 的硬盘识别方式找出完整的 kernel 与 initrd 文件名才行。 因此,我们可能需要有底下的方式来配置才行!

上面的 root, kernel, initrd 后面接的参数的意义说明如下:

root :代表的是『内核文件放置的那个 partition 而不是根目录』!不要搞错了! 若根目录为 /dev/sda2 而 /boot 独立为 /dev/sda1 ,因为与 /boot 有关, 所以在 grub 中磁盘代号就会成为 (hd0,0) 。

kernel :至于 kernel 后面接的则是内核的文件名,而在文件名后面接的则是内核的参数。 由于启动过程中需要挂载根目录,因此 kernel 后面接的那个 root=/dev/mapper/vg_zhangsir-lv_root 指的是『Linux 的根目录在哪个 partition 』的意思。至于 rhgb 为过程显示而 quiet 则是安静模式 (屏幕不会输出内核检测的信息)。

initrd :就是前面提到的 initrd 制作出 RAM Disk 的文件文件名啦!

注意 kernel 与 initrd 后面路径都是写的“/” ,此处的根并非 Linux 的根,而是前面定义的 /boot 的 root (hd0,0) 了,后面会自动继承前面的定义哦,若是不想这样,也可以写上完整的定义:initrd  (hd0,0)/initramfs-2.6.32-642.el6.x86_64.img

例题:

我的系统分区是: /dev/hda1 (/), /dev/hda2 (swap) 而已,且我的内核文件为 /boot/vmlinuz,请问 grub 的 menu.lst 内该如何撰写核心文件位置?

答:

我们使用叠代的方式来了解一下好了。由于内核文件名为 /boot/vmlinuz,转成设备名与代号会成为如下的过程:

所以最终的 kernel 写法会变成:

kernel (hd0,0)/boot/vmlinuz root=/dev/hda1 ...

利用 chain loader 的方式转交控制权

所谓的 chain loader (启动管理程序的链结) 仅是将控制权交给下一个 boot loader 而已, 所以 grub 并不需要认识与找出 kernel 的文件名 ,『 他只是将 boot 的控制权交给下一个 boot sector 或 MBR 内的 boot loader 而已 』 所以通常他也不需要去查验下一个 boot loader 的文件系统!

一般来说, chain loader 的配置只要两个就够了,一个是预计要前往的 boot sector 所在的分区代号, 另一个则是配置 chainloader 在那个分区的 boot sector (第一个磁区) 上!假设我的 Windows 分区在 /dev/hda1 ,且我又只有一个硬盘,那么要 grub 将控制权交给 windows 的 loader 只要这样就够了:

上面的范例中,我们可以很简单的这样想:那个 (hd0,0) 就是 Windows 的 C 盘所在的分区, 而 chainloader +1 就是让系统加载该分区当中的第一个扇区 (就是 boot sector) 内的启动管理程序。 不过,由于 Windows 的启动磁盘需要配置为激活 (active) 状态,且我们的 grub 默认会去检验该分区的文件系统。 因此我们可以重新将上面的范例改写成这样:

grub 的功能还不止此,他还能够隐藏某些分区。举例来说,我的 /dev/hda5 是安装 Linux 的分区, 我不想让 Windows 能够认识这个分区时,你可以这样做:

grub2的grub.cfg 配置文件

其内容大致如下:

一般 grub2 不建议手动修改grub.cfg 这个配置文件,取而代之的是修改几个特定的​​配置文件之后,由grub2-mkconfig 这个指令来产生新的grub.cfg 文件。不过,我们还是得要了解一下grub2.cfg 的大致内容。

在grub.cfg 最开始的部份,其实大多是环境设定与预设值设定等,比较重要的当然是预设由哪个选项开机(set default) 以及预设的秒数(set timeout), 再来则是每一个选单的设定,就是在『 menuentry 』这个设定值之后的项!

在menuentry 之后会有几个项目的规范,包括『 --class, --unrestricted --id 』等等的指定项目,之后通过『 { } 』将这个选单会用到的资料框起来, 在选择这个选单之后就会进行括号内的动作的意思。如果真的点选了这个选单,那 grub2 首先会载入模块,例如上表中的『 load_video, insmod gzio, insmod part_gpt, insmod xfs 』等等的项目, 都是在载入要读取内核文件所需要的磁盘、分区、文件系统、解压缩等等的驱动程序。之后就是三个比较重要的项目:

  • set root='hd0,msdos1'
    这 root 是指定 grub2 配置文件所在的那个设备。
  • linux16 /vmlinuz-... root=/dev/mapper/centos-root ...
    这个就是 Linux 内核以及内核执行时所下达的参数。

    • 如果没有 /boot 独立分区:
      /boot/vmlinuz-xxx --> (/)/boot/vmlinuz-xxx --> (hd0,msdos1)/boot/vmlinuz- xxx
    • 如果/ boot 是独立分区:
      /boot/vmlinuz-xxx --> (/boot)/vmlinuz-xxx --> (hd0,msdos1)/vmlinuz-xxx

    因此,这个linux16后面接的文件名得要跟上面的 root 搭配在一起,才是完整的绝对路径文件名!至于 linux16 /vmlinuz-xxx root=/file/name 那个 root指的是『 linux 文件系统中,根目录是在哪个装置上』的意思!这个root后面也是可以带入类似root=UUID=1111.2222.33...之类的模式!

  • initrd16 /initramfs-3.10...
    这个就是 initramfs 所在的文件名,跟 linux16 那个 vmlinuz-xxx 相同,这个文件名也是需要搭配『 set root=xxx 』那个项目的设置,才会得到正确的位置!注意注意!

grub2 建立 grub.cfg

因为 grub2 中 grub.cfg 的内容太过复杂,资料量非常庞大,grub2 官方说明不建议我们手动修改!而是应该要通过 /etc/default/grub 这个主要环境配置文件与 /etc/grub.d/ 目录内的相关配置文件来处理比较妥当!

/etc/default/grub这个文件就是用来制作/boot/grub2/grub.cfg 的模版文件了,其内容大致如下:

其主要配置项目如下:

  • 倒数时间参数: GRUB_TIMEOUT

这个设定值相当简单,后面就是接你要倒数的秒数即可~例如要等待30 秒,就在这边改成『GRUB_TIMEOUT=30』即可!如果不想等待则输入0 , 如果一定要使用者选择,则填-1 即可!

  • 是否隐藏选单项目:GRUB_TIMEOUT_STYLE

这个项目可选择的设定值有menu, countdown, hidden 等等。如果没有设定,预设是menu 的意思。这个项目主要是在设定要不要显示选单!如果你不想要让使用者看到选单,这里可以设定为countdown!那countdown 与hidden 有啥差异呢?countdown 会在屏幕上显示剩余的等待秒数, 而 hidden 则会直接跳过。

  • 信息输出的终端机模式:GRUB_TERMINAL_OUTPUT

这个项目是指定输出的画面应该使用哪一个终端机来显示的意思,主要的设定值有『 console, serial, gfxterm, vga_text 』等等。除非有特别的需求,否则一般使用console 即可!

  • 预设开机选单项目:GRUB_DEFAULT

这个项目在指定要用哪一个选单(menuentry) 来作为预设开机项目的意思。能使用的设定值包括有『 saved, 数字, title 名, ID 名』等等。假设你有三个menuentry 的项目大约像这样:

几个常见的设定值是这样的:

一般来说,预设就是以第一个开机选单来作为预设项目,如果想要有不同的选单设定,可以在这个项目填选所需要的--id 即可。当然啦,你的id 就应该不要重复!

  • 核心的外加参数功能:GRUB_CMDLINE_LINUX

如果你的核心在启动的时候还需要加入额外的参数,就可以在这里加入!举例来说,如果你除了预设的核心参数之外,还需要让你的磁盘读写机制为deadline 这个机制时, 可以这样处理:

在已有的项目之后加上如同上表的设定,这样就可以在开机时额外的加入聪攀读写的机制项目设定了!

这个主要环境配置文件编写完毕之后,必须要使用 grub2-mkconfig 来重建 grub.cfg 才行!因为主配置文件就是grub.cfg 而已, 我们通过许多脚本的协力来完成 grub.cfg 的自动建置。

当然,额外自己设定的项目,就是写入 /etc/default/grub 文件内就行了。


示例:假设你需要(1)开机选单等待40 秒钟、 (2)预设用第一个选单开机、 (3)选单请显示出来不要隐藏、 (4)核心外带『elevator=deadline』的参数值, 那应该要如何处理grub.cfg 呢?

grub2 配置文件

grub2-mkconfig 建立 /boot/grub2/grub.cfg 时会去分析 /etc/grub.d/* 里面的文件,然后执行该文件来建立 grub.cfg 的啦!所以, /etc/grub.d/* 里面的文件就显得很重要了。

一般来说,该目录下会有这些文件存在:

  • 00_header:主要在建立初始的显示项目,包括需要载入的模块分析、屏幕终端机的格式、倒数秒数、选单是否需要隐藏等等,大部分在 /etc/default/grub 里面所设定的变数,大概都会在这个脚本当中被利用来重建 grub.cfg 。
  • 10_linux:根据分析 /boot 底下的文件,尝试找到正确的 linux 内核与读取这个内核需要的文件系统模块与参数等,都在这个脚本运作后找到并设定到 grub.cfg 当中。因为这个脚本会将所有在 /boot 底下的每一个内核文件都对应到一个选单,因此内核文件数量越多,你的开机选单项目就越多了。如果未来你不想要旧的核心出现在选单上,那可以通过移除旧内核来处理即可。
  • 30_os-prober:这个脚本默认会到系统上找其他的 partition 里面可能含有的操作系统,然后将该操作系统做成选单来处理。如果你不想要让其他的作业系统被检测到并拿来开机,那可以在/etc/default/grub 里面加上『 GRUB_DISABLE_OS_PROBER=true 』取消这个文件的运作。
  • 40_custom:如果你还有其他想要自己手动加上去的选单项目,或者是其他的需求,那么建议在这里补充即可!

所以,一般来说,我们更改一般会到的就是仅有 40_custom 这个文件。那这个文件内容也大多在放置管理员自己想要加进来的选单项目。

我们知道 menuentry 就是一个选单,那 menuentry 常见的有这几样功能:

  • 直接指定内核开机

基本如果是 Linux 的内核需要直接被用来开机,那么应该要通过 grub2-mkconfig 去抓 10_linux 这个脚本直接制作即可!因为在 grub.cfg 当中就已经是系统能够捉到的正确的内核开机选单了!

不过如果有比较特别的参数需要进行的时候可以这样作:先到 grub.cfg 当中取得你要制作的那个内核的选单项目,然后将它复制到 40_custom 当中再到40_custom 当中依据你的需求修改即可。

示例:如果想选取第一个原有的 menuentry 后,再出现一个选单,该选单可以强制 systemd 使用 graphical.target 来启动 Linux 系统。

当你再次reboot 时,系统就会多出一个选单给你选择了!而且选择该选单之后,你的系统就可以直接进入图形界面(如果有安装相关的X window 软件时)。

  • 通过 chainloader 的方式移交 loader 控制权

所谓的 chain loader (开机管理程序的链接)仅是在将控制权交给下一个 boot loader 而已,所以 grub2 并不需要认识与找出 kernel 的文件名,『他只是将boot的控制权交给下一个 boot sector 或 MBR 内的 boot loader 而已』所以通常他也不需要去查验下一个 boot loader 的文件系统!

一般来说, chain loader 的设定只要两个就够了,一个是预计要前往的 boot sector 所在的分区代号, 另一个则是设定 chainloader 在那个分区的 boot sector (第一个扇区) 上!假设我的 Windows 分区在 /dev/sda1 ,且我又只有一个磁盘,那么要 grub 将控制权交给 windows 的 loader 只要这样就够了:

通过这个项目我们就可以让 grub2 交出控制权了!

参与评论