http://www.ibm.com/developerworks/cn/linux/l-k26initrd/#N1003C
1. 什么是initrd
initrd 的英文含义是 boot loader initialized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。 在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段: 第一阶段先执行 initrd 文件系统中的"某个文件",完成加载驱动模块等任务; 第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。 这里提到的"某个文件",2.6之后的内核会同以前版本有所不同,所以这里暂时使用了"某个文件"这个称呼,后面会详细讲到。第一阶段启动的目的是为第二阶段的启动扫清一切障碍,最主要的是加载根文件系统存储介质的驱动模块。我们知道根文件系统可以存储在包括IDE、SCSI、USB在内的多种介质上,如果将这些设备的驱动都编译进内核,可以想象内核会多么庞大、臃肿。 Initrd 的用途主要有以下四种: 1>. linux 发行版的必备部件 linux 发行版必须适应各种不同的硬件架构,将所有的驱动编译进内核是不现实的,initrd 技术是解决该问题的关键技术。Linux 发行版在内核中只编译了基本的硬件驱动,在安装过程中通过检测系统硬件,生成包含安装系统硬件驱动的 initrd,无非是一种即可行又灵活的解决方案。 2>. livecd 的必备部件 同 linux 发行版相比,livecd 可能会面对更加复杂的硬件环境,所以也必须使用 initrd。 3>. 制作 Linux usb 启动盘必须使用 initrd usb 设备是启动比较慢的设备,从驱动加载到设备真正可用大概需要几秒钟时间。如果将 usb 驱动编译进内核,内核通常不能成功访问 usb 设备中的文件系统。因为在内核访问 usb 设备时, usb 设备通常没有初始化完毕。所以常规的做法是,在 initrd 中加载 usb 驱动,然后休眠几秒中,等待 usb设备初始化完毕后再挂载 usb 设备中的文件系统。 4>. 在 linuxrc 脚本中可以很方便地启用个性化 bootsplash。
2. Linux2.4内核对 Initrd 的处理流程
为了更清晰的了解Linux 内核initrd机制的变化,在了解2.6的initrd机制之前,先简单看一下2.4的initrd机制。Linux2.4内核的initrd的格式是文件系统镜像文件,这里将其称为image-initrd,以区别后面介绍的linux2.6内核的cpio格式的initrd。 linux2.4内核对initrd的处理流程如下: 1>. boot loader把内核以及/dev/initrd的内容加载到内存,/dev/initrd是 由boot loader初始化的设备,存储着initrd。 2>. 在内核初始化过程中,内核把 /dev/initrd 设备的内容解压缩并拷贝到 /dev/ram0 设备上。 3>. 内核以可读写的方式把 /dev/ram0 设备挂载为原始的根文件系统。 4>. 如果 /dev/ram0 被指定为真正的根文件系统,那么内核跳至最后一步正常启动。 5>. 执行 initrd 上的 /linuxrc 文件,linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统 必需的驱动, 以及加载根文件系统。 6>. /linuxrc 执行完毕,真正的根文件系统被挂载。 7>. 如果真正的根文件系统存在 /initrd 目录,那么 /dev/ram0 将从 / 移动到 /initrd。否则如果 /initrd 目录不存在, /dev/ram0 将被卸载。 8>. 在真正的根文件系统上进行正常启动过程 ,执行 /sbin/init。 linux2.4 内核的 initrd 的执行是作为内核启动的一个中间阶段,也就是说 initrd 的 /linuxrc 执行以后,内核会继续执行初始化代码,后面会看到这是 linux2.4 内核同 2.6 内核的 initrd 处理流程的一个显著区别。
3. Linux2.6 内核对 Initrd 的处理流程
linux2.6 内核支持两种格式的 initrd,一种是前面第 3 部分介绍的 linux2.4 内核那种传统格式的文件系统镜像-image-initrd,它的制作方法同 Linux2.4 内核的 initrd 一样,其核心文件就是 /linuxrc。另外一种格式的 initrd 是 cpio 格式的,这种格式的 initrd 从 linux2.5 起开始引入,使用 cpio 工具生成,其核心文件不再是 /linuxrc,而是 /init,本文将这种 initrd 称为 cpio-initrd。尽管 linux2.6 内核对 cpio-initrd和 image-initrd 这两种格式的 initrd 均支持,但对其处理流程有着显著的区别,下面分别介绍 linux2.6 内核对这两种 initrd 的处理流程。 cpio-initrd 的处理流程 (1). boot loader 把内核以及 initrd 文件加载到内存的特定位置。 (2). 内核判断initrd的文件格式,如果是cpio格式。 (3). 将initrd的内容释放到rootfs中。 (4). 执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。 image-initrd的处理流程 (1). boot loader把内核以及initrd文件加载到内存的特定位置。 (2). 内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理。 (3). 内核将initrd的内容保存在rootfs下的/initrd.image文件中。 (4). 内核将/initrd.image的内容读入/dev/ram0设备中,也就是读入了一个内存盘中。 (5). 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。 (6). .如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。 (7). 执行initrd上的/linuxrc文件,linuxrc通常是一个脚本文件,负责加载内核访问根文件系统 必须的驱动, 以及加载根文件系统。 (8). /linuxrc执行完毕,常规根文件系统被挂载 (9). 如果常规根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd。否则如果 /initrd目录不存在, /dev/ram0将被卸载。 (10).在常规根文件系统上进行正常启动过程 ,执行/sbin/init。 通过上面的流程可知,Linux2.6内核对image-initrd的处理流程同linux2.4内核相比并没有显著的变化, cpio-initrd的处理流程相比于image-initrd的处理流程却有很大的区别,流程非常简单,在后面的源代码分析中,读者更能体会到处理的简捷。
4. cpio-initrd同image-initrd的区别与优势
<1>cpio-initrd的制作方法更加简单 cpio-initrd的制作非常简单,通过两个命令就可以完成整个制作过程: #假设当前目录位于准备好的initrd文件系统的根目录下 bash# find . | cpio -c -o > ../initrd.img bash# gzip ../initrd.img 而传统initrd的制作过程比较繁琐,需要如下六个步骤: #假设当前目录位于准备好的initrd文件系统的根目录下 bash# dd if=/dev/zero of=../initrd.img bs=512k count=5 bash# mkfs.ext2 -F -m0 ../initrd.img bash# mount -t ext2 -o loop ../initrd.img /mnt bash# cp -r * /mnt bash# umount /mnt bash# gzip -9 ../initrd.img <2>cpio-initrd的内核处理流程更加简化 通过对比可知cpio-initrd的处理流程在如下两个方面得到了简化: (1). cpio-initrd并没有使用额外的ramdisk,而是将其内容输入到rootfs中, 其实rootfs本身也是一个基于内存的文件系统。这样就省掉了ramdisk的挂载、卸载等步骤。 (2). cpio-initrd启动完/init进程,内核的任务就结束了,剩下的工作完全交给/init处理; 而对于image-initrd,内核在执行完/linuxrc进程后,还要进行一些收尾工作,并且要负责 执行真正的根文件系统的/sbin/init。 内核对cpio-initrd和image-initrd处理流程的对比如下: image-initrd : 内核-->initrd-->内核-->根文件系统 cpio-initrd : 内核-->initrd-------->根文件系统 <3>cpio-initrd的职责更加重要 cpio-initrd不再象image-initrd那样作为linux内核启动的一个中间步骤,而是作为内核启动的终点,内核将控制权交给cpio-initrd的/init文件后,内核的任务就结束了,所以在/init文件中,我们可以做更多的工作,而不比担心同内核后续处理的衔接问题。当然目前linux发行版的cpio-initrd的/init文件的内容还没有本质的改变,但是相信initrd职责的增加一定是一个趋势。5. 两个概念解释rootfs : 一个基于内存的文件系统,是linux在初始化时加载的第一个文件系统。 initramfs: initramfs同本文的主题关系不是很大,但是代码中涉及到了initramfs,为了更好的理解 代码,这里对其进行简单的介绍。Initramfs是在 kernel 2.5中引入的技术,实际上它的 含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内 核启动时,内核将这个cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的 一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显的好处是 精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。Linux 2.6.12内核的 initramfs还没有什么实质性的东西,一个包含完整功能的initramfs的实现可能还需要一 个缓慢的过程。@init/Makefile ########################################################### obj-y := main.o version.o mounts.o ifneq ($(CONFIG_BLK_DEV_INITRD),y) obj-y += noinitramfs.o else obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o endif obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o mounts-y := do_mounts.o mounts-$(CONFIG_BLK_DEV_RAM) += do_mounts_rd.o mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o # dependencies on generated files need to be listed explicitly $(obj)/version.o: include/generated/compile.h # compile.h changes depending on hostname, generation number, etc, # so we regenerate it always. # mkcompile_h will make sure to only update the # actual file if its content has changed. chk_compile.h = : quiet_chk_compile.h = echo ' CHK $@' silent_chk_compile.h = : include/generated/compile.h: FORCE @$($(quiet)chk_compile.h) $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkcompile_h $@ \ "$(UTS_MACHINE)" "$(CONFIG_SMP)" "$(CONFIG_PREEMPT)" "$(CC) $(KBUILD_CFLAGS)" ##################################################################### 内核中和 initrd 相关的代码主要放在 init 目录下,包括: main.c、noinitramfs.c、initramfs.c、do_mounts.c、do_mounts_initrd.c、 do_mounts_rd.c 和 do_mounts_md.c。 从 Makefile 中可以看出: noinitramfs.c : 在内核不支持 initrd 的情况下被编译进内核, initramfs.c :正好相反,它处理(cpio包类型的)的 initrd 。 do_mounts.c :主要是负责挂载根文件系统的,所以总是被编译。 do_mounts_initrd.c : 负责调用挂载和处理(ramdisk类型的)的 initrd 。 do_mounts_rd.c : 是具体实现如何挂载(ramdisk类型的)的 initrd 。 do_mount_md.c : 处理和 RAID 有关的一些情况。 cpio-initrd的处理: 内核在初始化启动的时候会先注册一个叫作 rootfs 的文件系统,然后通过 rootfs_initcall 来生成其中的内容。根据内核是否支持 initrd 和 ramdisk ,rootfs 的生成方法和内容都会 有所不同。 当内核不支持 initrd 时,rootfs_initcall 调用 noinitramfs.c 中的 default_rootfs() 函数。default_rootfs() 主要往 rootfs 中生成两个目录 /dev 和 /root 以及一个设备文件 /dev/console. 如果内核支持 initrd,但并没有配置 CONFIG_INITRAMFS_SOURCE 选项的话,内核在编译的时候 会自动生成一个最小的 cpio 包附在内核中。这个内核自带的 cpio 包的内容与由 default_rootfs() 生成的一样。具体可参见编译后的内核源码树中的 usr/initramfs_data.cpio.gz 文件。static int __init kernel_init(void * unused) +-- do_basic_setup(); +-- do_initcalls(); +-- for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level); +-- for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) +-- do_one_initcall(*fn); +-- ret = fn(); #define pure_initcall(fn) __define_initcall("0",fn,0) #define core_initcall(fn) __define_initcall("1",fn,1) #define core_initcall_sync(fn) __define_initcall("1s",fn,1s) #define postcore_initcall(fn) __define_initcall("2",fn,2) #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s) #define arch_initcall(fn) __define_initcall("3",fn,3) #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s) #define subsys_initcall(fn) __define_initcall("4",fn,4) #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #define fs_initcall(fn) __define_initcall("5",fn,5) #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #define device_initcall(fn) __define_initcall("6",fn,6) #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) #define late_initcall(fn) __define_initcall("7",fn,7) #define late_initcall_sync(fn) __define_initcall("7s",fn,7s) #define __initcall(fn) device_initcall(fn)
@initramfs.c rootfs_initcall(populate_rootfs); 由此,populate_rootfs()就是在 do_initcalls()函数中调用的//对于cpio-initrd,如果在 populate_rootfs() 中成功地 unpack_to_rootfs() 的话,之后内核就不会再对 //initrd 作任何操作,也不会去挂载根文件系统,所有的工作都留给 cpio 包(也就是rootfs)中的 /init //去完成了。(cpio-initrd ==> /dev/ram) //对于image-initrd,[initrd_start, initrd_end]被写到/initrd.image, //(image-initrd ==> /initrd.image) //后面/initrd.image会在下面的调用过程中被读出并写入到/dev/ram设备中 //kernel_init() -> prepare_namespace() -> initrd_load() -> rd_load_image("/initrd.image") //因此,不论是cpio-initrd还是image-inird,最终都会被释放到/dev/ram中 static int __init populate_rootfs(void) { //加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的, //initramfs的是作为内核的一部分而存在的,不是 boot loader加载的。现在initramfs没有 //任何实质内容。 char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size); if (err) panic(err); /* Failed to decompress INTERNAL initramfs */ //判断是否加载了initrd。无论哪种格式的initrd,都会被boot loader加载到地址initrd_start处。 if (initrd_start) { #ifdef CONFIG_BLK_DEV_RAM int fd; printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n"); //判断加载的是不是cpio-initrd。 err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); //如果是cpio-initrd则将其内容释放出来到rootfs中之后直接将[initrd_start,initrd_end] //释放掉,然后返回,否则clean_rootfs()后,重新 //unpack_to_rootfs(__initramfs_start, __initramfs_size); //然后将 if (!err) { free_initrd(); return 0; } else { clean_rootfs(); unpack_to_rootfs(__initramfs_start, __initramfs_size); } printk(KERN_INFO "rootfs image is not initramfs (%s)" "; looks like an initrd\n", err); //走到这里,说明[initrd_start, initrd_end]不是cpio-initrd,于是认为是一个image-initrd, //将其内容保存到/initrd.image中。在后面的image-initrd的处理代码中会读取/initrd.image //(对于image-initrd的处理代码主要早prepare_namespace()函数中) fd = sys_open((const char __user __force *) "/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...\n"); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); if (err) printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err); free_initrd(); #endif } return 0; }
//实际上 unpack_to_rootfs有两个功能: //1. 释放cpio包; //2. 判断是不是cpio包 static char * __init unpack_to_rootfs(char *buf, unsigned len) { int written, res; decompress_fn decompress; const char *compress_name; static __initdata char msg_buf[64]; header_buf = kmalloc(110, GFP_KERNEL); symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL); name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL); if (!header_buf || !symlink_buf || !name_buf) panic("can't allocate buffers"); state = Start; this_header = 0; message = NULL; while (!message && len) { loff_t saved_offset = this_header; if (*buf == '0' && !(this_header & 3)) { state = Start; //这个写操作是通过状态机来完成的 written = write_buffer(buf, len); buf += written; len -= written; continue; } if (!*buf) { buf++; len--; this_header++; continue; } this_header = 0; decompress = decompress_method(buf, len, &compress_name); if (decompress) { res = decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error); if (res) error("decompressor failed"); } else if (compress_name) { if (!message) { snprintf(msg_buf, sizeof msg_buf, "compression method %s not configured", compress_name); message = msg_buf; } } else error("junk in compressed archive"); if (state != Reset) error("junk in compressed archive"); this_header = saved_offset + my_inptr; buf += my_inptr; len -= my_inptr; } dir_utime(); kfree(name_buf); kfree(symlink_buf); kfree(header_buf); return message; } unpack_to_rootfs(char *buf, unsigned len) +-- state = Start; //初始化状态机的状态 +-- this_header = 0; +-- message = NULL; +-- write_buffer(char *buf, unsigned len) +-- count = len; //初始化全局变量count +-- victim = buf; //初始化全局变量victim +-- while (!actions[state]());//只要actions[state]()返回0就反复调用下去,遍历状态机的各个状态 //状态机的状态切换通过遍历一个名为actions[]数组来实现 static int __init write_buffer(char *buf, unsigned len) { count = len; victim = buf; //actions[]数组中的函数将先后被调用 while (!actions[state]()) ; return len - count; } static __initdata int (*actions[])(void) = { [Start] = do_start, [Collect] = do_collect, [GotHeader] = do_header, [SkipIt] = do_skip, [GotName] = do_name, [CopyFile] = do_copy, [GotSymlink] = do_symlink, [Reset] = do_reset, }; do_start(void) +-- read_into(header_buf, 110, GotHeader); +-- if(count > 110){ +-- collected = victim; //初始化全局变量collected +-- eat(size); +-- state = next; //设置下一个调用的函数是do_header() +-- else //(count < 110) +-- collect = collected = header_buf; +-- remains = size; +-- next_state = GotHeader; //设置从当前状态跳出后调用的函数是do_header() +-- state = Collect; //设置下一个调用的函数是do_cellect() do_collect() +-- n = remains; +-- memcpy(collect, victim, n); //将victim中的数据拷贝n个字节到collect中 +-- eat(n); +-- collect += n; //更新collect指针的位置 +-- remains -= n; //更新remains指定的剩余字节数 +-- state = next_state //之后跳转到next_state中指定的下一状态 do_header() +-- memcmp(collected, "070707", 6) +-- memcmp(collected, "070701", 6) +-- parse_header(collected); +-- state = SkipIt; +-- if (S_ISLNK(mode)) { if (body_len > PATH_MAX) return 0; collect = collected = symlink_buf; remains = N_ALIGN(name_len) + body_len; next_state = GotSymlink; state = Collect; return 0; } +-- if (S_ISREG(mode) || !body_len) read_into(name_buf, N_ALIGN(name_len), GotName); //do_name()或者do_collect() do_name() +-- state = SkipIt; +-- next_state = Reset; +-- if (strcmp( collected, "TRAILER!!!") == 0) { free_hash(); return 0; } +-- clean_path(collected, mode); +-- if(S_ISREG(mode)){ int openflags = O_WRONLY|O_CREAT; wfd = sys_open(collected, openflags, mode); //将collected指向的文件打开 if (wfd >= 0) { sys_fchown(wfd, uid, gid); sys_fchmod(wfd, mode); if (body_len) sys_ftruncate(wfd, body_len); vcollected = kstrdup(collected, GFP_KERNEL); state = CopyFile; //之后跳转到CopyFile } } +-- else if (S_ISDIR(mode)) { sys_mkdir(collected, mode); sys_chown(collected, uid, gid); sys_chmod(collected, mode); dir_add(collected, mtime); //创建目录 } +-- else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) { if (maybe_link() == 0) { sys_mknod(collected, mode, rdev); sys_chown(collected, uid, gid); sys_chmod(collected, mode); do_utime(collected, mtime); } } do_copy() +-- if (count >= body_len) { sys_write(wfd, victim, body_len); //将victim指向的内存写入wfd sys_close(wfd); do_utime(vcollected, mtime); kfree(vcollected); eat(body_len); state = SkipIt; return 0; } +-- else { sys_write(wfd, victim, count); //将victim指向的内存写入wfd body_len -= count; eat(count); return 1; }
void __init setup_arch(char **cmdline_p) +-- arm_memblock_init(&meminfo, mdesc); +-- if (phys_initrd_size) { memblock_reserve(phys_initrd_start, phys_initrd_size); /* Now convert initrd to virtual addresses */ initrd_start = __phys_to_virt(phys_initrd_start); initrd_end = initrd_start + phys_initrd_size; } early_param("initrd", early_initrd); static int __init early_initrd(char *p) +-- start = memparse(p, &endp); +-- if (*endp == ',') { size = memparse(endp + 1, NULL); phys_initrd_start = start; phys_initrd_size = size; } #define early_param(str, fn) __setup_param(str, fn, fn, 1) void __init setup_arch(char **cmdline_p) +-- mdesc = setup_machine_fdt(__atags_pointer); +-- if (!mdesc) mdesc = setup_machine_tags(machine_arch_type); +-- if (mdesc->fixup) //board spec的函数fixup创建字符串from mdesc->fixup(tags, &from, &meminfo); //from --> boot_command_line +-- strlcpy(boot_command_line, from, COMMAND_LINE_SIZE); //boot_command_line --> cmd_line +-- strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE); +-- *cmdline_p = cmd_line; //cmd_line --> *cmdlie_p asmlinkage void __init start_kernel(void) +-- setup_arch(&command_line); //*cmdline_p --> command_line +-- setup_command_line(command_line); // +-- saved_command_line = alloc_bootmem(strlen (boot_command_line)+1); static_command_line = alloc_bootmem(strlen (command_line)+1); strcpy (saved_command_line, boot_command_line); strcpy (static_command_line, command_line); //get static_command_line +-- parse_args("Booting kernel", static_command_line, __start___param, //static_command_line --> args __stop___param - __start___param, 0, 0, &unknown_bootoption); +-- while (*args) { +-- args = next_arg(args, ¶m, &val); //从args里面提取参数字符串 //对单个字符串进行解析 +--ret = parse_one(param, val, params, num, min_level, max_level, unknown); } early_param("initrd", early_initrd); #define early_param(str, fn) __setup_param(str, fn, fn, 1) 因此early_initrd()是在 p->setup_func(line + n) 的时候调用的 那么line+n又是哪来得呢? p = __setup_start; do { int n = strlen(p->str); if (parameqn(line, p->str, n)) { //“initrd=0x101100,0x20000” line+n就指向“0x101100,0x20000” p->setup_func(line + n)) } p++; }while (p < __setup_end); 遍历[__setup_start,__setup_end],用字符串line和其中每个obs_kernel_param的str指向的字符串比较 (长度n设定为str的长度),找到匹配的obs_kernel_param结构,然后调用其setup_func() 因此问题就成了line是哪里来的 obsolete_checksetup(param); //param-->line +-- parse_args("Booting kernel", static_command_line, //static_command_line --> args __start___param, __stop___param - __start___param, 0, 0, &unknown_bootoption); +-- while (*args) { args = next_arg(args, ¶m, &val); //从args里面提取参数字符串param //对单个字符串param进行解析 ret = parse_one(param, val, params, num, min_level, max_level, unknown); } 由此可知param就是从static_command_line中提取出来的单个的参数字符串 static int __init early_initrd(char *p) { unsigned long start, size; char *endp; start = memparse(p, &endp); //从“0x101100,0x20000”中提取出start=0x101100 if (*endp == ',') { size = memparse(endp + 1, NULL); //从“,0x20000”中提取出0x20000 phys_initrd_start = start; phys_initrd_size = size; } return 0; } early_param("initrd", early_initrd); //make menuconfig的时候给出的boot_command_line static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; static int __init parse_tag_cmdline(const struct tag *tag) { #if defined(CONFIG_CMDLINE_EXTEND) strlcat(default_command_line, " ", COMMAND_LINE_SIZE); strlcat(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); #elif defined(CONFIG_CMDLINE_FORCE) pr_warning("Ignoring tag cmdline (using the default kernel command line)\n"); #else strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); #endif return 0; } __tagtable(ATAG_CMDLINE, parse_tag_cmdline); #define __tag __used __attribute__((__section__(".taglist.init"))) #define __tagtable(tag, fn) \ static const struct tagtable __tagtable_##fn __tag = { tag, fn } 实际上就是定义了一个struct tagtable并放在了__section__(".taglist.init") static struct machine_desc * __init setup_machine_tags(unsigned int nr) { struct tag *tags = (struct tag *)&init_tags; //tags的默认值是init_tags struct machine_desc *mdesc = NULL, *p; //boot_command_line主要是来自default_command_line char *from = default_command_line; init_tags.mem.start = PHYS_OFFSET; /* * locate machine in the list of supported machines. */ for_each_machine_desc(p) if (nr == p->nr) { printk("Machine: %s\n", p->name); mdesc = p; break; } if (!mdesc) { early_print("\nError: unrecognized/unsupported machine ID" " (r1 = 0x%08x).\n\n", nr); dump_machine_table(); /* does not return */ } //获取tags的值 //从boot传入的__atags_pointer参数获取 if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->atag_offset) tags = (void *)(PAGE_OFFSET + mdesc->atag_offset); //从mdesc获取 if (tags->hdr.tag != ATAG_CORE) { tags = (struct tag *)&init_tags; } //某些板子会定义对tags内存段内参数的修正函数fixup if (mdesc->fixup) mdesc->fixup(tags, &from, &meminfo); if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags);//根据tags内的参数对default_command_line进行修正 } /* parse_early_param needs a boot_command_line */ //将default_command_line copy到boot_command_line strlcpy(boot_command_line, from, COMMAND_LINE_SIZE); return mdesc; }
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; default_command_line实际上就是make menuconfig的时候指定的CMDLINE,可以在 .config文件中找到 再看看__initramfs_start指向的内存区是怎样填充的 =========================================================== @Vmlinux.lds.h (include\asm-generic): #ifdef CONFIG_BLK_DEV_INITRD #define INIT_RAM_FS \ . = ALIGN(4); \ //这里指定了__initramfs_start指向哪里 VMLINUX_SYMBOL(__initramfs_start) = .; \ *(.init.ramfs) \ . = ALIGN(8); \ *(.init.ramfs.info) #else #define INIT_RAM_FS #endif //于是,如果定义了CONFIG_BLK_DEV_INTRD,则__initramfs_start之后就是.init.ramfs section和 //.init.ramfs.info section $grep -irn "INITRAMFS_IMAGE" ./* ./usr/Makefile:24: AFLAGS_initramfs_data.o += -DINITRAMFS_IMAGE="usr/initramfs_data.cpio$(suffix_y)" ./usr/initramfs_data.S:29: .incbin __stringify(INITRAMFS_IMAGE) @usr/initramfs_data.S #include #include .section .init.ramfs,"a" __irf_start: .incbin __stringify(INITRAMFS_IMAGE) __irf_end: .section .init.ramfs.info,"a" .globl VMLINUX_SYMBOL(__initramfs_size) VMLINUX_SYMBOL(__initramfs_size): #ifdef CONFIG_64BIT .quad __irf_end - __irf_start #else .long __irf_end - __irf_start #endif @usr/Makefile initramfs := $(CONFIG_SHELL) $(srctree)/scripts/gen_initramfs_list.sh ramfs-input := $(if $(filter-out "",$(CONFIG_INITRAMFS_SOURCE)), \ $(shell echo $(CONFIG_INITRAMFS_SOURCE)),-d) …… $(obj)/initramfs_data.cpio$(suffix_y): $(obj)/gen_init_cpio $(deps_initramfs) klibcdirs #initramfs 脚本($(srctree)/scripts/gen_initramfs_list.sh)产生一个list? $(Q)$(initramfs) -l $(ramfs-input) > $(obj)/.initramfs_data.cpio.d #调用initfs 函数 $(call if_changed,initfs)static int __init rdinit_setup(char *str) { unsigned int i; //command line里面如果指定了rdinit=*** //则这里会将ramdisk_execute_command执行该字符串 ramdisk_execute_command = str; for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("rdinit=", rdinit_setup); static int __init init_setup(char *str) { unsigned int i; //command line 里面如果指定了init=***, //则这里会将 execute_command指向该字符串 execute_command = str; for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("init=", init_setup); static int __init kernel_init(void * unused) +-- wait_for_completion(&kthreadd_done); +-- set_mems_allowed(node_states[N_HIGH_MEMORY]); +-- set_cpus_allowed_ptr(current, cpu_all_mask); +-- cad_pid = task_pid(current); +-- smp_prepare_cpus(setup_max_cpus); +-- do_pre_smp_initcalls(); +-- lockup_detector_init(); +-- smp_init(); +-- sched_init_smp(); +-- do_basic_setup(); //cpio-initrd ==> /dev/ram或者image-initrd ==> /initrd.image +-- init_calls --> init_setup(); //"init=×××" ==> execute_command --> rdinit_setup(); //"rdinit=×××" ==> ramdisk_execute_command --> populate_rootfs(); //cpio-initrd释放到/dev/ram 或者 //image-initrd释放到/initrd.image ====================================================================================== +-- if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); //如果命令行里面没有指定rdinit=***,则这里将其指定为/init +-- if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; //如果ramdisk_execute_command指定的程序不存在,清空ramdisk_execute_command //然后prepare_namespace() +-- if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; //prepare_namespace()主要负责的有两个事情: //1.检查如果是image-initrd,则将/initrd.image中的内容读取出来,并写入到/dev/ram中。 //2.将/dev/ram挂载到根目录 prepare_namespace(); } ======================================================================================= +-- init_post(); //若果ramdisk_execute_command指定的文件存在,则转入该user space程序的执行 +-- if (ramdisk_execute_command) run_init_process(ramdisk_execute_command); //如果ramdisk_execute_command指定的文件不存在,则看命令行里面有没有指定init=*** //如果有指定,则执行execute_command指定的用户空间程序 +-- if (execute_command) run_init_process(execute_command); //如果ramdisk_execute_command和execute_command指定的文件都不存在,则分别往下搜索 //分别按照 /sbin/init、/etc/init、/bin/init、/bin/sh优先级由高到低的顺序找到就转入 //该程序的执行 +-- run_init_process("/sbin/init"); +-- run_init_process("/etc/init"); +-- run_init_process("/bin/init"); +-- run_init_process("/bin/sh");
//prepare_namespace()主要负责挂载根文件系统和 ramdisk 类型的 initrd 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_device_probe(); md_run_setup(); //static int __init root_dev_setup(char *line){ // strlcpy(saved_root_name, line, sizeof(saved_root_name)); // return 1; //} //__setup("root=", root_dev_setup); //由此,如果命令行中传入了root=×××指明了root device,这里save_root_name就是××× //由此,进入if if (saved_root_name[0]) { root_device_name = saved_root_name; if (!strncmp(root_device_name, "mtd", 3) || !strncmp(root_device_name, "ubi", 3)) { mount_block_root(root_device_name, root_mountflags); goto out; } ROOT_DEV = name_to_dev_t(root_device_name); //如果root_device_name有"/dev/"目录前缀,去掉,只留后面的文件名 if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } //把 initrd 释放到内存盘中 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; //rootfs 中新建了一个名为 /dev/root 设备节点, //这个设备文件一般就是内核启动参数指定的包含根文件系统的设备。 //在 rootfs 中,这个设备文件被命名为 /dev/root。 mount_root(); out: devtmpfs_mount("dev"); //由于之前已经切换到了新的根文件系统的根目录中去,所以这两步的作用是 //用新的根文件系统的根目录替换 rootfs , sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot((const char __user __force *)"."); } void __init prepare_namespace(void) +-- md_run_setup(); +-- create_dev("/dev/md0", MKDEV(MD_MAJOR, 0)); +-- md_setup_drive(); +-- initrd_load() //主要部分在这个函数里面 +-- mount_root(); +-- devtmpfs_mount("dev"); +-- sys_mount(".", "/", NULL, MS_MOVE, NULL); +-- sys_chroot((const char __user __force *)"."); //变量 mount_initrd 为是否要加载 initrd 的标志,默认为1,当内核启动参数中包含 noinitrd 字符串时, //mount_initrd 会被设为0 。接着为了把 initrd 释放到内存盘中,需先创建设备文件,然后通过 //rd_load_image 把之前保存的 /initrd.image 加载到内存盘中。之后判断如果内核启动参数中指定的最终的 //根文件系统不是内存盘的话,那就先要执行 initrd 中的 linuxrc;如果最终的根文件系统就是刚加载到 //内存盘的 initrd 的话,那就先不处理它,留到之后当真正的根文件系统处理。 int __init initrd_load(void) { if (mount_initrd) { create_dev("/dev/ram", Root_RAM0); /* * Load the initrd data into /dev/ram0. Execute it as initrd * unless /dev/ram0 is supposed to be our actual root device, * in that case the ram disk is just set up here, and gets * mounted in the normal path. */ //如果内核启动参数中指定的最终的根文件系统不是内存盘的话(ROOT_DEV != Root_RAM0), //那就先要执行 initrd 中的 linuxrc if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); handle_initrd(); return 1; } } sys_unlink("/initrd.image"); return 0; } //将from指向的文件内容写入/dev/ram设备中,这里from指向"/initrd.image"文件 int __init rd_load_image(char *from) +-- out_fd = sys_open((const char __user __force *) "/dev/ram", O_RDWR, 0); +-- in_fd = sys_open(from, O_RDONLY, 0); //from指向的是"/initrd.image" +-- for (i = 0, disk = 1; i < nblocks; i++) { sys_read(in_fd, buf, BLOCK_SIZE); sys_write(out_fd, buf, BLOCK_SIZE); } static void __init handle_initrd(void) { int error; int pid; //initrd_load()之前,已经给ROOT_DEV赋值了 //ROOT_DEV = name_to_dev_t(root_device_name); real_root_dev = new_encode_dev(ROOT_DEV); //创建对应ROOT_DEV的临时设备节点"/dev/root.old"。 create_dev("/dev/root.old", Root_RAM0); //mount initrd 到 rootfs的/root目录 //mount_block_root会尝试把参数"/dev/root.old"指定的设备文件(也就是/dev/ram) //挂载到 /root 目录中去,并 cd 到新的'根文件系统'的根目录(也就是/root目录)中去。 mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY); 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 */ sys_chdir("/root");//change cwd //flags为MS_MOVE,表示把已安装的设备"/dev/root.old"(也就是/dev/ram), //从安装点"/root"移到另一个安装点"/" sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); /* * In case that a resume from disk is carried out by linuxrc or one of * its children, we need to tell the freezer not to wait for us. */ current->flags |= PF_FREEZER_SKIP; //执行 initrd 中的 linuxrc pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); // +-- kernel_execve("/linuxrc", argv, envp_init); if (pid > 0) while (pid != sys_wait4(-1, NULL, 0, NULL)) yield(); current->flags &= ~PF_FREEZER_SKIP; /* move initrd to rootfs' /old */ //fchdir返回到先前的工作目录,需要使用更换目录前保存的文件描述符 sys_fchdir(old_fd); //当前目录为/old //flags为MS_MOVE,表示把已安装的设备"/dev/root.old", 从安装点"/"移到另一个安装点"/old" sys_mount("/", ".", NULL, MS_MOVE, NULL); /* switch root and cwd back to / of rootfs */ //fchdir返回到先前的工作目录,需要使用更换目录前保存的文件描述符 sys_fchdir(root_fd);//当前目录变为"/" sys_chroot("."); //根目录变为"/" sys_close(old_fd); sys_close(root_fd); if (new_decode_dev(real_root_dev) == Root_RAM0) { sys_chdir("/old"); return; } ROOT_DEV = new_decode_dev(real_root_dev); //rootfs 中新建了一个名为 /dev/root 设备节点, //这个设备文件一般就是内核启动参数指定的包含根文件系统的设备。 //在 rootfs 中,这个设备文件被命名为 /dev/root 。 mount_root(); printk(KERN_NOTICE "Trying to move old root to /initrd ... "); //flags为MS_MOVE,表示把已安装的设备"/dev/ram"从一个安装点"/old" //移到另一个安装点"/root/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); if (error == -ENOENT) printk("/initrd does not exist. Ignored.\n"); else 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"); } } mount_root(void) +-- create_dev("/dev/root", ROOT_DEV); +-- mount_block_root("/dev/root", root_mountflags); //尝试把参数 name 指定的设备文件挂载到 /root 目录中去,并 cd 到新的根文件系统的根目录中去。 void __init mount_block_root(char *name, int flags) { char *fs_names = __getname_gfp(GFP_KERNEL|__GFP_NOTRACK_FALSE_POSITIVE); char *p; …… //拿到系统中所有的文件名,放在fs_names字符串中 get_fs_names(fs_names); retry: for (p = fs_names; *p; p += strlen(p)+1) { //p所指向的文件系统类型同name所指向的设备的文件系统类型一致时,mount才会成功。 int err = do_mount_root(name, p, flags, root_mount_data); // +-- sys_mount(name/*dev_name*/, "/root"/*dir_name*/, fs/*type*/, // flags, data); // +-- sys_chdir((const char __user __force *)"/root"); // +-- s = current->fs->pwd.dentry->d_sb; // +-- ROOT_DEV = s->s_dev; …… } …… out: putname(fs_names); }