initrd学习笔记

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);
}
 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值