1. 程式人生 > >initrd、rootfs及內核root=參數

initrd、rootfs及內核root=參數

off 任務 c中 mknod mnt 進行 free list loader

一、0號和1號進程

通俗的講0號進程就是以start_kernel為入口的一個任務,也就是內核本身,這個任務的task_struct結構就是我們在編譯的時候靜態初始化的init_task結構,這個結構的位置和地址在可執行文件生成的時候就已經確定,其中的大部分成員都按照所需進行了初始化。

1號任務就是以init函數為入口的一個任務,這個任務對內核來說,就是一個一個一般的線程,通過kernel_thread創建一個線程,為它分配一個task_struct結構,只是這個線程執行的代碼是在內核中寫入的,而不是一個用戶態代碼。這個任務在執行的最後會通過exec函數執行用戶態約定好的可執行文件,從而派生出系統的第一個任務。所有的內核,如果沒有定制,那麽他們到執行init之前,所有的代碼都是相同的。不同的系統如何配置,就是需要在這個init可執行文件中進行設置了。

二、rootfs和initrd

對於rootfs,是整個內核中真正的所有文件系統的根節點,它同樣是內核中靜態創建的一個結構。這個結構的特殊之處就在於它不要來任何外部設備而只以來內存。用內核文檔的話來說,它是一個特殊的ramfs。它的定義就是位於\linux-2.6.21\fs\ramfs\inode.c中,其中定義了rootfs,從這個名字就可以看出,這是系統中的根文件系統,並且它只存在於內存中,不依賴於外設。這個優點就是可以在外設沒有初始化的時候創建根文件系統。從另一方面來看,這也是必須的:因為根文件系統都是掛載在外設中的,而外設一般又是通過/dev來訪問的,所以必須首先有一個不依賴於外設的文件,這樣才能創建設備文件,進而在這個設備文件上創建根文件系統

這個根文件系統的註冊位於init_rootfs,這個其實比較簡單,只是簡單的註冊了這個文件系統,這個文件系統的名稱就是rootfs文件系統。而這個真正的系統文件系統的創建則是由init_mount_tree函數來執行的,執行的調用連關系為

start_kernel--->>>vfs_caches_init--->>mnt_init--->>>init_mount_tree

static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;

mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can‘t create rootfs");
ns = kmalloc(sizeof(*ns), GFP_KERNEL);
if (!ns)
panic("Can‘t allocate initial namespace");
atomic_set(&ns->count, 1);
INIT_LIST_HEAD(&ns->list);
init_waitqueue_head(&ns->poll);
ns->event = 0;
list_add(&mnt->mnt_list, &ns->list);
ns->root = mnt;
mnt->mnt_ns = ns;

init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);

set_fs_pwd(current->fs, ns->root, ns->root->mnt_root);
set_fs_root(current->fs, ns->root, ns->root->mnt_root
);通過這一步,已經 設置了0號進程的根文件系統,這樣就可以通過相對路徑和絕對路徑來訪問一個文件系統。同時更為重要也是最為重要的就是,我們已經可以使用文件系統的所有接口,包括設備文件的創建,文件夾的創建,文件的創建,文件系統的掛在等,這就為initrd指定的文件系統的掛在創建的基本條件
}
這裏只是創建了一個掛接樹的根文件夾,此時文件系統已經可以使用,可以調用sys_open sys_mknod之類的接口了

initrd是一個ramdisk文件,也就是它本身也是一個文件系統,一般是通過cpio + gzip生成的一個文件,或者大家可以按照tar來理解,就是一個基本的包含了一個文件系統結構的壓縮文件,當然裏面還可以包含更多的東西,例如包含大量的可執行文件,但是一般裏面只是包含了根文件系統大致的框架,例如proc文件夾、dev文件夾等我們常見的文件夾,還可以包含系統啟動時的一些配置文件等。

這個initrd的其實地址和結束地址同樣需要由bootloader傳遞給內核,但是我們通過 cat /proc/cmdline 中並不能看到initrd的開始和結束,甚至不能看到內核的命令行中有任何的內容,這一點在我們確定使用了initrd的qemu中也看不到。事實上,initrd的起始地址和結束地址同樣不能依賴於文件系統,因為rootfs是空的,正式試圖通過這個initrd來創建最為簡單原始的根文件系統,所以只能傳遞地址而不是字符串或者絕對路徑

對於這個initrd的起始地址和結束地址的傳遞方法,不同的體系結構有不同的實現約定。對於i386的實現,它位於啟動頁面之後的。在內核的setupS中,其中列出了這些約定的位置:

start:
jmp trampoline

…………

ramdisk_image: .long 0 # address of loaded ramdisk image
# Here the loader puts the 32-bit
# address where it loaded the image.
# This only will be read by the kernel.

ramdisk_size: .long 0 # its size in bytes

在boot.txt中有相應的文字說明Documentation\i386\boot.txt

The header looks like:
Offset Proto Name Meaning /Size
01F1/1 ALL(1 setup_sects The size of the setup in sectors 01F2/2 ALL root_flags If set, the root is mounted readonly 01F4/4 2.04+(2 syssize The size of the 32-bit code in 16-byte paras 01F8/2 ALL ram_size DO NOT USE - for bootsect.S use only 01FA/2 ALL vid_mode Video mode control 01FC/2 ALL root_dev Default root device number 01FE/2 ALL boot_flag 0xAA55 magic number 0200/2 2.00+ jump Jump instruction 0202/4 2.00+ header Magic signature "HdrS" 0206/2 2.00+ version Boot protocol version supported 0208/4 2.00+ realmode_swtch Boot loader hook (see below) 020C/2 2.00+ start_sys The load-low segment (0x1000) (obsolete) 020E/2 2.00+ kernel_version Pointer to kernel version string 0210/1 2.00+ type_of_loader Boot loader identifier 0211/1 2.00+ loadflags Boot protocol option flags 0212/2 2.00+ setup_move_size Move to high memory size (used with hooks) 0214/4 2.00+ code32_start Boot loader hook (see below)0218/4 2.00+ ramdisk_image initrd load address (set by boot loader) 021C/4 2.00+ ramdisk_size initrd size (set by boot loader) 0220/4 2.00+ bootsect_kludge DO NOT USE - for bootsect.S use only 0224/2 2.01+ heap_end_ptr Free memory after setup end 0226/2 N/A pad1 Unused 0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line 022C/4 2.03+ initrd_addr_max Highest legal initrd address 0230/4 2.05+ kernel_alignment Physical addr alignment required for kernel 0234/1 2.05+ relocatable_kernel Whether kernel is relocatable or not

對於PowerPC,這個地址則是通過寄存器來傳遞的,因為RISC系列的寄存器還是比較多的。

此時我們知道了一個ramdisk在內存中的位置,此時就需要把它解壓縮到另一個文件系統中,從而讓這個文件系統作為根文件系統。linux-2.6.21\init\initramfs.c這裏就開始進行initrd的解壓縮工作,這是平臺無關的,但是initrd如何從bootloader傳過來卻是平臺相關的,也就是386和powerPC傳遞initrd的起始和結束地址的方法是體系相關的。那麽解壓縮到哪裏呢?其實就是解壓縮到我們剛才說到的最為原始的那個不依賴於任何外設的rootfs文件系統的根文件夾下,整個視線在下面的代碼中完成

static int __init populate_rootfs(void) 從這個名字也可以看到,這個populate就是汙染rootfs的意思,也就是在空白的rootfs上創建新的文件系統,來自initrd。這也是rootfs第一次大批量的接受文件系統的創建和鏈接等
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
if (err)
panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
int fd;
printk(KERN_INFO "checking if image is initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
if (!err) {
printk(" it is\n");
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0
);
free_initrd();
return 0;
}
printk("it isn‘t (%s); looks like an initrd\n", err);
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
printk(KERN_INFO "Unpacking initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
if (err)
panic(err);
printk(" done\n");
free_initrd();
#endif
}
#endif
return 0;
}

具體的文件系統的創建 unpack_to_rootfs--->>>write_buffer--->>actions--->>do_symlink等完成cpio中文件系統向rootfs文件系統的創建。此時根文件系統就由大量的文件夾了,可能還包括copyfile傳遞過來的文件

root=命令的解析和使用linux-2.6.21\init\do_mounts.c

__setup("root=", root_dev_setup);

static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}

這裏就將root設備保存到了saved_root_name中,從而可以在之後使用,註意:此時rootfs中已經保存了initrd中的內容,所以整個root是一個文件系統的路徑。同樣是在這個文件中,完成了對於真正的initrd中文件系統的加載和根文件系統的切換,從而讓用戶看到的是root=設置的路徑

/*
* 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 */
while (driver_probe_done() != 0)
msleep(100);

md_run_setup();

if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3)) {
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)
root_device_name += 5;
}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (initrd_load())
goto out;

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

mount_root();
out:
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
security_sb_post_mountroot();
}

最後的sys_chroot將會修改根文件

set_fs_root(current->fs, nd.mnt, nd.dentry);
set_fs_altroot();


然後通過name_to_dev_t將一個字符串形式的路徑轉換為一個設備,例如可能是/dev/ram0,表示整個根文件系統在一個randisk中,二這個ramdisk很可能就是一個flash。註意:這個接口比較妙,它可以將一個字符串路徑轉換為一個設備號,而ROOT_DEV是早期版本中內核啟動的依據。這樣加上這個函數,就可以使之後的代碼依然只依賴和使用ROOT_DEV這種簡單的《Major,Minor》方式找到一個設備,例如一塊硬盤,一個flash,或者VMware使用mapper等。當然,其中也使用了一些技巧,但是還是要強調,根文件是一個設備,這一點可以通過

ls /dev/root -l

看到根文件系統對應的設備文件。

四、總結

在Vmware上的一些測試

者說明內核使用的跟設備的設備號為
[tsecer@Harry BuildDir]$ ls /dev/mapper/vg_harry-lv_root -l
brw-rw----. 1 root disk 253, 0 2011-03-27 11:54 /dev/mapper/vg_harry-lv_root
240-254 block LOCAL/EXPERIMENTAL USE
Allocated for local/experimental use. For devices not
assigned official numbers, these ranges should be
used in order to avoid conflicting with future assignments.

[root@Harry BuildDir]# mkdir /dev/MyRoot
[root@Harry BuildDir]# mount -t ext4 /dev/mapper/vg_harry-lv_root /dev/MyRoot/
[root@Harry BuildDir]# ls /dev/MyRoot/
bin dev home lost+found mnt proc sbin srv tmp var
boot etc lib media opt root selinux sys usr
[root@Harry BuildDir]# cd /dev/MyRoot/
[root@Harry MyRoot]# ls
bin dev home lost+found mnt proc sbin srv tmp var
boot etc lib media opt root selinux sys usr
[root@Harry MyRoot]# ls root/
anaconda-ks.cfg install.log install.log.syslog
[root@Harry MyRoot]# ls /root/
anaconda-ks.cfg install.log install.log.syslog
[root@Harry MyRoot]# ls /home/

但是對於動態掛接的文件,這裏並沒有創建這麽多的設備文件
[root@Harry MyRoot]# pwd
/dev/MyRoot
[root@Harry MyRoot]# ls /dev/
agpgart floppy-fd0 parport0 snapshot tty31 tty61
block full port snd tty32 tty62
bsg fuse ppp sr0 tty33 tty63
bus gpmctl ptmx stderr tty34 tty7
cdrom hpet pts stdin tty35 tty8

initrd、rootfs及內核root=參數