Linux2.6 内核的 Initrd 机制解析(5)

5.Linux2.6内核initrd处理的源代码分析

上面简要介绍了Linux2.4内核和2.6内核的initrd的处理流程,为了使读者对于Linux2.6内核的initrd的处理有一个更加深入的认识,下面将对Linuxe2.6内核初始化部分同initrd密切相关的代码给予一个比较细致的分析,为了讲述方便,进一步明确几个代码分析中使用的概念:

rootfs : 一个基于内存的文件系统,是Linux在初始化时加载的第一个文件系统,关于它的进一步介绍可以参考文献[4]。

initramfs : initramfs同本文的主题关系不是很大,但是代码中涉及到了initramfs,为了更好的理解代码,这里对其进行简单的介绍。Initramfs 是在 kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个 cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。Linux 2.6.12内核的 initramfs还没有什么实质性的东西,一个包含完整功能的initramfs的实现可能还需要一个缓慢的过程。对于initramfs的进一步了解可以参考文献[1][2][3]。

cpio-initrd : 前面已经定义过,指Linux2.6内核使用的cpio格式的initrd。

image-initrd : 前面已经定义过,专指传统的文件镜像格式的initrd。

realfs : 用户最终使用的真正的文件系统。

内核的初始化代码位于 init/main.c 中的 static int init(void * unused)函数中。同initrd的处理相关部分函数调用层次如下图,笔者按照这个层次对每一个函数都给予了比较详细的分析,为了更好的说明,下面列出的代码中删除了同本文主题不相关的部分:

图2

图2 initrd相关代码的调用层次关系图

init函数是内核所有初始化代码的入口,代码如下,其中只保留了同initrd相关部分的代码。

static int init(void * unused){

[1] populate_rootfs();

[2] if (sys_access((const char __user *) "/init", 0) == 0)

execute_command = "/init";

else

prepare_namespace();

[3] if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

printk(KERN_WARNING "Warning: unable to open an initial console.\n");

(void) sys_dup(0);

(void) sys_dup(0);

[4] if (execute_command)

run_init_process(execute_command);

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

panic("No init found.  Try passing init= option to kernel.");

}

代码[1]:populate_rootfs函数负责加载initramfs和cpio-initrd,对于populate_rootfs函数的细节后面会讲到。

代码[2]:如果rootfs的根目录下中包含/init进程,则赋予execute_command,在init函数的末尾会被执行。否则执行prepare_namespace函数,initrd是在该函数中被加载的。

代码[3]:将控制台设置为标准输入,后续的两个sys_dup(0),则复制标准输入为标准输出和标准错误输出。

代码[4]:如果rootfs中存在init进程,就将后续的处理工作交给该init进程。其实这段代码的含义是如果加载了cpio- initrd则交给cpio-initrd中的/init处理,否则会执行realfs中的init。读者可能会问:如果加载了cpio-initrd, 那么realfs中的init进程不是没有机会运行了吗?确实,如果加载了cpio-initrd,那么内核就不负责执行realfs的init进程了,而是将这个执行任务交给了cpio-initrd的init进程。解开Fedora core4的initrd文件,会发现根目录的下的init文件是一个脚本,在该脚本的最后一行有这样一段代码:

………..

switchroot --movedev /sysroot

就是switchroot语句负责加载realfs,以及执行realfs的init进程。

对cpio-initrd的处理

对cpio-initrd的处理位于populate_rootfs函数中。

void __init populate_rootfs(void){

[1]  char *err = unpack_to_rootfs(__initramfs_start,

__initramfs_end - __initramfs_start, 0);

[2] if (initrd_start) {

[3]  err = unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start, 1);

[4]  if (!err) {

printk(" it is\n");

unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start, 0);

free_initrd_mem(initrd_start, initrd_end);

return;

}

[5]  fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);

if (fd >= 0) {

sys_write(fd, (char *)initrd_start,

initrd_end - initrd_start);

sys_close(fd);

free_initrd_mem(initrd_start, initrd_end);

}

}

代码[1]:加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的,initramfs的是作为内核的一部分而存在的,不是 boot loader加载的。前面提到了现在initramfs没有任何实质内容。

代码[2]:判断是否加载了initrd。无论哪种格式的initrd,都会被boot loader加载到地址initrd_start处。

代码[3]:判断加载的是不是cpio-initrd。实际上 unpack_to_rootfs有两个功能一个是释放cpio包,另一个就是判断是不是cpio包, 这是通过最后一个参数来区分的, 0:释放 1:查看。

代码[4]:如果是cpio-initrd则将其内容释放出来到rootfs中。

代码[5]:如果不是cpio-initrd,则认为是一个image-initrd,将其内容保存到/initrd.image中。在后面的image-initrd的处理代码中会读取/initrd.image。

对image-initrd的处理在prepare_namespace函数里,包含了对image-initrd进行处理的代码,相关代码如下:

void __init prepare_namespace(void){

[1] if (initrd_load())

goto out;

out:

umount_devfs("/dev");

[2]  sys_mount(".", "/", NULL, MS_MOVE, NULL);

sys_chroot(".");

security_sb_post_mountroot();

mount_devfs_fs ();

}

代码[1]:执行initrd_load函数,将initrd载入,如果载入成功的话initrd_load函数会将realfs的根设置为当前目录。

代码[2]:将当前目录即realfs的根mount为Linux VFS的根。initrd_load函数执行完后,将真正的文件系统的根设置为当前目录。

initrd_load函数负责载入image-initrd,代码如下:

int __init initrd_load(void)

{

[1] if (mount_initrd) {

create_dev("/dev/ram", Root_RAM0, NULL);

[2]  if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {

sys_unlink("/initrd.image");

handle_initrd();

return 1;

}

}

sys_unlink("/initrd.image");

return 0;

}

代码[1]:如果加载initrd则建立一个ram0设备 /dev/ram。

代码[2]:/initrd.image文件保存的就是image-initrd,rd_load_image函数执行具体的加载操作,将 image-nitrd的文件内容释放到ram0里。判断ROOT_DEV!=Root_RAM0的含义是,如果你在grub或者lilo里配置了 root=/dev/ram0 ,则实际上真正的根设备就是initrd了,所以就不把它作为initrd处理 ,而是作为realfs处理。

handle_initrd()函数负责对initrd进行具体的处理,代码如下:

static void __init handle_initrd(void){

[1] real_root_dev = new_encode_dev(ROOT_DEV);

[2] create_dev("/dev/root.old", Root_RAM0, NULL);

mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);

[3] sys_mkdir("/old", 0700);

root_fd = sys_open("/", 0, 0);

old_fd = sys_open("/old", 0, 0);

/* move initrd over / and chdir/chroot in initrd root */

[4] sys_chdir("/root");

sys_mount(".", "/", NULL, MS_MOVE, NULL);

sys_chroot(".");

mount_devfs_fs ();

[5] pid = kernel_thread(do_Linuxrc, "/Linuxrc", SIGCHLD);

if (pid > 0) {

while (pid != sys_wait4(-1, &i, 0, NULL))

yield();

}

/* move initrd to rootfs' /old */

sys_fchdir(old_fd);

sys_mount("/", ".", NULL, MS_MOVE, NULL);

/* switch root and cwd back to / of rootfs */

[6] sys_fchdir(root_fd);

sys_chroot(".");

sys_close(old_fd);

sys_close(root_fd);

umount_devfs("/old/dev");

[7] if (new_decode_dev(real_root_dev) == Root_RAM0) {

sys_chdir("/old");

return;

}

[8] ROOT_DEV = new_decode_dev(real_root_dev);

mount_root();

[9] printk(KERN_NOTICE "Trying to move old root to /initrd ... ");

error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);

if (!error)

printk("okay\n");

else {

int fd = sys_open("/dev/root.old", O_RDWR, 0);

printk("failed\n");

printk(KERN_NOTICE "Unmounting old root\n");

sys_umount("/old", MNT_DETACH);

printk(KERN_NOTICE "Trying to free ramdisk memory ... ");

if (fd < 0) {

error = fd;

} else {

error = sys_ioctl(fd, BLKFLSBUF, 0);

sys_close(fd);

}

printk(!error ? "okay\n" : "failed\n");

}

handle_initrd函数的主要功能是执行initrd的Linuxrc文件,并且将realfs的根目录设置为当前目录。

代码[1]:real_root_dev,是一个全局变量保存的是realfs的设备号。

代码[2]:调用mount_block_root函数将initrd文件系统挂载到了VFS的/root下。

代码[3]:提取rootfs的根的文件描述符并将其保存到root_fd。它的作用就是为了在chroot到initrd的文件系统,处理完initrd之后要,还能够返回rootfs。返回的代码参考代码[7]。

代码[4]:chroot进入initrd的文件系统。前面initrd已挂载到了rootfs的/root目录。

代码[5]:执行initrd的Linuxrc文件,等待其结束。

代码[6]:initrd处理完之后,重新chroot进入rootfs。

代码[7]:如果real_root_dev在 Linuxrc中重新设成Root_RAM0,则initrd就是最终的realfs了,改变当前目录到initrd中,不作后续处理直接返回。

代码[8]:在Linuxrc执行完后,realfs设备已经确定,调用mount_root函数将realfs挂载到root_fs的 /root目录下,并将当前目录设置为/root。

代码[9]:后面的代码主要是做一些收尾的工作,将initrd的内存盘释放。

到此代码分析完毕。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwpdfx.html