假如/boot目录下面有多个内核(例如kernel1、kernel2、kernel3等),你现在运行的内核版本是kernel1,运行update-grub命令之后,你会发现grub.cfg文件的默认启动项被更改为启动kernel3了,这样如果你重启系统,系统就会切换到运行内核kernel3上面。
我发现这种现象是在修改了/boot/default/grub文件中的选项之后,运行update-grub命令发现的。我所改动的无非是增加cmdline中的命令行选项,为何会引起grub启动项menuentry的改变呢?
update-grub脚本内容如下:
#!/bin/sh set -e exec grub-mkconfig -o /boot/grub/grub.cfg "$@" |
此脚本很简单,就是调用grub-mkconfig命令。grub-mkconfig也是一个bash脚本,它就是用来生成grub.cfg文件的。
grub-mkconfig代码片段:
253行---263行就是在grub.cfg文件开头看到的注释部分。grub_mkconfig_dir变量就是/etc/grub.d目录,${sysconfdir}/default/grub对应就是/etc/default/grub,注释部分的意思就是生成grub.cfg的启动项是使用/etc/grub.d目录下的模板生成的,默认的一些配置,包括命令行选项、默认启动等待时间、默认启动项等设置是从读取/etc/default/grub文件。
266行---281行的for循环中就是遍历/etc/grub.d目录下的模板文件。先看下/etc/grub.d目录下有哪些文件:
/etc/grub.d/ ├── 00_header ├── 05_debian_theme ├── 10_linux ├── 20_linux_xen ├── 30_os-prober ├── 30_uefi-firmware ├── 40_custom ├── 41_custom └── README |
第273行的grub_file_is_not_garbage函数在/usr/share/grub/grub-mkconfig_lib文件中实现,在这里传进来的参数就是上面看到的/etc/grub.d目录下的文件。这一行就是找出此目录下具有执行权限的文件。
此for循环就是执行/etc/grub.d目录下具有可执行权限的文件。分析这些文件及结合grub.cfg的内容,不难发现生成grub.cfg文件启动项menuentry是在10_linux文件中。
10_linux文件代码片段:
这段代码主要就是遍历/boot目录或者/目录下的文件,并将它们保存在list变量中。
276行的is_top_level变量是用来标记区分menuentry和submenuentry的。
277行开始的while循环变量前面保存的list,也就是系统中的各种版本的内核文件。
278行调用version_find_latest函数取出此list中版本号latest的内核,并赋给变量linux。
version_find_latest函数在/usr/share/grub/grub-mkconfig_lib文件中实现:
version_test_gt () { version_test_gt_sedexp="s/[^-]*-//;s/[._-]\(pre\|rc\|test\|git\|old\|trunk\)/~\1/g" version_test_gt_a="`echo "$1" | sed -e "$version_test_gt_sedexp"`" version_test_gt_b="`echo "$2" | sed -e "$version_test_gt_sedexp"`" version_test_gt_cmp=gt if [ "x$version_test_gt_b" = "x" ] ; then return 0 fi case "$version_test_gt_a:$version_test_gt_b" in *.old:*.old) ;; *.old:*) version_test_gt_a="`echo -n "$version_test_gt_a" | sed -e 's/\.old$//'`" ; version_test_gt_cmp=gt ;; *:*.old) version_test_gt_b="`echo -n "$version_test_gt_b" | sed -e 's/\.old$//'`" ; version_test_gt_cmp=ge ;; esac dpkg --compare-versions "$version_test_gt_a" "$version_test_gt_cmp" "$version_test_gt_b" return "$?" }
version_find_latest () { version_find_latest_a="" for i in "$@" ; do if version_test_gt "$i" "$version_find_latest_a" ; then version_find_latest_a="$i" fi done echo "$version_find_latest_a" } |
version_find_latest函数调用version_test_gt函数找出list中列出的所有版本内核中最新的内核。version_test_gt函数首先将传进来的两个参数(内核文件名)取出版本号子字符,例如传进来内核是/boot/vmlinuz-3.16.0-4-amd64和/boot/vmlinuz-3.16.7-ckt7,那么取出来的版本子字符就是3.16.0-4-amd64和3.16.7-ckt7,然后调用dpkg --compare-versions来比较两个之间版本号比较大的一个。
回到277开始的for循环,此循环就是按照版本号从大到小生成grub.cfg文件中的各个内核启动项。接下来看看具体某个版本内核的启动项是如何生死的,例如系统中有两个版本的内核:/boot/vmlinuz-3.16.0-4-amd64和/boot/vmlinuz-3.16.7-ckt7,那么运行第278行取出来的最新版本内核就是/boot/vmlinuz-3.16.7-ckt7,即linux=”/boot/vmlinuz-3.16.7-ckt7”
287行:basename=vmlinuz-3.16.7-ckt7
288行:dirname=/boot
289行:rel_dirname=/boot
290行:version=3.16.7-ckt7
291行:alt_version=3.16.7-ckt7
292行:linux_root_device_thisversion=
295行~307行是一个for循环,它是用来在dirname目录下查找version版本对应的initrd文件系统,例如/boot/vmlinuz-3.16.7-ckt7文件系统,对应的文件系统:
initrd=initrd.img-3.16.7-ckt7
309行~315行的for循环是用来查找version版本内核对应的内核CONFIG选项的config文件,这里config=/boot/config-3.16.7-ckt7