kernel initrd & ramfs
来源:互联网 发布:tcp端口怎么设置 编辑:程序博客网 时间:2024/06/10 13:49
在早期的Linux系统中,一般就只有软盘或者硬盘被用来作为Linux的根文件系统,因此很容易把这些设备的驱动程序集成到内核中。但是现在根文件系统可能保存在各种存储设备上,包括SCSI, SATA, U盘等等。因此把这些设备驱动程序全部编译到内核中显得不太方便。在Linux内核模块自动加载机制的介绍中,我们看到利用udevd可以实现实现内核模块的自动加载,因此我们希望根文件系统的设备驱动程序也能够实现自动加载。但是这里有一个矛盾,udevd是一个可执行文件,在根文件系统被挂载前,是不可能执行udevd的,但是如果udevd没有启动,那就无法自动加载根根据系统设备的驱动程序,同时也无法在/dev目录下建立相应的设备节点。为了解决这个矛盾,于是出现了initrd(boot loader initialized RAM disk)。initrd是一个被压缩过的小型根目录,这个目录中包含了启动阶段中必须的驱动模块,可执行文件和启动脚本。包括上面提到的udevd,当系统启动的时候,booload会把initrd文件读到内存中,然后把initrd的起始地址告诉内核。内核在运行过程中会解压initrd,然后把initrd挂载为根目录,然后执行根目录中的/initrc脚本,您可以在这个脚本中运行initrd中的udevd,让它来自动加载设备驱动程序以及在/dev目录下建立必要的设备节点。在udevd自动加载磁盘驱动程序之后,就可以mount真正的根目录,并切换到这个根目录中。
您可以通过下面的方法来制作一个initrd文件。
# dd if=/dev/zero of=initrd.img bs=4k count=1024 # mkfs.ext2 -F initrd.img # mount -o loop initrd.img /mnt # cp -r miniroot/* /mnt # umount /mnt # gzip -9 initrd.img
see: Docummentation/ramdisk.txt
通过上面的命令,我们制作了一个4M的initrd,其中miniroot就是一个根目录。最后我们得到一个名为initrd.img.gz的压缩文件。
利用initrd内核在启动阶段可以顺利的加载设备驱动程序,然而initrd存在以下缺点:
initrd大小是固定的,例如上面的压缩之前的initrd大小是4M(4k*1024),假设您的根目录(上例中的miniroot/)总大小仅仅是1M,它仍然要占用4M的空间。如果您在dd阶段指定大小为1M,后来发现不够用的时候,必须按照上面的步骤重新来一次。
initrd是一个虚拟的块设备,在上面的例子中,您可是使用fdisk对这个虚拟块设备进行分区。在内核中,对块设备的读写还要经过缓冲区管理模块,也就是说,当内核读取initrd中的文件内容时,缓冲区管理层会认为下层的块设备速度比较慢,因此会启用预读和缓存功能。这样initrd本身就在内存中,同时块设备缓冲区管理层还会保存一部分内容。 为了避免上述缺点,于是出现了initramfs,它的作用和initrd类似,initramfs见第二部分。
initrd的解压缩
在start_kernel()的最后,调用rest_init(),rest_init()会建立一个新的内核进程,并在这个内核进程中执行kernel_init()函数,kernel_init()会调用populate_rootfs()来探测和解压initrd文件。这个函数需要处理上面的几种initrd的情况。
[kernel_init() -> populate_rootfs()]static int __init populate_rootfs(void){ /* 如果__initramfs_end - __initramfs_start不为0,就说明这是和内核文件集成在一起的cpio的intrd。*/ char *err = unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start, 0); if (err) panic(err);#ifdef CONFIG_BLK_DEV_INITRD /* 如果initrd_start不为0,说明这是由bootloader加载的initrd, * 那么需要进一步判断是cpio格式的initrd,还是老式块设备的initrd。 */ if (initrd_start) {#ifdef CONFIG_BLK_DEV_RAM int fd; /* 首先判断是不是cpio格式的initrd,也就是这里说的initramfs。*/ printk(KERN_INFO "checking if image is initramfs..."); /* 这里unpack_to_rootfs()的最后一个参数为1,表示check only,不会执行解压缩。*/ err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 1); if (!err) { /* 如果是cpio格式的initrd,把它解压到前面挂载的根文件系统上,然后释放initrd占用的内存。*/ printk(" it is/n"); unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 0); free_initrd(); return 0; } /* 如果执行到这里,说明这是旧的块设备格式的initrd。 * 那么首先在前面挂载的根目录上创建一个initrd.image文件, * 再把initrd_start到initrd_end的内容写入到/initrd.image中, * 最后释放initrd占用的内存空间(它的副本已经保存到/initrd.image中了。)。 */ printk("it isn't (%s); looks like an initrd/n", err); fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); if (fd >= 0) { sys_write(fd, (char *)initrd_start, initrd_end - initrd_start); sys_close(fd); free_initrd(); } ...... return 0;}rootfs_initcall(populate_rootfs);经过populate_rootfs()函数的处理之后,如果是cpio格式的initrd,那么unpack_to_rootfs()函数已经把目录解压缩到之前mount的根目录上面了。但是如果是旧的块设备的initrd,unpack_to_rootfs()函数解压缩后得到的是一个块虚拟的设备镜像文件/initrd.image,对于这种情况,还需要进一步处理才能使用。接下来,kernel_init()就要处理这种情况。
static int __init kernel_init(void * unused){ ...... do_basic_setup(); /* 内核启动时,可以通过启动参数 rdinit=xxx 来指定启动的最后阶段,需要运行initrd中的哪一个可执行文件, * 如果指定了这个参数,那么ramdisk_execute_command就会指向xxx这字符串,新cpio格式的initrd默认执行/init。 * 因此,如果如果ramdisk_execute_command为NULL, 就把它设置为/init。 */ if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; /* 现在,尝试访问ramdisk_execute_command,默认为/init,如果访问失败,说明根目录上不存在这个文件。 * 于是调用prepare_namespace(),进一步检查是不是旧的块设备的initrd * (在这种情况下,还是一个块设备镜像文件/initrd.image,所以访问/init文件失败。)。 */ if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace(); } init_post(); return 0;}老式的initrd的处理prepare_namespace()用于处理老式的initrd。
[kernel_init() -> prepare_namespace() -> initrd_load()]int __init initrd_load(void){ if (mount_initrd) { create_dev("/dev/ram", Root_RAM0); if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); handle_initrd(); return 1; } } sys_unlink("/initrd.image"); return 0;}initrd_load()执行以下步骤:
调用create_dev()建立设备文件节点/dev/ram,其实这也是一个ramfs文件系统。
调用rd_load_image()把/initrd.image加载到/dev/ram中。
调用handle_initrd()把把块设备文件/dev/ram挂载到/root。
其中handle_initrd()代码如下:
[kernel_init() -> prepare_namespace() -> initrd_load() -> handle_initrd()]static void __init handle_initrd(void){ ...... real_root_dev = new_encode_dev(ROOT_DEV); /* 建立/dev/root.old设备文件。*/ create_dev("/dev/root.old", Root_RAM0); /* 把/dev/root.old mount到/root目录。*/ /* mount initrd on rootfs' /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"); sys_mount(".", "/", NULL, MS_MOVE, NULL); /* chroot到/root目录,好了,现在/root目录成为当前的根目录。*/ 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; /* 建立一个线程,执行/linuxrc,这是旧的initrd默认执行的文件。*/ pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); ......}
第二部分
创建一个cpio格式的 ramfs,
# find miniroot/ | cpio -c -o > initrd.img # gzip initrd.img
这样得到的initrd.img大小是可变的,它取决于您的小型根目录miniroot/的总大小,由于首选使用cpio把根目录进行打包,因此这个initramfs又被称为cpio initrd. 在系统启动阶段,bootload除了从磁盘上机制内核镜像bzImage之外,还要加载initrd.img.gz,然后把initrd.img.gz的起始地址传递给内核。能不能把这两个文件合二为一呢?答案是肯定的,在Linux 2.6的内核中,可以把initrd.img.gz链接到内核文件(ELF格式)的一个特殊的数据段中,这个段的名字为.init.ramfs。其中全局变量__initramfs_start和__initramfs_end分别指向这个数据段的起始地址和结束地址。内核启动时会对.init.ramfs段中的数据进行解压,然后使用它作为临时的根文件系统。别看这个过程复杂,您只需要在make menuconfig中配置以下选项就可以了:General setup ---> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support (../miniroot/) Initramfs source file(s)cpio ramfs 在启动中的处理过程:cpio ramfs采用rootfs作为其载体, rootfs也是一种基于内存的文件系统格式。首选在内核启动过程,会初始化rootfs文件系统,rootfs和tmpfs都是内存中的文件系统,其类型为ramfs. 然后会把这个rootf挂载到根目录。 其代码如下:
[start_kernel() -> vfs_caches_init() -> mnt_init()] void __init mnt_init(void) { ...... init_rootfs(); init_mount_tree();}init_rootfs()注册rootfs文件系统,代码如下:
static struct file_system_type rootfs_fs_type = { .name = "rootfs", .get_sb = rootfs_get_sb, .kill_sb = kill_litter_super,};int __init init_rootfs(void){ err = register_filesystem(&rootfs_fs_type); ...... return err;}init_mount_tree会把rootfs挂载到/目录,代码如下:
static void __init init_mount_tree(void){ struct vfsmount *mnt; struct mnt_namespace *ns; mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); ...... set_fs_pwd(current->fs, ns->root, ns->root->mnt_root); set_fs_root(current->fs, ns->root, ns->root->mnt_root);}do_kern_mount()会调用前面注册的rootfs文件系统对象的rootfs_get_sb()函数,
[rootfs_get_sb() -> ramfs_fill_super() -> d_alloc_root()]struct dentry * d_alloc_root(struct inode * root_inode){ struct dentry *res = NULL; if (root_inode) { static const struct qstr name = { .name = "/", .len = 1 }; res = d_alloc(NULL, &name); if (res) { res->d_sb = root_inode->i_sb; res->d_parent = res; d_instantiate(res, root_inode); } } return res;}从上面的代码中的可以看出,这个rootfs的dentry对象的名字为"/",也就是根目录了。
cpio ramfs 省去了复杂的image解压和装载过程所以可以提高内核的启动速度。 Android采用cpio ramfs为根文件系统,
节省了进入用户空间的时间。
"arch/arm/kernel/vmlinux.lds" 358 lines
264 __security_initcall_end = .;
265 . = ALIGN(32);
266 __initramfs_start = .;
267 usr/built-in.o(.init.ramfs)
268 __initramfs_end = .;
269 . = ALIGN(4096);
"usr/initramfs_data.S"
10 ld -m elf_i386 --format binary --oformat elf32-i386 -r /
11 -T initramfs_data.scr initramfs_data.cpio.gz -o initramfs_data.o
12 ld -m elf_i386 -r -o built-in.o initramfs_data.o
13
14 initramfs_data.scr looks like this:
15 SECTIONS
16 {
17 .init.ramfs : { *(.data) }
18 }
28 .section .init.ramfs,"a"
29 .incbin "usr/initramfs_data.cpio.gz"
"init/initramfs.c" 584 lines
541 static int __init populate_rootfs(void)
542 {
543 char *err = NULL;
544
545 printk(KERN_EMERG"************************/n"); //gwj
546 err = unpack_to_rootfs(__initramfs_start,
547 __initramfs_end - __initramfs_start, 0);
584 rootfs_initcall(populate_rootfs);
the file initramfs.c only express the cpio ramfs and copy all files to rootfs filesystem, these actions implemented in kernel setting.
- 【转】kernel initrd & ramfs
- kernel initrd & ramfs
- kernel initrd & ramfs
- ramfs,rootfs,initramfs,initrd
- ramfs, rootfs, initrd and initramfs
- ramfs, rootfs, initrd and initramfs
- ramfs, rootfs, initrd and initramfs
- kernel, rootfs, ramfs
- [转]ramfs, rootfs, initrd and initramfs
- ramdisk/ramfs/tmpfs/initrd/initramfs/rootfs/cpio
- ramdisk initrd ramfs initramfs区别与联系
- linux中的rootfs/initrd/ramfs/initramfs
- kernel和initrd
- kernel和initrd-1
- Linux ramfs,rootfs,tmpfs,ramdisk,initrd and initramfs
- ramdisk 和 ramfs 文件系统 (initrd 和 initramfs )
- ramfs ramdis tmpfs rootfs initrd initramfs swap区别
- linux kernel 为什么需要initrd
- 修改ETM,用Ogre实现《天龙八部》地形与部分场景详解(附源码)
- 浅谈视频监控管理平台现状及发展趋势
- Linux 内核配置及编译简介
- 使用托管C++粘合C#和C++代码(一)
- JAVA正则表达式--Pattern和Matcher
- kernel initrd & ramfs
- 批处理教程
- 用Ogre实现《天龙八部》场景中水面(TerrainLiquid)详解
- 关于WINSOCK.H与winsock2.h中的重定义解决办法分析
- Linux内核裁减
- 使用托管C++粘合C#和C++代码(二)
- SQL Server 锁机制,多人访问系统特别的慢.
- 关于ASP.NET中Request.QueryString的乱码问题
- RAL brief intro