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);
}
本文详细介绍了initrd在Linux系统启动过程中的作用及其不同格式的处理流程。解释了initrd的两种主要形式:image-initrd和cpio-initrd,并探讨了它们在内核启动过程中的差异。同时,文章还分析了initrd的制作方法、内核处理流程以及它们各自的优势。
2314

被折叠的 条评论
为什么被折叠?



