2010年3月31日 星期三

grub and boot


好久没装xp了,以前装过双系统,现在就一个fedora8,编译过kernel 2.6.31。虽然没装xp,但不影响介绍
引导原理。这里先学会修复xp+linux双系统启动的几个问题,然后从实用角度介绍Linux启动过程。
先了解一些常识:

filesystem必须被mount上才能使用,一个partition上有一个fs
root filesystem: 系统启动后第一个被mount的文件系统,其它fs必须在root fs的基础上才能被mount。
root device: root fs所依附的存储介质,可选项很多: 硬盘, 光盘, RAID, USB,硬盘的某个分区...
kernel启动时会逐步尝试找root device: ata, scsi, RAID, USB,root=/dev/X...

root directory: 每个partition最顶级的目录,每个partition一个
root partition: root directory 所在的partition

比如 info grub中介绍grub-install命令时就用到了,当然grub-install命令容易出错,最好别用:

But all the above examples assume that GRUB should use images under
the root directory. If you want GRUB to use images under a directory
other than the root directory, you need to specify the option
`--root-directory'. The typical usage is that you create a GRUB boot
floppy with a filesystem. Here is an example:

# mke2fs /dev/fd0
# mount -t ext2 /dev/fd0 /mnt
# grub-install --root-directory=/mnt fd0
# umount /mnt

Another example is when you have a separate boot partition which is
mounted at `/boot'. Since GRUB is a boot loader, it doesn't know
anything about mountpoints at all. Thus, you need to run `grub-install'
like this:
# grub-install --root-directory=/boot /dev/hda

每个partition都有一个superblock,dumpe2fs /dev/sda5 看到很多sb都是第一个sb的备份
注意 /root 不是root directory,它只是root用户的 home dir

对于sata和scsi接口的硬盘:
第一Master:/dev/sda 第一Slave:/dev/sdb 第二Master:/dev/sdc 第二Slave:/dev/sdd
xp下多个硬盘时是顺序分配盘符,而linux这种表示方法是能区分每一块硬盘的。

MBR是整个硬盘的第一个sector,包括446B的bootstrap program,64B分区表,2B的signature。
boot sector是某个partition的第一个sector,因此MBR是一个特殊的boot sector。

win下对应grub的引导程序叫ntldr。grub分stage1和stage2两个部分,有的系统的引导程序还有个1.5,
都在/boot/grub/下,stage2有配置文件/etc/grub.conf,linux启动文件都在 /boot/ 下,

[cngrid@cn122 ~]$ ll /boot
total 15939
-rw-r--r--. 1 root root 103788 2009-11-08 10:38 config-2.6.31.5-127.fc12.i686.PAE
drwxr-xr-x. 3 root root 1024 2010-03-30 18:12 efi
drwxr-xr-x. 2 root root 1024 2010-03-30 18:36 grub
-rw-r--r--. 1 root root 11252942 2010-03-30 18:26 initramfs-2.6.31.5-127.fc12.i686.PAE.img
drwx------. 2 root root 12288 2010-03-30 17:51 lost+found
-rw-r--r--. 1 root root 1486532 2009-11-08 10:38 System.map-2.6.31.5-127.fc12.i686.PAE
-rwxr-xr-x. 1 root root 3454368 2009-11-08 10:38 vmlinuz-2.6.31.5-127.fc12.i686.PAE

注意vmlinuz就是stage2要load的kernel。

[cngrid@cn122 ~]$ ls -l /boot/grub
total 305
-rw-r--r--. 1 root root 63 2010-03-30 18:36 device.map
-rw-r--r--. 1 root root 14872 2010-03-30 18:36 e2fs_stage1_5
-rw-r--r--. 1 root root 14036 2010-03-30 18:36 fat_stage1_5
-rw-r--r--. 1 root root 13344 2010-03-30 18:36 ffs_stage1_5
-rw-------. 1 root root 728 2010-03-30 18:36 grub.conf
-rw-r--r--. 1 root root 13356 2010-03-30 18:36 iso9660_stage1_5
-rw-r--r--. 1 root root 14928 2010-03-30 18:36 jfs_stage1_5
lrwxrwxrwx. 1 root root 11 2010-03-30 18:36 menu.lst -> ./grub.conf
-rw-r--r--. 1 root root 13480 2010-03-30 18:36 minix_stage1_5
-rw-r--r--. 1 root root 16128 2010-03-30 18:36 reiserfs_stage1_5
-rw-r--r--. 1 root root 17488 2009-10-02 00:08 splash.xpm.gz
-rw-r--r--. 1 root root 512 2010-03-30 18:36 stage1
-rw-r--r--. 1 root root 125432 2010-03-30 18:36 stage2
-rw-r--r--. 1 root root 13628 2010-03-30 18:36 ufs2_stage1_5
-rw-r--r--. 1 root root 12932 2010-03-30 18:36 vstafs_stage1_5
-rw-r--r--. 1 root root 15664 2010-03-30 18:36 xfs_stage1_5

[cngrid@cn122 ~]$ sudo cat /etc/grub.conf
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE: You have a /boot partition. This means that
# all kernel and initrd paths are relative to /boot/, eg.
# root (hd0,0)
# kernel /vmlinuz-version ro root=/dev/sda2
# initrd /initrd-[generic-]version.img
#boot=/dev/sda
default=0
timeout=0
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title Fedora (2.6.31.5-127.fc12.i686.PAE)
root (hd0,0)
kernel /vmlinuz-2.6.31.5-127.fc12.i686.PAE ro root=UUID=bf2509ce-dd51-4760-8e9f-3b7cdb7d79b5 LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYBOARDTYPE=pc KEYTABLE=us rhgb quiet
initrd /initramfs-2.6.31.5-127.fc12.i686.PAE.img

注释是说/boot分区的,另一台机器只有一个 / 分区,对比两台机器的grub.conf就明白了:

[root@gridserver ~]# cat /etc/grub.conf
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE: You do not have a /boot partition. This means that
# all kernel and initrd paths are relative to /, eg.
# root (hd0,0)
# kernel /boot/vmlinuz-version ro root=/dev/sda1
# initrd /boot/initrd-version.img
#boot=/dev/sda
default=0
timeout=5
splashimage=(hd0,0)/boot/grub/splash.xpm.gz
hiddenmenu
title Red Hat Enterprise Linux AS (2.6.9-22.ELsmp)
root (hd0,0)
kernel /boot/vmlinuz-2.6.9-22.ELsmp ro root=LABEL=/ rhgb quiet
initrd /boot/initrd-2.6.9-22.ELsmp.img

关键就下面三行:
# root (hd0,0)
# kernel /vmlinuz-version ro root=/dev/sda2
# initrd /initrd-version.img

第一行是说root device的位置在 (hd0,0),stage1, stage2, kernel image和initrd都在那。
一般grub就在硬盘上,所以后面跟的是一个硬盘分区。如果把grub装在了cd上,就 root (cd),

第二行是kernel image name, 后面的 root=/dev/sda2 才是真正在说root device: 视/dev/sda2为
root device,kernel将来就用这个root device上的filesystem作为root fs。
# df -h 能看到 /dev/sda2 就是 / 分区。而grub指定的那个"root"是指 /boot 分区。
[cngrid@cn122 ~]$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 9.7G 438M 8.7G 5% /
tmpfs 499M 420K 498M 1% /dev/shm
/dev/sda1 146M 22M 117M 16% /boot
/dev/sda3 39G 28G 9.2G 76% /home
/dev/sda5 24G 17G 6.4G 72% /usr
//10.0.1.15/共享 108G 100G 7.6G 94% /mnt/win

从这也看出: 如果没有单独划分/boot分区,则这两个root就相同了,更容易混淆了。

这里自己可以加参数,比如忘记root密码,grub图形时e 然后在 kernel /vmlinuz 这行加上 single,
就可以单用户进入系统,passwd root改密码了。rhgb quiet也是参数,还有更多。

1。config-2.6.31.5-127.fc12.i686.PAE
内核编译选项。想知道系统是否preemptible就grep PREEMPT config-2.6.31.5-127.fc12.i686.PAE
2。System.map-2.6.31.5-127.fc12.i686.PAE 是kernel的符号表,man nm
3。initramfs-2.6.31.5-127.fc12.i686.PAE.img 是系统启动时帮助mount root fs的,是一个基于内存
的文件系统。这样解压:

# file initramfs-2.6.31.5-127.fc12.i686.PAE.img
initramfs-2.6.31.5-127.fc12.i686.PAE.img: gzip compressed data, from Unix, last modified:
Tue Mar 30 18:25:50 2010, max compression

# mv initramfs-2.6.31.5-127.fc12.i686.PAE.img initrd-2.6.31.gz
# gunzip initrd-2.6.31.gz
# cpio -id < initrd-2.6.31

linux启动时先mount了一个叫rootfs的内存文件系统,cat /proc/mounts 可以看到,rootfs不能被umount。
在此基础上进一步mount基于存储介质的root fs。

为什么需要initrd.img呢?编译kernel时在硬盘驱动那里容易kernel panic:
因为编译kernel时ext3和一些scsi,ata的硬盘驱动默认是m,并没有编译到kernel中来。这样当kernel启动
阶段需要scsi, ata这些硬盘的驱动时,由于驱动没编译进kernel中,就现找,而所需的驱动模块却又在硬盘
上,自然是找不到。initrd就是来解决这个问题的,它里面有一些基本文件系统如ext3和硬盘的驱动模块,
系统启动时grub把initrd完全load到内存里,等kernel启动时就不会再到硬盘上找驱动模块了。

当然,如果在make menuconfig时把ext3等硬盘驱动的选项由m改成y就不用initrd了,关键是选多了会使
kernel臃肿,选少了又会kernel panic,所以一般都用initrd做帮助,而且目标是将来把驱动全放这个
initrd里,这样kernel就会变的精简。

initrd是一个基于内存的root fs,用来帮助kernel mount我们平时说的root fs,而对一些没有非易失性介
质的嵌入式系统,initrd就是系统最终的root fs。

看看源码:
kernel_init()-> prepare_namespace():
/*
* Prepare the namespace - decide what/where to mount, load ramdisks, etc.
*/
void __init prepare_namespace(void)
{
int is_floppy;

if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}

/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();

md_run_setup();

if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) || // RAID
!strncmp(root_device_name, "ubi", 3)) { // USB
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0) // grub root=/dev/X
root_device_name += 5;
}

if (initrd_load())
goto out;

/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;

mount_root();
out:
sys_mount(".", "/", NULL, MS_MOVE, NULL); // mount root fs
sys_chroot(".");
}

先是Wating一段时间让用户准备root device;
对RAID USB进行探测,要探测成功还要在mount_block_root()中对各种文件系统类型ext2/ext3/等逐个试验,
然后调用sys_mount()来mount root fs,此时不用initrd。
看一下mount_block_root(),其中的很多printk有助于我们将来排错:

void __init mount_block_root(char *name, int flags)
{
char *fs_names = __getname_gfp(GFP_KERNEL
| __GFP_NOTRACK_FALSE_POSITIVE);
char *p;
#ifdef CONFIG_BLOCK
char b[BDEVNAME_SIZE];
#else
const char *b = name;
#endif

get_fs_names(fs_names);
retry:
for (p = fs_names; *p; p += strlen(p)+1) { // test filesystem type one by one
int err = do_mount_root(name, p, flags, root_mount_data);
switch (err) {
case 0:
goto out;
case -EACCES:
flags |= MS_RDONLY;
goto retry;
case -EINVAL:
continue;
}
/*
* Allow the user to distinguish between failed sys_open
* and bad superblock on root device.
* and give them a list of the available devices
*/
#ifdef CONFIG_BLOCK
__bdevname(ROOT_DEV, b);
#endif
printk("VFS: Cannot open root device \"%s\" or %s\n",
root_device_name, b);
printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");

printk_all_partitions();
#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
"explicit textual name for \"root=\" boot option.\n");
#endif
panic("VFS: Unable to mount root fs on %s", b);
}

printk("List of all partitions:\n");
printk_all_partitions();
printk("No filesystem could mount root, tried: ");
for (p = fs_names; *p; p += strlen(p)+1)
printk(" %s", p);
printk("\n");
#ifdef CONFIG_BLOCK
__bdevname(ROOT_DEV, b);
#endif
panic("VFS: Unable to mount root fs on %s", b);
out:
putname(fs_names);
}

若没成功就继续尝试本地硬盘,这是最常见的情况。由grub传的参数/dev/X得出device number,
然后把initrd文件load进内存,然后再mount root fs。如果硬盘也不成功就尝试floppy。
mount root fs之后,就 execve /sbin/init生成init进程,init进程会重新mount root fs为rw。

知道这些就会定制Linux的启动过程了,比如有了一种新硬件,完全可以做好驱动,把root fs放在新硬件上,
然后更改kernel源码就行了; 也可以把grub备份在u盘上; 在LAN中把vmlinuz文件放在网络上,进行网络安装。

硬盘安装的例子,在fedora 8上硬盘装fedora 12:

把iso放在一个安装过程中不会被破坏的分区中,假设在/home/cngrid/f12/下
把iso中的 initrd.img 和 vmlinuz copy到/boot/下,可以改名,修改grub,成为:
/boot/initrd-f12.img
/boot/vmlinuz-f12

要求iso和iso中的images都在一个文件夹下,即
/home/cngrid/f12/Fedora-12-i386-DVD.iso
/home/cngrid/f12/images

命令是:
# mount -t iso9660 /home/cngrid/fedora/Fedora-12-i386-DVD.iso /mnt/f12/ -o loop
# cp /mnt/f12/isolinux/initrd /boot/initrd-fedora.12.img
# cp /mnt/f12/isolinux/vmlinuz /boot/vmlinuz-fedora.12
# cp -r /mnt/f12/images/ /home/cngrid/f12/
# umount /mnt/f12/
# vi /etc/grub.conf 末尾添加启动项
title Fedora 12
root (hd0,0)
kernel /vmlinuz-f12 ro root=LABEL=/ rhgb quiet
initrd /initrd-f12.img

df -h 记住iso所在分区,一会要用。重启,title Fedora 12 -> Hard Drive -> /dev/sda2 ->
Directory holding images: 不用填能自动搜索到iso, 继续就和光盘安装一样了。