这篇博客讲述了博主为了安装 sqlite3,不小心删了 boot 目录下的内核,还重启系统。结果重启失败,通过 Ubuntu 安装 U 盘和 chroot 修复的故事。
想在学校的服务器上装一个 sqlite3,被告知之前一次更新内核由于 boot 目录满了,没有更新成功,需要完成内核的更新才能进行其它软件的安装。我一看 boot 分区里已经有了很多版本的内核,想都没想就把旧版本的内核 mv 到了 home 目录下,只在 boot 中留了最新版本的内核,然后执行 update-grub 命令再重启,BOOM,系统开不起来了...刚刚才提示的上一次内核更新没有完全结束,怎么会脑子一抽只留不完整的最新内核呢!
一、修复过程
没办法,只好带了一个 Ubuntu 的安装 U 盘跑去机房。用 U 盘启动后,选择“试用 Ubuntu”进入 liveCD(或者应该叫 liveUSB?)。幸好之前把旧版本的内核在 home 目录下备份了,把完整的旧内核复制回 boot 目录,执行 update-grub 后,得到了错误提示:
/usr/sbin/grub-probe: error: failed to get canonical path of `aufs'.
上网搜索了一下,aufs 是一种特殊的文件系统,liveCD 里有用。原来我们执行 update-grub 的环境是在 liveCD 中,而不是我们要修复的硬盘上的系统,当然会报错。接下来我们就要通过 chroot 把环境转换到硬盘上的系统中,再重新安装 grub 并 update-grub 就能完成修复。
1. 挂载分区
为了 chroot 到硬盘上的系统中,首先需要挂载硬盘分区。通过 sudo fdisk -l 命令可以查看硬盘的分区情况,寻找 root 目录所在的分区。下面用我的虚拟机进行演示。我的虚拟机并没有给 boot 目录或者 usr 目录或者 var 目录一类的单独分区,而是全部划在一块硬盘分区里。执行 fdisk 后,可以看到以下输出:
可以看出,我的根目录在硬盘的第一个分区里,我们执行 sudo mount /dev/sda1 /mnt 把它挂载到 mnt 目录下。
有的系统在分区时,会把根目录放在一个逻辑分区里,这时候根目录所在的分区显示的 Type 会是 LVM(我学校的服务器就是这样的)。这时候,根目录所在分区的设备名一般是 /dev/mapper/xxx,xxx 这个字符串一般会指示这个逻辑分区底下有什么目录,比如 xxx 里面包含 root 一般就是这个逻辑分区底下有根目录。那么我们就要执行 sudo mount /dev/mapper/xxx /mnt 完成根目录的挂载。
还有的系统在分区时,可能会把 boot 目录或者 usr 目录或者 var 目录等等单独分区下(我学校的服务器就是把 boot 目录单独分区)。在完成根目录的挂载后,还需要把这些目录挂载到根目录底下。比如 boot 目录单独分区,设备名是 /dev/sdXY,那么就要执行 sudo mount /dev/sdXY /mnt/boot 完成 boot 目录的挂载。
2. 挂载虚拟文件系统
Linux 系统运行过程中,还需要 /proc、/dev、/run 等虚拟文件系统。参考资料 1 中使用下面这个命令统一完成虚拟文件系统的挂载:
for i in /dev /dev/pts /proc /sys /run; do sudo mount -B $i /mnt$i; done
我在虚拟机中执行这个指令成功了,但是在修复学校服务器时不知道为什么不能成功(但是现在也无法重现这个失败...)。所以我使用了参考资料 2 中的命令完成虚拟文件系统的挂载:
sudo mount -t proc /proc /mnt/proc sudo mount --rbind /sys /mnt/sys sudo mount --rbind /dev /mnt/dev sudo mount --rbind /run /mnt/run
我后来特地搜索了一下 mount 指令的几个选项,-B(或者 --bind)可以挂载一个文件系统中已有的子目录,--rbind 则是递归版的 --bind(可以看一下 这篇博客 了解两者的区别),-t 则是指定挂载的文件系统类型。网上也说 sudo mount -B /proc /mnt/proc 和 sudo mount -t proc /proc /mnt/proc 一般没什么区别,所以也不太知道发生了什么...如果有知道区别的朋友还请留言赐教。
3. chroot + 重新安装 grub
完成以上 chroot 的准备后,我们就可以执行 chroot /mnt 将环境切换到硬盘上的系统了。现在执行 grub-install /dev/sdX (sdX 是你硬盘上的系统所在的设备名)就能在系统所在的设备上重新安装 grub,再执行 update-grub 完成 boot 目录下可用内核的扫描并更新 grub 即可(记得在 update 前先把 boot 目录下损坏的内核移走,只留下好的内核)。最后重新启动,硬盘上的系统又能启动啦!
二、如何安全删除内核
手动删除 boot 下的内核有一定风险,我们应该如何安全地删除内核呢?各种丰富的软件包安装器或管理器给了我们很大的便利。以 Debian 系的系统 Ubuntu 为例,我们可以通过 dpkg 查看现在已经安装了哪些内核。执行 dpkg --get-selections | grep linux ,我的虚拟机中结果如下:
其中 linux-image-xxx,linux-headers-xxx 就是内核相关的了。
我们可以利用 grep 匹配不需要的内核,用软件包管理器删除即可。假如我们只想留下 4.13.0-37 的内核,可以执行
sudo apt purge `dpkg --get-selections | grep linux | grep 4.13.0 | grep -v 37 | cut -f1`
这一段 shell 命令的意思是:先通过 dpkg --get-selections 查看现在已经安装了哪些软件包,再通过 grep 把所有名字含 linux 的软件包选出来,再把其中所有名字含 4.13.0 的软件包选出来,再把其中所有名字里不含 37 的软件包选出来(-v 选项表示选出不匹配的字符串),再把每一行的第一列都选出来。这样就获得了所有不需要的内核的名字,再用 apt purge 删除即可。当然,grep 命令还需要根据自己机器上已有内核的名称进行调整;而且在删除之前,一定要确认一下是不是真的删除这些内核,防止不想删除的内核被匹配上了,结果误删除。毕竟删除内核还是一个比较有风险的操作...
删除完毕后,一般会自动更新 grub。不过为了保险起见,还是手动执行一下 update-grub 比较好。这样就安全地删除了不需要的内核。
参考资料:
1. https://help.ubuntu.com/community/Grub2/Installing#via_ChRoot:通过 chroot 安装 grub;
2. https://wiki.archlinux.org/index.php/change_root:chroot 环境的准备;
3. http://www.wutianqi.com/?p=3699:mount --bind 和 mount --rbind 的区别。