标签 引导 下的文章

简介

这个实验分为三个部分。第一部分主要是为了熟悉使用 x86 汇编语言、QEMU x86 仿真器、以及 PC 的加电引导过程。第二部分查看我们的 6.828 内核的引导加载器,它位于 lab 树的 boot 目录中。第三部分深入到我们的名为 JOS 的 6.828 内核模型内部,它在 kernel 目录中。

软件安装

本课程中你需要的文件和接下来的实验任务所需要的文件都是通过使用 Git 版本控制系统来分发的。学习更多关于 Git 的知识,请查看 Git 用户手册,或者,如果你熟悉其它的版本控制系统,这个 面向 CS 的 Git 概述 可能对你有帮助。

本课程在 Git 仓库中的地址是 https://exokernel.scripts.mit.edu/joslab.git 。在你的 Athena 帐户中安装文件,你需要运行如下的命令去克隆课程仓库。你也可以使用 ssh -X athena.dialup.mit.edu 去登入到一个公共的 Athena 主机。

athena% mkdir ~/6.828
athena% cd ~/6.828
athena% add git
athena% git clone https://exokernel.scripts.mit.edu/joslab.git lab
Cloning into lab...
athena% cd lab
athena%

Git 可以帮你跟踪代码中的变化。比如,如果你完成了一个练习,想在你的进度中打一个检查点,你可以运行如下的命令去提交你的变更:

athena% git commit -am 'my solution for lab1 exercise 9'
Created commit 60d2135: my solution for lab1 exercise 9
 1 files changed, 1 insertions(+), 0 deletions(-)
athena%

你可以使用 git diff 命令跟踪你的变更。运行 git diff 将显示你的代码自最后一次提交之后的变更,而 git diff origin/lab1 将显示这个实验相对于初始代码的变更。在这里,origin/lab1 是为了完成这个作业,从我们的服务器上下载的初始代码在 Git 分支上的名字。

在 Athena 上,我们为你配置了合适的编译器和模拟器。如果你要去使用它们,请运行 add exokernel 命令。 每次登入 Athena 主机你都必须要运行这个命令(或者你可以将它添加到你的 ~/.environment 文件中)。如果你在编译或者运行 qemu 时出现晦涩难懂的错误,可以双击 "check" 将它添加到你的课程收藏夹中。

如果你使用的是非 Athena 机器,你需要安装 qemugcc,它们在 工具页面 目录中。为了以后的实验需要,我们做了一些 qemu 调试方面的变更和补丁,因此,你必须构建你自己的工具。如果你的机器使用原生的 ELF 工具链(比如,Linux 和大多数 BSD,但不包括 OS X),你可以简单地从你的包管理器中安装 gcc。除此之外,都应该按工具页面的指导去做。

动手过程

我们为了你便于做实验,为你使用了不同的 Git 仓库。做实验用的仓库位于一个 SSH 服务器后面。你可以拥有你自己的实验仓库,其他的任何同学都不可访问你的这个仓库。为了通过 SSH 服务器的认证,你必须有一对 RSA 密钥,并让服务器知道你的公钥。

实验代码同时还带有一个脚本,它可以帮你设置如何访问你的实验仓库。在运行这个脚本之前,你必须在我们的 submission web 界面 上有一个帐户。在登陆页面上,输入你的 Athena 用户名,然后点击 “Mail me my password”。在你的邮箱中将马上接收到一封包含有你的 6.828 课程密码的邮件。注意,每次你点击这个按钮的时候,系统将随机给你分配一个新密码。

现在,你已经有了你的 6.828 密码,在 lab 目录下,运行如下的命令去配置实践仓库:

athena% make handin-prep
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y

Login to 6.828 submission website.
If you do not have an account yet, sign up at https://exokernel.scripts.mit.edu/submit/
before continuing.
Username: <your Athena username>
Password: <your 6.828 password>
Your public key has been successfully updated.
Setting up hand-in Git repository...
Adding remote repository ssh://[email protected]/joslab.git as 'handin'.
Done! Use 'make handin' to submit your lab code.
athena%

如果你没有 RSA 密钥对,这个脚本可能会询问你是否生成一个新的密钥对:

athena% make handin-prep
SSH key file ~/.ssh/id_rsa does not exists, generate one? [Y/n] Y
Generating public/private rsa key pair.
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
The keyʼs randomart image is:
+--[ RSA 2048]----+
| ........ |
| ........ |
+-----------------+
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y
.....
athena%

当你开始动手做实验时,在 lab 目录下,输入 make handin 去使用 git 做第一次提交。后面将运行 git push handin HEAD,它将推送当前分支到远程 handin 仓库的同名分支上。

athena% git commit -am "ready to submit my lab"
[lab1 c2e3c8b] ready to submit my lab
 2 files changed, 18 insertions(+), 2 deletions(-)

athena% make handin
Handin to remote repository using 'git push handin HEAD' ...
Counting objects: 59, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (55/55), done.
Writing objects: 100% (59/59), 49.75 KiB, done.
Total 59 (delta 3), reused 0 (delta 0)
To ssh://[email protected]/joslab.git
 * [new branch] HEAD -> lab1
athena%

如果在你的实验仓库上产生变化,你将收到一封电子邮件,让你去确认这个提交。以后,你可能会多次去运行 run make handin(或者 git push handin)。对于一个指定实验的最后提交时间是由相应分支的最新推送(最后一个推送)的时间决定的。

在这个案例中,make handin 运行可能并不正确,你可以使用 Git 命令去尝试修复这个问题。或者,你可以去运行 make tarball。它将为你生成一个 tar 文件,这个文件可以通过我们的 web 界面 来上传。make handin 提供了很多特殊说明。

对于实验 1,你不需要去回答下列的任何一个问题。(尽管你不用自己回答,但是它们对下面的实验有帮助)

我们将使用一个评级程序来分级你的解决方案。你可以使用这个评级程序去测试你的解决方案的分级情况。

第一部分:PC 引导

第一个练习的目的是向你介绍 x86 汇编语言和 PC 引导过程,你可以使用 QEMU 和 QEMU/GDB 调试开始你的练习。这部分的实验你不需要写任何代码,但是,通过这个实验,你将对 PC 引导过程有了你自己的理解,并且为回答后面的问题做好准备。

从使用 x86 汇编语言开始

如果你对 x86 汇编语言的使用还不熟悉,通过这个课程,你将很快熟悉它!如果你想学习它,PC 汇编语言 这本书是一个很好的开端。希望这本书中有你所需要的一切内容。

警告:很不幸,这本书中的示例是为 NASM 汇编语言写的,而我们使用的是 GNU 汇编语言。NASM 使用所谓的 Intel 语法,而 GNU 使用 AT&T 语法。虽然在语义上是等价的,但是根据你使用的语法不同,至少从表面上看,汇编文件的差别还是挺大的。幸运的是,这两种语法的转换很简单,在 Brennan's Guide to Inline Assembly 有详细的介绍。

练习 1

熟悉在 6.828 参考页面 上列出的你想去使用的可用汇编语言。你不需要现在就去阅读它们,但是在你阅读和写 x86 汇编程序的时候,你可以去参考相关的内容。

我并不推荐你阅读 Brennan's Guide to Inline Assembly 上的 “语法” 章节。虽然它对 AT&T 汇编语法描述的很好(并且非常详细),而且我们在 JOS 中使用的 GNU 汇编就是它。

对于 x86 汇编语言程序最终还是需要参考 Intel 的指令集架构,你可以在 6.828 参考页面 上找到它,它有两个版本:一个是 HTML 版的,是老的 80386 程序员参考手册,它比起最新的手册更简短,更易于查找,但是,它包含了我们的 6.828 上所使用的 x86 处理器的所有特性;而更全面的、更新的、更好的是,来自 Intel 的 IA-32 Intel 架构软件开发者手册,它涵盖了我们在课程中所需要的、(并且可能有些是你不感兴趣的)大多数处理器的全部特性。另一个差不多的(并且经常是很友好的)一套手册是 来自 AMD 的。当你为了一个特定的处理器特性或者指令,去查找最终的解释时,保存的最新的 Intel/AMD 架构手册或者它们的参考就很有用了。

仿真 x86

与在一台真实的、物理的、个人电脑上引导一个操作系统不同,我们使用程序去如实地仿真一台完整的 PC:你在仿真器中写的代码,也能够引导一台真实的 PC。使用仿真器可以简化调试工作;比如,你可以在仿真器中设置断点,而这在真实的机器中是做不到的。

在 6.828 中,我们将使用 QEMU 仿真器,它是一个现代化的并且速度非常快的仿真器。虽然 QEMU 内置的监视功能提供了有限的调试支持,但是,QEMU 也可以做为 GNU 调试器 (GDB) 的远程调试目标,我们在这个实验中将使用它来一步一步完成引导过程。

在开始之前,按照前面 “软件安装“ 中在 Athena 主机上描述的步骤,提取实验 1 的文件到你自己的目录中,然后,在 lab 目录中输入 make(如果是 BSD 的系统,是输入 gmake )来构建最小的 6.828 引导加载器和用于启动的内核。(把在这里我们运行的这些代码称为 ”内核“ 有点夸大,但是,通过这个学期的课程,我们将把这些代码充实起来,成为真正的 ”内核“)

athena% cd lab
athena% make
+ as kern/entry.S
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 414 bytes (max 510)
+ mk obj/kern/kernel.img

(如果你看到有类似 ”undefined reference to `\_\_udivdi3'” 这样的错误,可能是因为你的电脑上没有 32 位的 “gcc multilib”。如果你运行在 Debian 或者 Ubuntu,你可以尝试去安装 “gcc-multilib” 包。)

现在,你可以去运行 QEMU 了,并将上面创建的 obj/kern/kernel.img 文件提供给它,以作为仿真 PC 的 “虚拟硬盘”,这个虚拟硬盘中包含了我们的引导加载器(obj/boot/boot) 和我们的内核(obj/kernel)。

athena% make qemu

运行 QEMU 时需要使用选项去设置硬盘,以及指示串行端口输出到终端。在 QEMU 窗口中将出现一些文本内容:

Booting from Hard Disk...
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K>

Booting from Hard Disk... 之后的内容,就是由我们的基本 JOS 内核输出的:K> 是包含在我们的内核中的小型监听器或者交互式控制程序的提示符。内核输出的这些行也会出现在你运行 QEMU 的普通 shell 窗口中。这是因为测试和实验分级的原因,我们配置了 JOS 的内核,使它将控制台输出不仅写入到虚拟的 VGA 显示器(就是 QEMU 窗口),也写入到仿真 PC 的虚拟串口上,QEMU 会将虚拟串口上的信息转发到它的标准输出上。同样,JOS 内核也将接收来自键盘和串口的输入,因此,你既可以从 VGA 显示窗口中输入命令,也可以从运行 QEMU 的终端窗口中输入命令。或者,你可以通过运行 make qemu-nox 来取消虚拟 VGA 的输出,只使用串行控制台来输出。如果你是通过 SSH 拨号连接到 Athena 主机,这样可能更方便。

在这里有两个可以用来监视内核的命令,它们是 helpkerninfo

K> help
help - display this list of commands
kerninfo - display information about the kernel
K> kerninfo
Special kernel symbols:
 entry f010000c (virt) 0010000c (phys)
 etext f0101a75 (virt) 00101a75 (phys)
 edata f0112300 (virt) 00112300 (phys)
 end f0112960 (virt) 00112960 (phys)
Kernel executable memory footprint: 75KB
K>

help 命令的用途很明确,我们将简短地讨论一下 kerninfo 命令输出的内容。虽然它很简单,但是,需要重点注意的是,这个内核监视器是 “直接” 运行在仿真 PC 的 “原始(虚拟)硬件” 上的。这意味着你可以去拷贝 obj/kern/kernel.img 的内容到一个真实硬盘的前几个扇区,然后将那个硬盘插入到一个真实的 PC 中,打开这个 PC 的电源,你将在一台真实的 PC 屏幕上看到和上面在 QEMU 窗口完全一样的内容。(我们并不推荐你在一台真实机器上这样做,因为拷贝 kernel.img 到硬盘的前几个扇区将覆盖掉那个硬盘上原来的主引导记录,这将导致这个硬盘上以前的内容丢失!)

PC 的物理地址空间

我们现在将更深入去了解 “关于 PC 是如何启动” 的更多细节。一台 PC 的物理地址空间是硬编码为如下的布局:

+------------------+  <- 0xFFFFFFFF (4GB)
|      32-bit      |
|  memory mapped   |
|     devices      |
|                  |
/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\
|                  |
|      Unused      |
|                  |
+------------------+  <- depends on amount of RAM
|                  |
|                  |
| Extended Memory  |
|                  |
|                  |
+------------------+  <- 0x00100000 (1MB)
|     BIOS ROM     |
+------------------+  <- 0x000F0000 (960KB)
|  16-bit devices, |
|  expansion ROMs  |
+------------------+  <- 0x000C0000 (768KB)
|   VGA Display    |
+------------------+  <- 0x000A0000 (640KB)
|                  |
|    Low Memory    |
|                  |
+------------------+  <- 0x00000000

首先,这台 PC 是基于 16 位的 Intel 8088 处理器,它仅能处理 1 MB 的物理地址。所以,早期 PC 的物理地址空间开始于 0x00000000,结束于 0x000FFFFF 而不是 0xFFFFFFFF。被标记为 “低位内存” 的区域是早期 PC 唯一可以使用的随机访问内存(RAM);事实上,更早期的 PC 仅可以配置 16KB、32KB、或者 64KB 的内存!

0x000A00000x000FFFFF 的 384 KB 的区域是为特定硬件保留的区域,比如,视频显示缓冲和保存在非易失存储中的固件。这个保留区域中最重要的部分是基本输入/输出系统(BIOS),它位于从 0x000F00000x000FFFFF 之间的 64KB 大小的区域。在早期的 PC 中,BIOS 在真正的只读存储(ROM)中,但是,现在的 PC 的 BIOS 都保存在可更新的 FLASH 存储中。BIOS 负责执行基本系统初始化工作,比如,激活视频卡和检查已安装的内存数量。这个初始化工作完成之后,BIOS 从相关位置加载操作系统,比如从软盘、硬盘、CD-ROM、或者网络,然后将机器的控制权传递给操作系统。

当 Intel 最终在 80286 和 80386 处理器上 “打破了 1MB 限制” 之后,这两个处理器各自支持 16MB 和 4GB 物理地址空间,尽管如此,为了确保向下兼容现存软件,PC 架构还是保留着 1 MB 以内物理地址空间的原始布局。因此,现代 PC 的物理内存,在 0x000A00000x00100000 之间有一个 “黑洞区域”,将内存分割为 “低位” 或者 “传统内存” 区域(前 640 KB)和 “扩展内存”(其它的部分)。除此之外,在 PC 的 32 位物理地址空间顶部之上的一些空间,在全部的物理内存上面,现在一般都由 BIOS 保留给 32 位的 PCI 设备使用。

最新的 x86 处理器可以支持超过 4GB 的物理地址空间,因此,RAM 可以进一步扩展到 0xFFFFFFFF 之上。在这种情况下,BIOS 必须在 32 位可寻址空间顶部之上的系统 RAM 上,设置第二个 “黑洞区域”,以便于为这些 32 位的设备映射留下空间。因为 JOS 设计的限制,它仅可以使用 PC 物理内存的前 256 MB,因此,我们将假设所有的 PC “仅仅” 拥有 32 位物理地址空间。但是处理复杂的物理地址空间和其它部分的硬件系统,将涉及到许多年前操作系统开发所遇到的实际挑战之一。

ROM BIOS

在实验的这一部分中,你将使用 QEMU 的调试功能去研究 IA-32 相关的计算机是如何引导的。

打开两个终端窗口,在其中一个中,输入 make qemu-gdb(或者 make qemu-nox-gdb),这将启动 QEMU,但是处理器在运行第一个指令之前将停止 QEMU,以等待来自 GDB 的调试连接。在第二个终端窗口中,从相同的目录中运行 make,以及运行 make gdb。你将看到如下的输出。

athena% make gdb
GNU gdb (GDB) 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
+ target remote localhost:1234
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb)

make gdb 的运行目标是一个称为 .gdbrc 的脚本,它设置了 GDB 在早期引导期间调试所用到的 16 位代码,并且将它指向到正在监听的 QEMU 上。

下列行:

[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b

是 GDB 运行的第一个指令的反汇编。这个输出包含如下的信息:

  • IBM PC 从物理地址 0x000ffff0 开始运行,这个地址位于为 ROM BIOS 保留的 64 KB 区域的顶部。
  • PC 使用 CS = 0xf000IP = 0xfff0 开始运行。
  • 运行的第一个指令是一个 jmp 指令,它跳转段地址 CS = 0xf000IP = 0xe05b

为什么 QEMU 是这样开始的呢?这是因为 Intel 设计的 8088 处理器是这样做的,这个处理器是 IBM 最早用在他们的 PC 上的处理器。因为在一台 PC 中,BIOS 是硬编码在物理地址范围 0x000f0000-0x000fffff 中的,这样的设计确保了在机器接通电源或者任何系统重启之后,BIOS 总是能够首先控制机器 —— 这是至关重要的,因为机器接通电源之后,在机器的内存中没有处理器可以运行的任何软件。QEMU 仿真器有它自己的 BIOS,它的位置在处理器的模拟地址空间中。在处理器复位之后,(模拟的)处理器进入了实模式,然后设置 CS0xf000IP0xfff0,所以,运行开始于那个(CS:IP)段地址。那么,段地址 0xf000:fff0 是如何转到物理地址的呢?

在回答这个问题之前,我们需要了解有关实模式地址的知识。在实模式(PC 启动之后就处于实模式)中,物理地址是根据这个公式去转换的:物理地址 = 16 * 段地址 + 偏移。因此,当 PC 设置 CS0xf000IP0xfff0 之后,物理地址指向到:

16 * 0xf000 + 0xfff0 # in hex multiplication by 16 is
 = 0xf0000 + 0xfff0 # easy--just append a 0.
 = 0xffff0

0xffff0 是 BIOS (0x100000) 结束之前的 16 字节。因此,BIOS 所做的第一件事情是向后 jmp 到 BIOS 中的早期位置就一点也不奇怪了;毕竟只有 16 字节,还能指望它做些什么呢?

练习 2

使用 GDB 的 si(步进指令)指令去跟踪进入到 ROM BIOS 的更多指令,然后尝试猜测它可能会做什么。你可能需要去查看 Phil Storrs I/O 端口描述,以及在 6.828 参考资料页面 上的其它资料。不需要了解所有的细节 —— 只要搞明白 BIOS 首先要做什么就可以了。

当 BIOS 运行后,它将设置一个中断描述符表和初始化各种设备,比如, VGA 显示。在这时,你在 QEMU 窗口中将出现 Starting SeaBIOS 的信息。

在初始化 PCI 产品线和 BIOS 知道的所有重要设备之后,它将搜索可引导设备,比如,一个软盘、硬盘、或者 CD-ROM。最后,当它找到可引导磁盘之后,BIOS 从可引导硬盘上读取引导加载器,然后将控制权交给它。

第二部分:引导加载器

在 PC 的软盘和硬盘中,将它们分割成 512 字节大小的区域,每个区域称为一个扇区。一个扇区就是磁盘的最小转存单元:每个读或写操作都必须是一个或多个扇区大小,并且按扇区边界进行对齐。如果磁盘是可引导盘,第一个扇区则为引导扇区,因为,第一个扇区中驻留有引导加载器的代码。当 BIOS 找到一个可引导软盘或者硬盘时,它将 512 字节的引导扇区加载进物理地址为 0x7c000x7dff 的内存中,然后使用一个 jmp 指令设置 CS:IP0000:7c00,并传递控制权到引导加载器。与 BIOS 加载地址一样,这些地址是任意的 —— 但是它们对于 PC 来说是固定的,并且是标准化的。

后来,随着 PC 的技术进步,它们可以从 CD-ROM 中引导,因此,PC 架构师趁机对引导过程进行轻微的调整。最后的结果使现代的 BIOS 从 CD-ROM 中引导的过程更复杂(并且功能更强大)。CD-ROM 使用 2048 字节大小的扇区,而不是 512 字节的扇区,并且,BIOS 在传递控制权之前,可以从磁盘上加载更大的(不止是一个扇区)引导镜像到内存中。更多内容,请查看 “El Torito” 可引导 CD-ROM 格式规范

不过对于 6.828,我们将使用传统的硬盘引导机制,意味着我们的引导加载器必须小于 512 字节。引导加载器是由一个汇编源文件 boot/boot.S 和一个 C 源文件 boot/main.c 构成,仔细研究这些源文件可以让你彻底理解引导加载器都做了些什么。引导加载器必须要做两件主要的事情:

  1. 第一、引导加载器将处理器从实模式切换到 32 位保护模式,因为只有在 32 位保护模式中,软件才能够访问处理器中 1 MB 以上的物理地址空间。关于保护模式将在 PC 汇编语言 的 1.2.7 和 1.2.8 节中详细描述,更详细的内容请参阅 Intel 架构手册。在这里,你只要理解在保护模式中段地址(段基地址:偏移量)与物理地址转换的差别就可以了,并且转换后的偏移是 32 位而不是 16 位。
  2. 第二、引导加载器通过 x86 的专用 I/O 指令直接访问 IDE 磁盘设备寄存器,从硬盘中读取内核。如果你想去更好地了解在这里说的专用 I/O 指令,请查看 6.828 参考页面 上的 “IDE 硬盘控制器” 章节。你不用学习太多的专用设备编程方面的内容:在实践中,写设备驱动程序是操作系统开发中的非常重要的部分,但是,从概念或者架构的角度看,它也是最让人乏味的部分。

理解了引导加载器源代码之后,我们来看一下 obj/boot/boot.asm 文件。这个文件是在引导加载器编译过程中,由我们的 GNUmakefile 创建的引导加载器的反汇编文件。这个反汇编文件让我们可以更容易地看到引导加载器代码所处的物理内存位置,并且也可以更容易地跟踪在 GDB 中步进的引导加载器发生了什么事情。同样的,obj/kern/kernel.asm 文件中包含了 JOS 内核的一个反汇编,它也经常被用于内核调试。

你可以使用 b 命令在 GDB 中设置中断点地址。比如,b *0x7c00 命令在地址 0x7C00 处设置了一个断点。当处于一个断点中时,你可以使用 csi 命令去继续运行:c 命令让 QEMU 继续运行,直到下一个断点为止(或者是你在 GDB 中按下了 Ctrl - C),而 si N 命令是每次步进 N 个指令。

要检查内存中的指令(除了要立即运行的下一个指令之外,因为它是由 GDB 自动输出的),你可以使用 x/i 命令。这个命令的语法是 x/Ni ADDR,其中 N 是连接的指令个数,ADDR 是开始反汇编的内存地址。

练习 3

查看 实验工具指南,特别是 GDB 命令的相关章节。即便你熟悉使用 GDB 也要好好看一看,GDB 的一些命令比较难理解,但是它对操作系统的工作很有帮助。

在地址 0x7c00 处设置断点,它是加载后的引导扇区的位置。继续运行,直到那个断点。在 boot/boot.S 中跟踪代码,使用源代码和反汇编文件 obj/boot/boot.asm 去保持跟踪。你也可以使用 GDB 中的 x/i 命令去反汇编引导加载器接下来的指令,比较引导加载器源代码与在 obj/boot/boot.asm 和 GDB 中的反汇编文件。

boot/main.c 文件中跟踪进入 bootmain() ,然后进入 readsect()。识别 readsect() 中相关的每一个语句的准确汇编指令。跟踪 readsect() 中剩余的指令,然后返回到 bootmain() 中,识别 for 循环的开始和结束位置,这个循环从磁盘上读取内核的剩余扇区。找出循环结束后运行了什么代码,在这里设置一个断点,然后继续。接下来再走完引导加载器的剩余工作。

完成之后,就能够回答下列的问题了:

  • 处理器开始运行 32 代码时指向到什么地方?从 16 位模式切换到 32 位模式的真实原因是什么?
  • 引导加载器执行的最后一个指令是什么,内核加载之后的第一个指令是什么?
  • 内核的第一个指令在哪里?
  • 为从硬盘上获取完整的内核,引导加载器如何决定有多少扇区必须被读入?在哪里能找到这些信息?

加载内核

我们现在来进一步查看引导加载器在 boot/main.c 中的 C 语言部分的详细细节。在继续之前,我们先停下来回顾一下 C 语言编程的基础知识。

练习 4

下载 pointers.c 的源代码,运行它,然后确保你理解了输出值的来源的所有内容。尤其是,确保你理解了第 1 行和第 6 行的指针地址的来源、第 2 行到第 4 行的值是如何得到的、以及为什么第 5 行指向的值表面上看像是错误的。

如果你对指针的使用不熟悉,Brian Kernighan 和 Dennis Ritchie(就是大家知道的 “K&R”)写的《C Programming Language》是一个非常好的参考书。同学们可以去买这本书(这里是 Amazon 购买链接),或者在 MIT 的图书馆的 7 个副本 中找到其中一个。在 SIPB Office 也有三个副本可以细读。

在课程阅读中,Ted Jensen 写的教程 可以使用,它大量引用了 K&R 的内容。

警告:除非你特别精通 C 语言,否则不要跳过这个阅读练习。如果你没有真正理解了 C 语言中的指针,在接下来的实验中你将非常痛苦,最终你将很难理解它们。相信我们;你将不会遇到什么是 ”最困难的方式“。

要了解 boot/main.c,你需要了解一个 ELF 二进制格式的内容。当你编译和链接一个 C 程序时,比如,JOS 内核,编译器将每个 C 源文件('.c')转换为一个包含预期硬件平台的汇编指令编码的二进制格式的对象文件('.o'),然后链接器将所有编译过的对象文件组合成一个单个的二进制镜像,比如,obj/kern/kernel,在本案例中,它就是 ELF 格式的二进制文件,它表示是一个 ”可运行和可链接格式“。

关于这个格式的全部信息可以在 我们的参考页面 上的 ELF 规范 中找到,但是,你并不需要深入地研究这个格式 的细节。虽然完整的格式是非常强大和复杂的,但是,大多数复杂的部分是为了支持共享库的动态加载,在我们的课程中,并不需要做这些。

鉴于 6.828 的目的,你可以认为一个 ELF 可运行文件是一个用于加载信息的头文件,接下来的几个程序节,根据加载到内存中的特定地址的不同,每个都是连续的代码块或数据块。引导加载器并不修改代码或者数据;它加载它们到内存,然后开始运行它。

一个 ELF 二进制文件使用一个固定长度的 ELF 头开始,紧接着是一个可变长度的程序头,列出了每个加载的程序节。C 语言在 inc/elf.h 中定义了这些 ELF 头。在程序节中我们感兴趣的部分有:

  • .text:程序的可运行指令。
  • .rodata:只读数据,比如,由 C 编译器生成的 ASCII 字符串常量。(然而我们并不需要操心设置硬件去禁止写入它)
  • .data:保持在程序的初始化数据中的数据节,比如,初始化声明所需要的全局变量,比如,像 int x = 5;

当链接器计算程序的内存布局的时候,它为未初始化的全局变量保留一些空间,比如,int x;,在内存中的被称为 .bss 的节后面会马上跟着一个 .data。C 规定 "未初始化的" 全局变量以一个 0 值开始。因此,在 ELF 二进制中 .bss 中并不存储内容;而是,链接器只记录地址和.bss 节的大小。加载器或者程序自身必须在 .bss 节中写入 0。

通过输入如下的命令来检查在内核中可运行的所有节的名字、大小、以及链接地址的列表:

athena% i386-jos-elf-objdump -h obj/kern/kernel

如果在你的计算机上默认使用的是一个 ELF 工具链,比如像大多数现代的 Linux 和 BSD,你可以使用 objdump 来代替 i386-jos-elf-objdump

你将看到更多的节,而不仅是上面列出的那几个,但是,其它的那些节对于我们的实验目标来说并不重要。其它的那些节中大多数都是为了保留调试信息,它们一般包含在程序的可执行文件中,但是,这些节并不会被程序加载器加载到内存中。

我们需要特别注意 .text 节中的 VMA(或者链接地址)和 LMA(或者加载地址)。一个节的加载地址是那个节加载到内存中的地址。在 ELF 对象中,它保存在 ph->p_pa 域(在本案例中,它实际上是物理地址,不过 ELF 规范在这个域的意义方面规定的很模糊)。

一个节的链接地址是这个节打算在内存中运行时的地址。链接器在二进制代码中以变量的方式去编码这个链接地址,比如,当代码需要全局变量的地址时,如果二进制代码从一个未链接的地址去运行,结果将是无法运行。(它一般是去生成一个不包含任何一个绝对地址的、与位置无关的代码。现在的共享库大量使用的就是这种方法,但这是以性能和复杂性为代价的,所以,我们在 6.828 中不使用这种方法。)

一般情况下,链接和加载地址是一样的。比如,通过如下的命令去查看引导加载器的 .text 节:

athena% i386-jos-elf-objdump -h obj/boot/boot.out

BIOS 加载引导扇区到内存中的 0x7c00 地址,因此,这就是引导扇区的加载地址。这也是引导扇区的运行地址,因此,它也是链接地址。我们在boot/Makefrag 中通过传递 -Ttext 0x7C00 给链接器来设置链接地址,因此,链接器将在生成的代码中产生正确的内存地址。

练习 5

如果你得到一个错误的引导加载器链接地址,通过再次跟踪引导加载器的前几个指令,你将会发现第一个指令会 “中断” 或者出错。然后在 boot/Makefrag 修改链接地址来修复错误,运行 make clean,使用 make 重新编译,然后再次跟踪引导加载器去查看会发生什么事情。不要忘了改回正确的链接地址,然后再次 make clean

我们继续来看内核的加载和链接地址。与引导加载器不同,这里有两个不同的地址:内核告诉引导加载器加载它到内存的低位地址(小于 1 MB 的地址),但是它期望在一个高位地址来运行。我们将在下一节中深入研究它是如何实现的。

除了节的信息之外,在 ELF 头中还有一个对我们很重要的域,它叫做 e_entry。这个域保留着程序入口的链接地址:程序的 .text 节中的内存地址就是将要被执行的程序的地址。你可以用如下的命令来查看程序入口链接地址:

athena% i386-jos-elf-objdump -f obj/kern/kernel

你现在应该能够理解在 boot/main.c 中的最小的 ELF 加载器了。它从硬盘中读取内核的每个节,并将它们节的加载地址读入到内存中,然后跳转到内核的入口点。

练习 6

我们可以使用 GDB 的 x 命令去检查内存。GDB 手册 上讲的非常详细,但是现在,我们知道命令 x/Nx ADDR 是输出地址 ADDRN word 就够了。(注意在命令中所有的 x 都是小写。)警告: word 的多少并没有一个普遍的标准。在 GNU 汇编中,一个 word 是两个字节(在 xorw 中的 'w',它在这个词中就是 2 个字节)。

重置机器(退出 QEMU/GDB 然后再次启动它们)。检查内存中在 0x00100000 地址上的 8 个词,输出 BIOS 上的引导加载器入口,然后再次找出引导载器上的内核的入口。为什么它们不一样?在第二个断点上有什么内容?(你并不用真的在 QEMU 上去回答这个问题,只需要思考就可以。)

第三部分:内核

我们现在开始去更详细地研究最小的 JOS 内核。(最后你还将写一些代码!)就像引导加载器一样,内核也是从一些汇编语言代码设置一些东西开始的,以便于 C 语言代码可以正确运行。

使用虚拟内存去解决位置依赖问题

前面在你检查引导加载器的链接和加载地址时,它们是完全一样的,但是内核的链接地址(可以通过 objdump 来输出)和它的加载地址之间差别很大。可以回到前面去看一下,以确保你明白我们所讨论的内容。(链接内核比引导加载器更复杂,因此,链接和加载地址都在 kern/kernel.ld 的顶部。)

操作系统内核经常链接和运行在高位的虚拟地址,比如,0xf0100000,为的是给让用户程序去使用处理器的虚拟地址空间的低位部分。至于为什么要这么安排,在下一个实验中我们将会知道。

许多机器在 0xf0100000 处并没有物理地址,因此,我们不能指望在那个位置可以存储内核。相反,我们使用处理器的内存管理硬件去映射虚拟地址 0xf0100000(内核代码打算运行的链接地址)到物理地址 0x00100000(引导加载器将内核加载到内存的物理地址的位置)。通过这种方法,虽然内核的虚拟地址是高位的,离用户程序的地址空间足够远,它将被加载到 PC 的物理内存的 1MB 的位置,只处于 BIOS ROM 之上。这种方法要求 PC 至少要多于 1 MB 的物理内存(以便于物理地址 0x00100000 可以工作),这在上世纪九十年代以后生产的PC 上应该是没有问题的。

实际上,在下一个实验中,我们将映射整个 256 MB 的 PC 的物理地址空间,从物理地址 0x000000000x0fffffff,映射到虚拟地址 0xf00000000xffffffff。你现在就应该明白了为什么 JOS 只能使用物理内存的前 256 MB 的原因了。

现在,我们只映射前 4 MB 的物理内存,它足够我们的内核启动并运行。我们通过在 kern/entrypgdir.c 中手工写入静态初始化的页面目录和页面表就可以实现。现在,你不需要理解它们是如何工作的详细细节,只需要达到目的就行了。将上面的 kern/entry.S 文件中设置 CR0_PG 标志,内存引用就被视为物理地址(严格来说,它们是线性地址,但是,在 boot/boot.S 中设置了一个从线性地址到物理地址的映射标识,我们绝对不能改变它)。一旦 CR0_PG 被设置,内存引用的就是虚拟地址,这个虚拟地址是通过虚拟地址硬件将物理地址转换得到的。entry_pgdir 将把从 0x000000000x00400000 的物理地址范围转换在 0xf00000000xf0400000 的范围内的虚拟地址。任何不在这两个范围之一中的地址都将导致硬件异常,因为,我们还没有设置中断去处理这种情况,这种异常将导致 QEMU 去转储机器状态然后退出。(或者如果你没有在 QEMU 中应用 6.828 专用补丁,将导致 QEMU 无限重启。)

练习 7

使用 QEMU 和 GDB 去跟踪进入到 JOS 内核,然后停止在 movl %eax, %cr0 指令处。检查 0x001000000xf0100000 处的内存。现在使用GDB 的 stepi 命令去单步执行那个指令。再次检查 0x001000000xf0100000 处的内存。确保你能理解这时发生的事情。

新映射建立之后的第一个指令是什么?如果没有映射到位,它将不能正常工作。在 kern/entry.S 中注释掉 movl %eax, %cr0。然后跟踪它,看看你的猜测是否正确。

格式化控制台的输出

大多数人认为像 printf() 这样的函数是天生就有的,有时甚至认为这是 C 语言的 “原语”。但是在操作系统的内核中,我们需要自己去实现所有的 I/O。

通过阅读 kern/printf.clib/printfmt.c、以及 kern/console.c,确保你理解了它们之间的关系。在后面的实验中,你将会明白为什么 printfmt.c 是位于单独的 lib 目录中。

练习 8

我们将省略掉一小部分代码片断 —— 这部分代码片断是使用 ”%o" 模式输出八进制数字所需要的。找到它并填充到这个代码片断中。

然后你就能够回答下列的问题:

  1. 解释 printf.cconsole.c 之间的接口。尤其是,console.c 出口的函数是什么?这个函数是如何被 printf.c 使用的?
  2. console.c 中解释下列的代码:
 if (crt_pos >= CRT_SIZE) {
    int i;
    memcpy(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
    for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
        crt_buf[i] = 0x0700 | ' ';
    crt_pos -= CRT_COLS;
 }
  1. 下列的问题你可能需要参考第一节课中的笔记。这些笔记涵盖了 GCC 在 x86 上的调用规则。

一步一步跟踪下列代码的运行:

 int x = 1, y = 3, z = 4;
 cprintf("x %d, y %x, z %d\n", x, y, z);
1. 在调用 `cprintf()` 时,`fmt` 做了些什么?`ap` 做了些什么?
2. (按运行顺序)列出 `cons_putc`、`va_arg`、以及 `vcprintf` 的调用列表。对于 `cons_putc`,同时列出它的参数。对于`va_arg`,列出调用之前和之后的 `ap` 内容?对于 `vcprintf`,列出它的两个参数值。
  1. 运行下列代码:
unsigned int i = 0x00646c72;
cprintf("H%x Wo%s", 57616, &i);

输出是什么?解释如何在前面的练习中一步一步实现这个输出。这是一个 ASCII 表,它是一个字节到字符串的映射表。

这个输出取决于 x86 是小端法这一事实。如果这个 x86 采用大端法格式,你怎么去设置 i,以产生相同的输出?你需要将 57616 改变为一个不同值吗?

这是小端法和大端法的描述一个更古怪的描述

  1. 在下列代码中,y= 会输出什么?(注意:这个问题没有确切值)为什么会发生这种情况? cprintf("x=%d y=%d", 3);
  2. 假设修改了 GCC 的调用规则,以便于按声明的次序在栈上推送参数,这样最后的参数就是最后一个推送进去的。那你如何去改变 cprintf 或者它的接口,以便它仍然可以传递数量可变的参数?

在本实验的最后一个练习中,我们将理详细地解释在 x86 中 C 语言是如何使用栈的,以及在这个过程中,我们将写一个新的内核监视函数,这个函数将输出栈的回溯信息:一个保存了指令指针(IP)值的列表,这个列表中有嵌套的 call 指令运行在当前运行点的指针值。

练习 9

搞清楚内核在什么地方初始化栈,以及栈在内存中的准确位置。内核如何为栈保留空间?以及这个保留区域的 “结束” 位置是指向初始化结束后的指针吗?

x86 栈指针(esp 寄存器)指向当前使用的栈的最低位置。在这个区域中那个位置以下的所有部分都是空闲的。给一个栈推送一个值涉及下移栈指针和栈指针指向的位置中写入值。从栈中弹出一个值涉及到从栈指针指向的位置读取值和上移栈指针。在 32 位模式中,栈中仅能保存 32 位值,并且 esp 通常分为四部分。各种 x86 指令,比如,call,是 “硬编码” 去使用栈指针寄存器的。

相比之下,ebp(基指针)寄存器,按软件惯例主要是由栈关联的。在进入一个 C 函数时,函数的前序代码在函数运行期间,通常会通过推送它到栈中来保存前一个函数的基指针,然后拷贝当前的 esp 值到 ebp 中。如果一个程序中的所有函数都遵守这个规则,那么,在程序运行过程中的任何一个给定时间点,通过在 ebp 中保存的指针链和精确确定的函数嵌套调用顺序是如何到达程序中的这个特定的点,就可以通过栈来跟踪回溯。这种跟踪回溯的函数在实践中非常有用,比如,由于给某个函数传递了一个错误的参数,导致一个 assert 失败或者 panic,但是,你并不能确定是谁传递了错误的参数。栈的回溯跟踪可以让你找到这个惹麻烦的函数。

练习 10

要熟悉 x86 上的 C 调用规则,可以在 obj/kern/kernel.asm 文件中找到函数 test_backtrace 的地址,设置一个断点,然后检查在内核启动后,每次调用它时发生了什么。每个递归嵌套的 test_backtrace 函数在栈上推送了多少个词(word),这些词(word)是什么?

上面的练习可以给你提供关于实现栈跟踪回溯函数的一些信息,为实现这个函数,你应该去调用 mon_backtrace()。在 kern/monitor.c 中已经给你提供了这个函数的一个原型。你完全可以在 C 中去使用它,但是,你可能需要在 inc/x86.h 中使用到 read_ebp() 函数。你应该在这个新函数中实现一个到内核监视命令的钩子,以便于用户可以与它交互。

这个跟踪回溯函数将以下面的格式显示一个函数调用列表:

Stack backtrace:
 ebp f0109e58 eip f0100a62 args 00000001 f0109e80 f0109e98 f0100ed2 00000031
 ebp f0109ed8 eip f01000d6 args 00000000 00000000 f0100058 f0109f28 00000061
 ...

输出的第一行列出了当前运行的函数,名字为 mon_backtrace,就是它自己,第二行列出了被 mon_backtrace 调用的函数,第三行列出了另一个被调用的函数,依次类推。你可以输出所有未完成的栈帧。通过研究 kern/entry.S,你可以发现,有一个很容易的方法告诉你何时停止。

在每一行中,ebp 表示了那个函数进入栈的基指针:即,栈指针的位置,它就是函数进入之后,函数的前序代码设置的基指针。eip 值列出的是函数的返回指令指针:当函数返回时,指令地址将控制返回。返回指令指针一般指向 call 指令之后的指令(想一想为什么?)。在 args 之后列出的五个十六进制值是在问题中传递给函数的前五个参数。当然,如果函数调用时传递的参数少于五个,那么,在这里就不会列出全部五个值了。(为什么跟踪回溯代码不能检测到调用时实际上传递了多少个参数?如何去修复这个 “缺陷”?)

下面是在阅读 K&R 的书中的第 5 章中的一些关键点,为了接下来的练习和将来的实验,你应该记住它们。

  • 如果 int *p = (int*)100,那么 (int)p + 1(int)(p + 1) 是不同的数字:前一个是 101,但是第二个是 104。当在一个指针上加一个整数时,就像第二种情况,这个整数将隐式地与指针所指向的对象相乘。
  • p[i] 的定义与 *(p+i) 定义是相同的,都反映了在内存中由 p 指向的第 i 个对象。当对象大于一个字节时,上面的加法规则可以使这个定义正常工作。
  • &p[i](p+i) 是相同的,获取在内存中由 p 指向的第 i 个对象的地址。

虽然大多数 C 程序不需要在指针和整数之间转换,但是操作系统经常做这种转换。不论何时,当你看到一个涉及内存地址的加法时,你要问你自己,你到底是要做一个整数加法还是一个指针加法,以确保做完加法后的值是正确的,而不是相乘后的结果。

练 11

实现一个像上面详细描述的那样的跟踪回溯函数。一定使用与示例中相同的输出格式,否则,将会引发评级脚本的识别混乱。在你认为你做的很好的时候,运行 make grade 这个评级脚本去查看它的输出是否是我们的脚本所期望的结果,如果不是去修改它。你提交了你的实验 1 代码后,我们非常欢迎你将你的跟踪回溯函数的输出格式修改成任何一种你喜欢的格式。

在这时,你的跟踪回溯函数将能够给你提供导致 mon_backtrace() 被运行的,在栈上调用它的函数的地址。但是,在实践中,你经常希望能够知道这个地址相关的函数名字。比如,你可能希望知道是哪个有 Bug 的函数导致了你的内核崩溃。

为帮助你实现这个功能,我们提供了 debuginfo_eip() 函数,它在符号表中查找 eip,然后返回那个地址的调试信息。这个函数定义在 kern/kdebug.c 文件中。

练习 12

修改你的栈跟踪回溯函数,对于每个 eip,显示相关的函数名字、源文件名、以及那个 eip 的行号。

debuginfo_eip 中,__STAB_* 来自哪里?这个问题的答案很长;为帮助你找到答案,下面是你需要做的一些事情:

  • kern/kernel.ld 文件中查找 __STAB_*
  • 运行 i386-jos-elf-objdump -h obj/kern/kernel
  • 运行 i386-jos-elf-objdump -G obj/kern/kernel
  • 运行 i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s
  • 如果引导加载器在加载二进制内核时,将符号表作为内核的一部分加载进内存中,那么,去查看它。

通过在 stab_binsearch 中插入调用,可以完成在 debuginfo_eip 中通过地址找到行号的功能。

在内核监视中添加一个 backtrace 命令,扩展你实现的 mon_backtrace 的功能,通过调用 debuginfo_eip,然后以下面的格式来输出每个栈帧行:

K> backtrace
Stack backtrace:
 ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
 kern/monitor.c:143: monitor+106
 ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
 kern/init.c:49: i386_init+59
 ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
 kern/entry.S:70: <unknown>+0
K>

每行都给出了文件名和在那个文件中栈帧的 eip 所在的行,紧接着是函数的名字和那个函数的第一个指令到 eip 的偏移量(比如,monitor+106 意味着返回 eip 是从 monitor 开始之后的 106 个字节)。

为防止评级脚本引起混乱,应该将文件和函数名输出在单独的行上。

提示:printf 格式的字符串提供一个易用(尽管有些难理解)的方式去输出 非空终止 non-null-terminated 字符串,就像在 STABS 表中的这些一样。printf("%.*s", length, string) 输出 string 中的最多 length 个字符。查阅 printf 的 man 页面去搞清楚为什么这样工作。

你可以从 backtrace 中找到那些没有的功能。比如,你或者可能看到一个到 monitor() 的调用,但是没有到 runcmd() 中。这是因为编译器的行内(in-lines)函数调用。其它的优化可能导致你看到一些意外的行号。如果你从 GNUMakefile 删除 -O2 参数,backtraces 可能会更有意义(但是你的内核将运行的更慢)。

到此为止, 在 lab 目录中的实验全部完成,使用 git commit 提交你的改变,然后输入 make handin 去提交你的代码。


via: https://sipb.mit.edu/iap/6.828/lab/lab1/

作者:mit 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你是否曾经对操作系统为何能够执行应用程序而感到疑惑?那么本文将为你揭开操作系统引导与启动的面纱。

理解操作系统开机引导和启动过程对于配置操作系统和解决相关启动问题是至关重要的。该文章陈述了 GRUB2 引导装载程序开机引导装载内核的过程和 systemd 初始化系统执行开机启动操作系统的过程。

事实上,操作系统的启动分为两个阶段: 引导 boot 启动 startup 。引导阶段开始于打开电源开关,结束于内核初始化完成和 systemd 进程成功运行。启动阶段接管了剩余工作,直到操作系统进入可操作状态。

总体来说,Linux 的开机引导和启动过程是相当容易理解,下文将分节对于不同步骤进行详细说明。

  • BIOS 上电自检(POST)
  • 引导装载程序 (GRUB2)
  • 内核初始化
  • 启动 systemd,其是所有进程之父。

注意,本文以 GRUB2 和 systemd 为载体讲述操作系统的开机引导和启动过程,是因为这二者是目前主流的 linux 发行版本所使用的引导装载程序和初始化软件。当然另外一些过去使用的相关软件仍然在一些 Linux 发行版本中使用。

引导过程

引导过程能以两种方式之一初始化。其一,如果系统处于关机状态,那么打开电源按钮将开启系统引导过程。其二,如果操作系统已经运行在一个本地用户(该用户可以是 root 或其他非特权用户),那么用户可以借助图形界面或命令行界面通过编程方式发起一个重启操作,从而触发系统引导过程。重启包括了一个关机和重新开始的操作。

BIOS 上电自检(POST)

上电自检过程中其实 Linux 没有什么也没做,上电自检主要由硬件的部分来完成,这对于所有操作系统都一样。当电脑接通电源,电脑开始执行 BIOS( 基本输入输出系统 Basic I/O System )的 POST( 上电自检 Power On Self Test )过程。

在 1981 年,IBM 设计的第一台个人电脑中,BIOS 被设计为用来初始化硬件组件。POST 作为 BIOS 的组成部分,用于检验电脑硬件基本功能是否正常。如果 POST 失败,那么这个电脑就不能使用,引导过程也将就此中断。

BIOS 上电自检确认硬件的基本功能正常,然后产生一个 BIOS 中断 INT 13H,该中断指向某个接入的可引导设备的引导扇区。它所找到的包含有效的引导记录的第一个引导扇区将被装载到内存中,并且控制权也将从引导扇区转移到此段代码。

引导扇区是引导加载器真正的第一阶段。大多数 Linux 发行版本使用的引导加载器有三种:GRUB、GRUB2 和 LILO。GRUB2 是最新的,也是相对于其他老的同类程序使用最广泛的。

GRUB2

GRUB2 全称是 GRand Unified BootLoader,Version 2(第二版大一统引导装载程序)。它是目前流行的大部分 Linux 发行版本的主要引导加载程序。GRUB2 是一个用于计算机寻找操作系统内核并加载其到内存的智能程序。由于 GRUB 这个单词比 GRUB2 更易于书写和阅读,在下文中,除特殊指明以外,GRUB 将代指 GRUB2。

GRUB 被设计为兼容操作系统多重引导规范,它能够用来引导不同版本的 Linux 和其他的开源操作系统;它还能链式加载专有操作系统的引导记录。

GRUB 允许用户从任何给定的 Linux 发行版本的几个不同内核中选择一个进行引导。这个特性使得操作系统,在因为关键软件不兼容或其它某些原因升级失败时,具备引导到先前版本的内核的能力。GRUB 能够通过文件 /boot/grub/grub.conf 进行配置。(LCTT 译注:此处指 GRUB1)

GRUB1 现在已经逐步被弃用,在大多数现代发行版上它已经被 GRUB2 所替换,GRUB2 是在 GRUB1 的基础上重写完成。基于 Red Hat 的发行版大约是在 Fedora 15 和 CentOS/RHEL 7 时升级到 GRUB2 的。GRUB2 提供了与 GRUB1 同样的引导功能,但是 GRUB2 也是一个类似主框架(mainframe)系统上的基于命令行的前置操作系统(Pre-OS)环境,使得在预引导阶段配置更为方便和易操作。GRUB2 通过 /boot/grub2/grub.cfg 进行配置。

两个 GRUB 的最主要作用都是将内核加载到内存并运行。两个版本的 GRUB 的基本工作方式一致,其主要阶段也保持相同,都可分为 3 个阶段。在本文将以 GRUB2 为例进行讨论其工作过程。GRUB 或 GRUB2 的配置,以及 GRUB2 的命令使用均超过本文范围,不会在文中进行介绍。

虽然 GRUB2 并未在其三个引导阶段中正式使用这些 阶段 stage 名词,但是为了讨论方便,我们在本文中使用它们。

阶段 1

如上文 POST(上电自检)阶段提到的,在 POST 阶段结束时,BIOS 将查找在接入的磁盘中查找引导记录,其通常位于 MBR( 主引导记录 Master Boot Record ),它加载它找到的第一个引导记录中到内存中,并开始执行此代码。引导代码(及阶段 1 代码)必须非常小,因为它必须连同分区表放到硬盘的第一个 512 字节的扇区中。 在传统的常规 MBR 中,引导代码实际所占用的空间大小为 446 字节。这个阶段 1 的 446 字节的文件通常被叫做引导镜像(boot.img),其中不包含设备的分区信息,分区是一般单独添加到引导记录中。

由于引导记录必须非常的小,它不可能非常智能,且不能理解文件系统结构。因此阶段 1 的唯一功能就是定位并加载阶段 1.5 的代码。为了完成此任务,阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。在加载阶段 1.5 代码进入内存后,控制权将由阶段 1 转移到阶段 1.5。

阶段 1.5

如上所述,阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。该空间由于历史上的技术原因而空闲。第一个分区的开始位置在扇区 63 和 MBR(扇区 0)之间遗留下 62 个 512 字节的扇区(共 31744 字节),该区域用于存储阶段 1.5 的代码镜像 core.img 文件。该文件大小为 25389 字节,故此区域有足够大小的空间用来存储 core.img。

因为有更大的存储空间用于阶段 1.5,且该空间足够容纳一些通用的文件系统驱动程序,如标准的 EXT 和其它的 Linux 文件系统,如 FAT 和 NTFS 等。GRUB2 的 core.img 远比更老的 GRUB1 阶段 1.5 更复杂且更强大。这意味着 GRUB2 的阶段 2 能够放在标准的 EXT 文件系统内,但是不能放在逻辑卷内。故阶段 2 的文件可以存放于 /boot 文件系统中,一般在 /boot/grub2 目录下。

注意 /boot 目录必须放在一个 GRUB 所支持的文件系统(并不是所有的文件系统均可)。阶段 1.5 的功能是开始执行存放阶段 2 文件的 /boot 文件系统的驱动程序,并加载相关的驱动程序。

阶段 2

GRUB 阶段 2 所有的文件都已存放于 /boot/grub2 目录及其几个子目录之下。该阶段没有一个类似于阶段 1 与阶段 1.5 的镜像文件。相应地,该阶段主要需要从 /boot/grub2/i386-pc 目录下加载一些内核运行时模块。

GRUB 阶段 2 的主要功能是定位和加载 Linux 内核到内存中,并转移控制权到内核。内核的相关文件位于 /boot 目录下,这些内核文件可以通过其文件名进行识别,其文件名均带有前缀 vmlinuz。你可以列出 /boot 目录中的内容来查看操作系统中当前已经安装的内核。

GRUB2 跟 GRUB1 类似,支持从 Linux 内核选择之一引导启动。Red Hat 包管理器(DNF)支持保留多个内核版本,以防最新版本内核发生问题而无法启动时,可以恢复老版本的内核。默认情况下,GRUB 提供了一个已安装内核的预引导菜单,其中包括问题诊断菜单(recuse)以及恢复菜单(如果配置已经设置恢复镜像)。

阶段 2 加载选定的内核到内存中,并转移控制权到内核代码。

内核

内核文件都是以一种自解压的压缩格式存储以节省空间,它与一个初始化的内存映像和存储设备映射表都存储于 /boot 目录之下。

在选定的内核加载到内存中并开始执行后,在其进行任何工作之前,内核文件首先必须从压缩格式解压自身。一旦内核自解压完成,则加载 systemd 进程(其是老式 System V 系统的 init 程序的替代品),并转移控制权到 systemd。

这就是引导过程的结束。此刻,Linux 内核和 systemd 处于运行状态,但是由于没有其他任何程序在执行,故其不能执行任何有关用户的功能性任务。

启动过程

启动过程紧随引导过程之后,启动过程使 Linux 系统进入可操作状态,并能够执行用户功能性任务。

systemd

systemd 是所有进程的父进程。它负责将 Linux 主机带到一个用户可操作状态(可以执行功能任务)。systemd 的一些功能远较旧式 init 程序更丰富,可以管理运行中的 Linux 主机的许多方面,包括挂载文件系统,以及开启和管理 Linux 主机的系统服务等。但是 systemd 的任何与系统启动过程无关的功能均不在此文的讨论范围。

首先,systemd 挂载在 /etc/fstab 中配置的文件系统,包括内存交换文件或分区。据此,systemd 必须能够访问位于 /etc 目录下的配置文件,包括它自己的。systemd 借助其配置文件 /etc/systemd/system/default.target 决定 Linux 系统应该启动达到哪个状态(或 目标态 target )。default.target 是一个真实的 target 文件的符号链接。对于桌面系统,其链接到 graphical.target,该文件相当于旧式 systemV init 方式的 runlevel 5。对于一个服务器操作系统来说,default.target 更多是默认链接到 multi-user.target, 相当于 systemV 系统的 runlevel 3emergency.target 相当于单用户模式。

(LCTT 译注:“target” 是 systemd 新引入的概念,目前尚未发现有官方的准确译名,考虑到其作用和使用的上下文环境,我们认为翻译为“目标态”比较贴切。以及,“unit” 是指 systemd 中服务和目标态等各个对象/文件,在此依照语境译作“单元”。)

注意,所有的 目标态 target 服务 service 均是 systemd 的 单元 unit

如下表 1 是 systemd 启动的 目标态 target 和老版 systemV init 启动 运行级别 runlevel 的对比。这个 systemd 目标态别名 是为了 systemd 向前兼容 systemV 而提供。这个目标态别名允许系统管理员(包括我自己)用 systemV 命令(例如 init 3)改变运行级别。当然,该 systemV 命令是被转发到 systemd 进行解释和执行的。

SystemV 运行级别systemd 目标态systemd 目标态别名描述
halt.target 停止系统运行但不切断电源。
0poweroff.targetrunlevel0.target停止系统运行并切断电源.
Semergency.target 单用户模式,没有服务进程运行,文件系统也没挂载。这是一个最基本的运行级别,仅在主控制台上提供一个 shell 用于用户与系统进行交互。
1rescue.targetrunlevel1.target挂载了文件系统,仅运行了最基本的服务进程的基本系统,并在主控制台启动了一个 shell 访问入口用于诊断。
2 runlevel2.target多用户,没有挂载 NFS 文件系统,但是所有的非图形界面的服务进程已经运行。
3multi-user.targetrunlevel3.target所有服务都已运行,但只支持命令行接口访问。
4 runlevel4.target未使用。
5graphical.targetrunlevel5.target多用户,且支持图形界面接口。
6reboot.targetrunlevel6.target重启。
default.target 这个 目标态 target 是总是 multi-user.targetgraphical.target 的一个符号链接的别名。systemd 总是通过 default.target 启动系统。default.target 绝不应该指向 halt.targetpoweroff.targetreboot.target

表 1 老版本 systemV 的 运行级别与 systemd 与 目标态 target 或目标态别名的比较

每个 目标态 target 有一个在其配置文件中描述的依赖集,systemd 需要首先启动其所需依赖,这些依赖服务是 Linux 主机运行在特定的功能级别所要求的服务。当配置文件中所有的依赖服务都加载并运行后,即说明系统运行于该目标级别。

systemd 也会查看老式的 systemV init 目录中是否存在相关启动文件,若存在,则 systemd 根据这些配置文件的内容启动对应的服务。在 Fedora 系统中,过时的网络服务就是通过该方式启动的一个实例。

如下图 1 是直接从 bootup 的 man 页面拷贝而来。它展示了在 systemd 启动过程中一般的事件序列和确保成功的启动的基本的顺序要求。

sysinit.targetbasic.target 目标态可以被视作启动过程中的状态检查点。尽管 systemd 的设计初衷是并行启动系统服务,但是部分服务或功能目标态是其它服务或目标态的启动的前提。系统将暂停于检查点直到其所要求的服务和目标态都满足为止。

sysinit.target 状态的到达是以其所依赖的所有资源模块都正常启动为前提的,所有其它的单元,如文件系统挂载、交换文件设置、设备管理器的启动、随机数生成器种子设置、低级别系统服务初始化、加解密服务启动(如果一个或者多个文件系统加密的话)等都必须完成,但是在 sysinit.target 中这些服务与模块是可以并行启动的。

sysinit.target 启动所有的低级别服务和系统初具功能所需的单元,这些都是进入下一阶段 basic.target 的必要前提。

图 1:systemd 的启动流程

sysinit.target 的条件满足以后,systemd 接下来启动 basic.target,启动其所要求的所有单元。 basic.target 通过启动下一目标态所需的单元而提供了更多的功能,这包括各种可执行文件的目录路径、通信 sockets,以及定时器等。

最后,用户级目标态(multi-user.targetgraphical.target) 可以初始化了,应该注意的是 multi-user.target 必须在满足图形化目标态 graphical.target 的依赖项之前先达成。

图 1 中,以 * 开头的目标态是通用的启动状态。当到达其中的某一目标态,则说明系统已经启动完成了。如果 multi-user.target 是默认的目标态,则成功启动的系统将以命令行登录界面呈现于用户。如果 graphical.target 是默认的目标态,则成功启动的系统将以图形登录界面呈现于用户,界面的具体样式将根据系统所配置的显示管理器而定。

故障讨论

最近我需要改变一台使用 GRUB2 的 Linux 电脑的默认引导内核。我发现一些 GRUB2 的命令在我的系统上不能用,也可能是我使用方法不正确。至今,我仍然不知道是何原因导致,此问题需要进一步探究。

grub2-set-default 命令没能在配置文件 /etc/default/grub 中成功地设置默认内核索引,以至于期望的替代内核并没有被引导启动。故在该配置文件中我手动更改 GRUB_DEFAULT=savedGRUB_DEFAULT=2,2 是我需要引导的安装好的内核文件的索引。然后我执行命令 grub2-mkconfig > /boot/grub2/grub.cfg 创建了新的 GRUB 配置文件,该方法如预期的规避了问题,并成功引导了替代的内核。

结论

GRUB2、systemd 初始化系统是大多数现代 Linux 发行版引导和启动的关键组件。尽管在实际中,systemd 的使用还存在一些争议,但是 GRUB2 与 systemd 可以密切地配合先加载内核,然后启动一个业务系统所需要的系统服务。

尽管 GRUB2 和 systemd 都比其前任要更加复杂,但是它们更加容易学习和管理。在 man 页面有大量关于 systemd 的帮助说明,freedesktop.org 也在线收录了完整的此帮助说明。下面有更多相关信息链接。

附加资源


作者简介:

David Both 居住在美国北卡罗纳州的首府罗利,是一个 Linux 开源贡献者。他已经从事 IT 行业 40 余年,在 IBM 教授 OS/2 20余年。1981 年,他在 IBM 开发了第一个关于最初的 IBM 个人电脑的培训课程。他也曾在 Red Hat 教授 RHCE 课程,也曾供职于 MCI worldcom,Cico 以及北卡罗纳州等。他已经为 Linux 开源社区工作近 20 年。


via: https://opensource.com/article/17/2/linux-boot-and-startup

作者:David Both 译者: penghuster 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

boot repair tool repair the most boot related problems

​我们都碰到过启动相关的问题,并且大部分时候都是简单的 GRUB 上的问题。 有时候很多人会觉得、输入一段很长的命令或在论坛中搜索来找到解决方法太麻烦了。今天我要告诉你如何使用一个简单而轻巧的软件来解决大部分的启动相关的问题。这个工具就是著名的 Boot Repair Tool 。好了,话不多说,让我们开始吧。

如何在 Linux 中安装和使用启动修复工具

你需要一个你所安装的操作系统的现场版的 USB 棒或 DVD,这是安装这个应用并使用它的前提条件。 引导到操作系统17 并打开终端并输入以下命令

sudo add-apt-repository -y ppa:yannubuntu/boot-repair
sudo apt-get update
sudo apt-get install -y boot-repair && boot-repair

install boot repair tool in linux

在安装结束以后,你可以从应用菜单或或其它你在系统上启动应用的地方启动你的修复工具。

run boot-repair from apps menu

你可以在菜单栏中看到 Boot Repair。

Picture

​启动它,它就会开始进行一些扫描操作,我们只要等它自己结束就好了。 ​

boot repair app menu to repair system

现在你会看到这个界面,这是基于之前扫描的建议修复。 在底部还可以有一个高级选项,你可以在高级选项里进行各方面的设置。 我建议没有经验的用户使用推荐维修,因为它很简单,在大多数情况下我们都可以这样做。

apply recommended fixes to fix grub issues

选择推荐的更新后,它将开始修复。 等待进一步的处理。

fix the grub menu using boot repair

你现在会看到一个指令界面。 现在是轮到我们操作的时候了。打开终端,逐个复制并粘贴其中高亮的命令到终端中。

download and install grub using the given commands

命令完成后,你会看到一个上面提及的要求确认的界面。 使用箭头键或 Tab 键选择“Yes”,然后按回车键。 现在在 启动修复工具 界面中点击 “forward”。

install grub menu and kernel to fix boot menu

现在你会看到这个界面。 复制在那里提到的命令,并将其粘贴到终端中,然后按回车并让其执行此操作。 需要一段时间所以请耐心等待,它将下载GRUB、内核或任何修复您的引导所需的内容。

install grub

现在你可能会看到一些选项用于配置安装 GRUB 的位置。 选择“yes”,然后按回车,你会看到上面的界面。使用空格键选择选项和按 TAB 以浏览选项。 选择并安装 GRUB 后,可以关闭终端。 现在在启动修复工具屏幕中选择 “forward” 选项。

scan for the boot issues

现在它会做一些扫描操作,并且会询问你一些需要确认的一些选项。 每个选项都选择是即可。

fixed boot issues with boot rescue

它会显示一个成功的确认消息。 如果没有,并且显示失败的消息,则将生成链接。 转到该链接获取更多帮助。

成功后,重启你的电脑。 当你重新启动时,你会看到 GRUB。 现在已成功维修您的电脑。 一切顺利。

引导修复的高级技巧

当我的电脑出现双引导启动画面时,我发现在修复时,它无法识别 安装在另一个分区上的 Windows 7。 这里有一个简单的提示来帮你解决这个问题。

打开终端并安装 os-prober。 它很简单,可以在软件中心或通过终端找到。

os-prober 可以帮助您识别安装在 PC 上的其他操作系统。

install os-prober

os-prober 安装完成后,通过输入 os-prober 在终端运行它。 如果失败了那么试着用 root 账号运行它。 之后运行update-grub 命令。 这就是你可以用于从 GRUB 中启动 Windows 的所需要做的全部。

upgrade-grub in linux

总结

​以上就是全部的内容。现在你已经成功地修复了你的 PC。


via: http://www.linuxandubuntu.com/home/boot-repair-tool-repair-the-most-boot-related-problems

作者:linuxandubuntu 译者:chenxinlong 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这是一个关于如何在 Kali Linux 中更改 GRUB 背景的简单指南(实际上它是 Kali Linux 的 GRUB 启动图像)。 Kali 开发团队在这方面做的不多,他们好像太忙了,所以在这篇文章中,我会对 GRUB 解释一二,但是不会冗长到我失去写作的激情。 那么我们开始吧……

这通常是所有人首先会遇到的一个问题,在哪里设置?有很多方法来查找 GRUB 设置。每个人都可能有自己的方法,但我发现 update-grub 是最简单的。如果在 VMWare 或 VirtualBox 中执行 update-grub,你将看到如下所示的内容:

root@kali:~# update-grub
Generating grub configuration file ...
Found background image: /usr/share/images/desktop-base/desktop-grub.png
Found linux image: /boot/vmlinuz-4.0.0-kali1-amd64
Found initrd image: /boot/initrd.img-4.0.0-kali1-amd64
  No volume groups found
done
root@kali:~#

如果您是双系统,或者三系统,那么您将看到 GRUB 以及其他操作系统入口。然而,我们感兴趣的部分是背景图像,这是在我这里看到的(你会看到完全相同的内容):

Found background image: /usr/share/images/desktop-base/desktop-grub.png

GRUB 启动图像搜索顺序

在 grub-2.02 中,对基于 Debian 的系统来说,它将按照以下顺序搜索启动背景:

  1. /etc/default/grub 里的 GRUB_BACKGROUND
  2. /boot/grub/ 里找到的第一个图像(如果发现多张,将以字母顺序排序)
  3. /usr/share/desktop-base/grub_background.sh 中指定的
  4. /etc/grub.d/05_debian_themeWALLPAPER 行列出的

现在将此信息留在这里,我们会尽快重新检查它。

Kali Linux GRUB 启动图像

在我使用 Kali Linux 时(因为我喜欢用它做事),会发现 Kali 正在使用这里的背景图像:/usr/share/images/desktop-base/desktop-grub.png

为了确定,我们来检查一下这个 .png 文件的属性。

root@kali:~# 
root@kali:~# ls -l /usr/share/images/desktop-base/desktop-grub.png
lrwxrwxrwx 1 root root 30 Oct  8 00:31 /usr/share/images/desktop-base/desktop-grub.png -> /etc/alternatives/desktop-grub
root@kali:~#

Change GRUB background in Kali Linux - blackMORE OPs -1

什么?它只是 /etc/alternatives/desktop-grub 的一个符号链接? 但是 /etc/alternatives/desktop-grub 不是图片文件。看来我也要检查一下它的属性。

root@kali:~# 
root@kali:~# ls -l /etc/alternatives/desktop-grub
lrwxrwxrwx 1 root root 44 Oct  8 00:27 /etc/alternatives/desktop-grub -> /usr/share/images/desktop-base/kali-grub.png
root@kali:~# 

Change GRUB background in Kali Linux - blackMORE OPs -3

好吧,真让人费解。 /etc/alternatives/desktop-grub 也是一个符号链接,它指向 /usr/share/images/desktop-base/kali-grub.png,来自最初同样的文件夹。呃! 无语。 但是现在我们至少可以替换该文件并将其解决。

在替换之前,我们需要检查 /usr/share/images/desktop-base/kali-grub.png 的属性,以确保下载相同类型和大小的文件。

root@kali:~# 
root@kali:~# file /usr/share/images/desktop-base/kali-grub.png
/usr/share/images/desktop-base/kali-grub.png: PNG image data, 640 x 480, 8-bit/color RGB, non-interlaced
root@kali:~# 

可以确定这是一个 PNG 图像文件,像素尺寸为 640 x 480。

GRUB 背景图像属性

可以使用 PNG, JPG/JPEG 以及 TGA 类型的图像文件作为 GRUB 2 的背景。必须符合以下规范:

  • JPG/JPEG 图像必须是 8-bit (256 色)
  • 图像应该是非索引的,RGB

默认情况下,如果安装了 desktop-base 软件包,符合上述规范的图像将放在 /usr/share/images/desktop-base/ 目录中。在谷歌上很容易找到类似的文件。我也找了一个。

root@kali:~# 
root@kali:~# file Downloads/wallpaper-1.png 
Downloads/wallpaper-1.png: PNG image data, 640 x 480, 8-bit/color RGB, non-interlaced
root@kali:~# 

Change GRUB background in Kali Linux - blackMORE OPs -6

方式 1:替换图像

现在我们只需简单的用新文件将 /usr/share/images/desktop-base/kali-grub.png 替换掉。值得注意这是最简单的方法,不需要修改 grub-config 文件。 如果你对 GRUB 很熟,建议你简单的修改 GRUB 的默认配置文件,然后执行 update-grub

像往常一样,我会将原文件重命名为 kali-grub.png.bkp 进行备份。

root@kali:~# 
root@kali:~# mv /usr/share/images/desktop-base/kali-grub.png /usr/share/images/desktop-base/kali-grub.png.bkp
root@kali:~# 

Change GRUB background in Kali Linux - blackMORE OPs -4

现在我们将下载的文件重命名为 kali-grub.png

root@kali:~# 
root@kali:~# cp Downloads/wallpaper-1.png /usr/share/images/desktop-base/kali-grub.png
root@kali:~# 

Change GRUB background in Kali Linux - blackMORE OPs -5

最后执行命令 update-grub

root@kali:~# update-grub
Generating grub configuration file ...
Found background image: /usr/share/images/desktop-base/desktop-grub.png
Found linux image: /boot/vmlinuz-4.0.0-kali1-amd64
Found initrd image: /boot/initrd.img-4.0.0-kali1-amd64
  No volume groups found
done
root@kali:~#

Change GRUB background in Kali Linux - blackMORE OPs -7

下次重新启动你的 Kali Linux 时,你会看到 GRUB 背景变成了你自己的图像(GRUB 启动界面)。

下面是我现在正在使用的新 GRUB 启动背景。你呢?要不要试试这个办法?

Change GRUB background in Kali Linux - blackMORE OPs -9

这是最简单最安全的办法,最糟的情况也不过是在 GRUB 看到一个蓝色的背景,但你依然可以登录后修复它们。现在如果你有信心,让我们尝试一个改变 GRUB 设置的更好的方法(有点复杂)。后续步骤更加有趣,而且可以在任何使用 GRUB 引导的 Linux 上使用。

现在回忆一下 GRUB 在哪 4 个地方寻找启动背景图像?再看一遍:

  1. /etc/default/grub 里的 GRUB_BACKGROUND
  2. /boot/grub/ 里找到的第一个图像(如果发现多张,将以字母顺序排序)
  3. /usr/share/desktop-base/grub_background.sh 中指定的
  4. /etc/grub.d/05_debian_theme 里 WALLPAPER 行列出的

那么我们再在 Kali Linux 上(或任意使用 GRUB2 的 Linux系统)试一下新的选择。

方式 2:在 GRUB\_BACKGROUND 中定义图像路径

所以你可以根据上述的查找优先级使用上述任一项,将 GRUB 背景图像改为自己的。以下是我自己系统上 /etc/default/grub 的内容。

root@kali:~# vi /etc/default/grub

按照 GRUB_BACKGROUND="/root/World-Map.jpg" 的格式添加一行,其中 World-Map.jpg 是你要作为 GRUB 背景的图像文件。

# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
#&nbsp;&nbsp; info -f grub -n 'Simple configuration'

GRUB_DEFAULT=0
GRUB_TIMEOUT=15
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX="initrd=/install/gtk/initrd.gz"
GRUB_BACKGROUND="/root/World-Map.jpg"

一旦使用上述方式完成更改,务必执行 update-grub 命令,如下所示。

root@kali:~# update-grub
Generating grub configuration file ...
Found background: /root/World-Map.jpg
Found background image: /root/World-Map.jpg
Found linux image: /boot/vmlinuz-4.0.0-kali1-amd64
Found initrd image: /boot/initrd.img-4.0.0-kali1-amd64
&nbsp; No volume groups found
done
root@kali:~#

现在重启机器,你会在 GRUB 看到自定义的图像。

方式 3:把图像文件放到 /boot/grub/ 文件夹

如果没有在 /etc/default/grub 文件中指定 GRUB_BACKGROUND 项,理论上 GRUB 应当使用在 /boot/grub/ 文件夹找到的第一个图像文件作为背景。如果 GRUB 在 /boot/grub/ 找到多个图像文件,它会按字母排序并使用第一个图像文件。

方式 4:在 grub\_background.sh 指定图像路径

如果没有在 /etc/default/grub 文件中指定 GRUB_BACKGROUND 项,而且 /boot/grub/ 目录下没有图像文件,GRUB 将会开始在 /usr/share/desktop-base/grub_background.sh 文件中指定的图像路径中搜索。Kali Linux 是在这里指定的。每个 Linux 发行版都有自己的特色。

方式 5:在 /etc/grub.d/05\_debian\_theme 文件的 WALLPAPER 一行指定图像

这是 GRUB 搜寻背景图像的最后一个位置。如果在其他部分都没有找到,它将会在这里查找。

结论

这篇文章较长,但我想介绍一些基础但很重要的东西。如果你有仔细阅读,你会理解如何在 Kali Linux 上来回跟踪符号链接。当你需要在一些 Linux 系统上查找 GRUB 背景图像的位置时,你会感到得心应手。只要再多阅读一点来理解 GRUB 颜色的工作方式,你就是行家了。


via: https://www.blackmoreops.com/2015/11/27/change-grub-background-in-kali-linux/

作者:https://www.blackmoreops.com/ 译者:fuowang 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当你打开你的机器,开机自检(POST)成功完成后,BIOS(基本输入输出系统)立即定位所配置的引导介质,并从 MBR(主引导记录)或 GUID(全局唯一标识符)分区表读取一些命令,这是引导介质的最前面 512 个字节内容。主引导记录(MBR)中包含两个重要的信息集合,第一个是引导程序,第二个是分区表。

什么是引导程序?

引导程序 Boot Loader 是存储在 MBR(主引导记录)或 GUID(全局唯一标识符)分区表中的一个小程序,用于帮助把操作系统装载到内存中。如果没有引导程序,那么你的操作系统将不能够装载到内存中。

有一些我们可以随同 Linux 安装到系统上的引导程序,在这篇文章里,我将简要地谈论几个最好的可以与 Linux 一同工作的 Linux 引导程序。

1. GNU GRUB

GNU GRUB 是一个非常受欢迎,也可能是用的最多的具有多重引导能力的 Linux 引导程序,它以原始的 Eirch Stefan Broleyn 发明的 GRUB( 大一统引导程序 GRand Unified Bootlader )为基础。GNU GRUB 增强了原来的 GRUB,带来了一些改进、新的特性和漏洞修复。

重要的是,GRUB 2 现在已经取代了 GRUB。值得注意的是,GRUB 这个名字被重新命名为 GRUB Legacy,但没有活跃开发,不过,它可以用来引导老的系统,因为漏洞修复依然继续。

GRUB 具有下面一些显著的特性:

  • 支持多重引导
  • 支持多种硬件结构和操作系统,比如 Linux 和 Windows
  • 提供一个类似 Bash 的交互式命令行界面,从而用户可以运行 GRUB 命令来和配置文件进行交互
  • 允许访问 GRUB 编辑器
  • 支持设置加密口令以确保安全
  • 支持从网络进行引导,以及一些次要的特性

访问主页: https://www.gnu.org/software/grub/

2. LILO

LILO ( Linux 引导程序 LInux LOader )是一个简单但强大且非常稳定的 Linux 引导程序。由于 GRUB 有很大改善和增加了许多强大的特性,越来越受欢迎,因此 LILO 在 Linux 用户中已经不是很流行了。

当 LILO 引导的时候,单词“LILO”会出现在屏幕上,并且每一个字母会在一个特定的事件发生前后出现。然而,从 2015 年 12 月开始,LILO 的开发停止了,它有许多特性比如下面列举的:

  • 不提供交互式命令行界面
  • 支持一些错误代码
  • 不支持网络引导(LCTT 译注:其变体 ELILO 支持 TFTP/DHCP 引导)
  • 所有的文件存储在驱动的最开始 1024 个柱面上
  • 面临 BTFS、GTP、RAID 等的限制

访问主页: http://lilo.alioth.debian.org/

3. BURG - 新的引导程序

基于 GRUB,BURG 是一个相对来说比较新的引导程序(LCTT 译注:已于 2011 年停止了开发)。由于 BURG 起源于 GRUB, 所以它带有一些 GRUB 主要特性。尽管如此, BURG 也提供了一些出色的特性,比如一种新的对象格式可以支持包括 Linux、Windows、Mac OS、 FreeBSD 等多种平台。

另外,BURG 支持可高度配置的文本和图标模式的引导菜单,计划增加的“流”支持未来可以不同的输入/输出设备一同工作。

访问主页: https://launchpad.net/burg

4. Syslinux

Syslinux 是一种能从光盘驱动器、网络等进行引导的轻型引导程序。Syslinux 支持诸如 MS-DOS 上的 FAT、 Linux 上的 ext2、ext3、ext4 等文件系统。Syslinux 也支持未压缩的单一设备上的 Btrfs。

注意由于 Syslinux 仅能访问自己分区上的文件,因此不具备多重文件系统引导能力。

访问主页: http://www.syslinux.org/wiki/index.php?title=The_Syslinux_Project

结论

一个引导程序允许你在你的机器上管理多个操作系统,并在某个的时间选择其中一个使用。没有引导程序,你的机器就不能够装载内核以及操作系统的剩余部分。

我们是否遗漏了任何一流的 Linux 引导程序?如果有请让我们知道,请在下面的评论表中填入值得推荐的 Linux 系统引导程序。


via: http://www.tecmint.com/best-linux-boot-loaders/

作者:Aaron Kili 译者:ucasFL 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

没有什么事情比 bootloader 坏掉更气人的了。充分发挥 Grub 2 的作用,让 bootloader 安分工作吧。

为什么这么说?

  • Grub 2 是最受欢迎的 bootloader ,几乎用在所有 Linux 发行版上。
  • bootloader 是一个至关重要的软件,但是非常容易损坏。
  • Grub 2 是兼具扩展性和灵活性的一款引导加载程序,提供了大量可定制选项。

Grub 2 是一款精彩的功能强大的软件。它不是 bootloader 界的一枝独秀,但却最受欢迎,几乎所有主要的桌面发行版都在使用它。 Grub 的工作有两个。首先,它用一个菜单展示计算机上所有已经安装的操作系统供你选择。其次,当你从启动菜单中选择了一个 Linux 操作系统, Grub 便加载这个 Linux 的内核。

你知道,如果使用 Linux ,你就离不开 bootloader 。然而它却是 Linux 发行版内部最鲜为人知的部分。在这篇文章里,我们将带你熟悉 Grub 2 一些著名的特性,强化你相关技能,使你在 bootloader 跑飞的时候能够自行处理。

Grub 2 最重要的部分是一堆文本文件和两个脚本文件。首先需要了解的是 /etc/default/grub 。这是一个文本文件,你可以在里面设置通用配置变量和 Grub 2 菜单(见下方 “常见用户设置” )的其它特性。

Grub 2 另一个重要的部分是 /etc/grub.d 文件夹。定义每个菜单项的所有脚本都放置在这里。这些脚本的名称必须有两位的数字前缀。其目的是,在构建 Grub 2 菜单时定义脚本的执行顺序以及相应菜单项的顺序。文件 00_header 首先被读取,负责解析 /etc/default/grub 配置文件。然后是 Linux 内核的菜单项,位于 10_linux 文件中。这个脚本在默认的 /boot 分区为每个内核创建一个正规菜单项和一个恢复菜单项。

紧接着的是为第三方应用所用的脚本,如 30_os-prober40_customos-prober 脚本为内核和其它分区里的操作系统创建菜单项。它能识别安装的 Linux、 Windows、 BSD 以及 Mac OS X 。 如果你的硬盘布局比较独特,使得 os-prober 无法找到已经安装的发行版,你可以在 40_custom 文件(见下方 “添加自定义菜单项”)中添加菜单项。

Grub 2 不需要你手动维护你的启动选项的配置文件:取而代之的是使用 grub2-mkconfig 命令产生 /boot/grub/grub.cfg 文件。这个功能会解析 /etc/grub.d 目录中的脚本以及 /etc/default/grub 设置文件来定义你的设置情况。

图形化的引导修复

多亏了 Boot Repair 应用,只需要点击按钮,Grub 2 许许多多的问题都能轻易解决。这个漂亮小巧的应用有一个直观的用户界面,可以扫描并识别多种硬盘布局和分区方案,还能发现并正确识别安装在其中的操作系统。这个应用可以处理传统计算机里的 主引导记录 Master Boot Record (MBR),也可以处理新型 UEFI 计算机中的 GUID 分区表 GUID Partition Table (GPT)。

Boot Repair 最简单的使用方式是安装到 Live Ubuntu 会话中。在一个 bootloader 损坏的机器上启动 Ubuntu Live 发行版,先通过添加它的 PPA 版本库来安装 Boot Repair ,命令如下:

sudo add-apt-repository ppa:yannubuntu/Boot Repair

然后刷新版本库列表:

sudo apt-get update

安装应用,如下:

sudo apt-get install -y Boot Repair

安装完毕后就启动应用。在显示它的界面(由一对按键组成)之前将会扫描你的硬盘。根据工具的指示,只需按下 Recommended Repair 推荐的修复 按钮,即可修复大部分坏掉的 bootloader 。修复 bootloader 之后,这个工具会输出一个短小的 URL ,你应该把它记录下来。这个 URL 包含了硬盘详尽的信息:分区信息以及重要的 Grub 2 文件(如 /etc/default/grub/boot/grub/grub.cfg )的内容。如果工具不能解决 bootloader 的问题,可以把你这个 URL 共享在你的发行版的论坛上,让其他人可以分析你的硬盘布局以便给你建议。

Boot Repair 也可以让你定制 Grub 2 的选项。

Bootloader 急救

Grub 2 引导问题会让系统处于几种不同状态。屏幕(如你所想,本该显示 bootloader 菜单的地方)所展示的文本会指示出系统的当前状态。如果系统中止于 grub> 提示符,表明 Grub 2 模块已经被加载,但是找不到 grub.cfg 文件。当前是完全版的 Grub 2 命令行 shell,你可以通过多种方式解决此问题。如果你看到的是 grub rescue> 提示符,表明 bootloader 不能找到 Grub 2 模块或者找不到任何 引导文件 boot files 。然而,如果你的屏幕只显示 ‘GRUB’ 一词,表明 bootloader 找不到通常位于 主引导记录 Master Boot Record 里的最基本的信息。

你可以通过使用 live CD 或者在 Grub 2 shell 中修正此类错误。如果你够幸运, bootloader 出现了 grub> 提示符,你就能获得 Grub 2 shell 的支配权,来帮助你排错。

接下来几个命令工作在 grub>grub rescue> 提示符下。 set pager=1 命令设置显示 分页 pager ,防止文本在屏幕上一滚而过。你还可以使用 ls 命令列出 Grub 识别出的所有分区,如下:

grub> ls
(hd0) (hd0,msdos5) (hd0,msdos6) (hd1,msdos1)

如你所见,这个命令列出分区的同时一并列出了分区表方案(即 msdos)。

你还可以在每个分区上面使用 ls 来查找你的根文件系统:

grub> ls (hd0,5)/
lost+found/ var/ etc/ media/ bin/ initrd.gz
boot/ dev/ home/ selinux/ srv/ tmp/ vmlinuz

你可以不写上分区名的 msdos 部分。同样,如果你忘记了尾部的 斜杠 trailing slash 只输入 ls (hd0,5) ,那你将获得分区的信息,比如文件系统类型、总体大小和最后修改时间。如果你有多个分区,可以使用 cat 读取 /etc/issue 文件中的内容,来确定发行版,格式如 cat (hd0,5)/etc/issue

假设你在 (hd0,5) 中找到根文件系统,请确保它包含 /boot/grub 目录,以及你想引导进入的内核镜像,如 vmlinuz-3.13.0-24-generic 。此时输入以下命令:

grub> set root=(hd0,5)
grub> linux /boot/vmlinuz-3.13.0-24-generic root=/dev/sda5
grub> initrd /boot/initrd.img-3.13.0-24-generic

第一个命令把 Grub 指向我们想引导进入的发行版所在的分区。接着第二个命令告知 Grub 内核镜像在分区中的位置,以及根文件系统的位置。最后一行设置 虚拟文件系统 initial ramdisk 文件的位置。你可以使用 tab 补全功能补全内核名字和 虚拟文件系统 initrd: initial ramdisk 的名字,节省时间和精力。

输入完毕,在下一个 grub> 提示符后输入 boot , Grub 将会引导进入指定的操作系统。

如果你在 grub rescue> 提示符下,情况会有些许不同。因为 bootloader 未能够找到并加载任何必需的模块,你需要手动添加这些模块:

grub rescue> set root=(hd0,5)
grub rescue> insmod (hd0,5)/boot/grub/normal.mod
grub rescue> normal
grub> insmod linux

如上所示,跟之前一样,使用 ls 命令列出所有分区之后,使用 set 命令标记起来。然后添加 normal 模块,此模块激活时将会恢复到标准 grub> 模式。如果 linux 模块没加载,接下来的命令会进行添加。如果这个模块已经加载,你可以跟之前一样,把引导加载程序指向内核镜像和 虚拟文件系统 initrd 文件,然后使用 boot 启动发行版,完美收官。

一旦成功启动发行版,别忘了为 Grub 重新产生新的配置文件,使用

grub-mkconfig -o /boot/grub/grub.cfg

命令。你还需要往 MBR 里安装一份 bootloader 的拷贝,使用

sudo grub2-install /dev/sda

命令。

想要禁用 /etc/grub.d 目录下的脚本,你只需移除其可执行位,比如使用 chmod -x /etc/grub.d/20_memtest86+ 就能将 ‘Memory Test’ 选项从菜单中移除。

Grub 2 和 UEFI

在支持 UEFI 的机器(最近几年上市的机器大部分都是)调试坏掉的 Grub 2 增加了另一复杂的层次。恢复安装在 UEFI 机器上的 Grub 2 的和安装在非 UEFI 机器上的并没多大区别,只是新的固件处理方式不一样,从而导致了很多种恢复结果。

对于基于 UEFI 的系统,不要在 MBR 上安装任何东西。相反,你要在 EFI 系统分区 EFI System Partition ( ESP )里安装 Linux EFI bootloader,并且借助工具把它设置为 EFI 的默认启动程序,这个工具对于 Linux 用户是 efibootmgr ,对于 window 用户则是 bcdedit

照目前情况看,在安装任何与 Windows 8 兼容的主流桌面 Linux 发行版前,应该正确安装好 Grub 2。然而,如果 bootloader 损坏,你可以使用 live 发行版修复机器。在启动 live 介质之时,请确保是以 UEFI 模式启动。计算机每个可移动驱动器的启动菜单将会有两个: 一个普通的和一个以 EFI 标记的。使用后者会用到 /sys/firmware/efi/ 文件中的 EFI 变量。

在 live 环境中,挂载教程前面所提的安装挂掉系统的根文件系统。除此之外,还需要挂载 ESP 分区。假设分区是 /dev/sda1 ,你可以如下所示挂载:

sudo mount /dev/sda1 /mnt/boot/efi

接着在 chroot 到安装完毕的发行版前之前,使用 modprobe efivars 加载 efivars 模块。

在这里, Fedora 用户可以使用如下命令重新安装 bootloader

yum reinstall grub2-efi shim

但在此之前,需要使用

grub2-mkconfig -o /boot/grub2/grub.cfg

来产生新的配置文件。 Ubuntu 用户则改用以下命令

apt-get install --reinstall grub-efi-amd64

一旦 bootloader 正确就位,退出 chroot ,卸载所有分区,重启到 Grub 2 菜单。

伙计,我的 Grub 哪去了?

Grub 2 最好的特性是可以随时重新安装。因此,当其它像 Windows 之类的系统用它们自己的 bootloader 替换后,导致 Grub 2 丢失,你可以使用 live 发行版,寥寥数步即可重装 Grub 。假设你在 /dev/sda5 安装了一个发行版,若要重装 Grub ,你只需首先使用以下命令为发行版创建一个挂载目录:

sudo mkdir -p /mnt/distro

然后挂载分区,如下:

mount /dev/sda5 /mnt/distro

接着就能重装 Grub 了,如下:

grub2-install --root-directory=/mnt/distro /dev/sda

这个命令会改写 /dev/sda 设备上的 MBR 信息,指向当前 Linux 系统,并重写一些 Grub 2 文件,如 grubenvdevice.map

另一个问题常见于装有多个发行版的计算机上。当你安装了新的 Linux 发行版,它的 bootloader 应当要能找到所有已经安装的发行版。一旦不行,只要引导进入新安装的发行版,并运行

grub2-mkconfig

在运行这个命令之前,请确保启动菜单中缺失的发行版的 root 分区已经挂载。如果你想添加的发行版有单独的 /root/home 分区,在运行 grub2-mkconfig 之前,只需挂载包含 /root 的分区。

虽然 Grub 2 能够找到大部分发行版,但是在 Ubuntu 中尝试添加安装的 Fedora 系统需要额外的一个步骤。如果你以默认设置安装了 Fedora ,则发行版的安装器已经创建了 LVM 分区。此时你需要使用发行版的包管理系统安装 lvm2 驱动,如下

sudo apt-get install lvm2

才能使得 Grub 2 的 os-prober 脚本能够找到并将 Fedora 添加进启动菜单。

常见用户设置

Grub 2 有很多可配置变量。 这里有一些 /etc/default/grub 文件中你最可能会修改到的常见变量。 GRUB\_DEFAULT 变量指定默认的启动项,可以设置为数字值,比如 0 ,表示第一个菜单项,或者设置为 “saved” ,将指向上一次启动时选中的菜单项。 GRUB\_TIMEOUT 变量指定启动默认菜单项之前的停留时间。 GRUB\_CMDLINE\_LINUX 列出了要传递给所有 Linux 菜单项的内核命令行参数。

如果 GRUB\_DISABLE\_RECOVERY 变量设置为 true ,那么将不生成恢复模式菜单项。这些菜单项会以单用户模式启动发行版,这种模式下允许你利用命令行工具修复系统。 GRUB\_GFXMODE 变量同样有用,它指定了菜单上文本显示的分辨率,它可以设置为你的显卡所支持的任何数值。

Grub 2 有个命令行模式,通过在 bootloader 菜单上按 C 进入。

彻底的修复

如果 grub2-install 命令不能正常运作,使得你无法引导进入 Linux ,你需要完整地重装以及重新配置 bootloader 。为此目的,需要用到强大的 chroot 功能将运行环境从 live CD 环境切换至我们想修复的 Linux 的安装位置。任何拥有 chroot 工具的 Linux live CD 都可以实现这个目的。不过需要确保 live 介质的系统架构和硬盘上系统的架构一致。因此当你希望 chroot 到 64 位系统,你必须使用 amd64 live 发行版。

启动进入 live 发行版之后,首先需要检查机器上的分区。使用 fdisk -l 列出磁盘上所有分区,记录你想修复的 Grub 2 系统所在的分区。

假设我们希望从安装在 /dev/sda5 中的发行版中恢复 bootloader 。启动终端使用如下命令挂载分区:

sudo mount /dev/sda5 /mnt

此时需要 绑定 bind Grub 2 bootloader 需要进入的目录,以便检测其它操作系统:

$ sudo mount --bind /dev /mnt/dev
$ sudo mount --bind /dev/pts /mnt/dev/pts
$ sudo mount --bind /proc /mnt/proc
$ sudo mount --bind /sys /mnt/sys

此时可以离开 live 环境进入安装在 /dev/sda5 分区中的发行版了,通过 chroot

$ sudo chroot /mnt /bin/bash

现在可以安装、检测、以及升级 Grub 了,跟之前一样,使用

sudo grub2-install /dev/sda

命令来重装 bootloader 。因为 grub2-install 命令不能创建grub.cfg 文件,需要手动创建,如下

sudo grub-mkconfig -o /boot/grub/grub.cfg

这样应该就可以了。现在你就有了 Grub 2 的一份全新拷贝,罗列了机器上所有的操作系统和发行版。在重启电脑之前,你需要依次退出 chroot 系统,卸载所有分区,如下所示:

$ exit
$ sudo umount /mnt/sys
$ sudo umount /mnt/proc
$ sudo umount /mnt/dev/pts
$ sudo umount /mnt/dev
$ sudo umount /mnt

现在你可以安全地重启电脑了,而它应该会回退到 Grub 2 的控制之中,你已经修好了这个 bootloader。

添加自定义菜单项

如果希望往 bootloader 菜单里添加菜单项,你需要在 40\_custom 文件里添加一个 启动段 boot stanza 。例如,你可以使用它展示一个菜单项来启动安装在可移动 USB 驱动里的 Linux 发行版。假设你的 USB 驱动器是 sdb1 ,并且 vmlinuz 内核镜像和 虚拟文件系统 initrd 都位于根 (/)目录下,在 40\_custom 文件中添加以下内容:

menuentry “Linux on USB” {
   set root=(hd1,1)
   linux /vmlinuz root=/dev/sdb1 ro quiet splash
   initrd /initrd.img
}

相比使用设备和分区名,使用它们的 UUID 可以获得更精确结果,比如

set root=UUID=54f22dd7-eabe

使用

sudo blkid

来获得所有已连接的驱动器和分区的 UUID 。你还可以为你磁盘上没被 os-prober 脚本找到的发行版添加菜单项,只要你知道该发行版的安装位置以及其内核和 虚拟文件系统 initrd 的位置即可。


via: https://www.linuxvoice.com/grub-2-heal-your-bootloader/

作者:Mayank Sharma 译者:soooogreen 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出