linux文件系统初始化过程(4)---加载initrd(中)

一、目的

    上文详细介绍了CPIO格式的initrd文件,本文从源代码角度分析加载并解析initrd文件的过程。

    initrd文件和linux内核一般存储在磁盘空间中,在系统启动阶段由bootload负责把磁盘上的内核和initrd加载到指定的内存空间中;然后,再由内核读取和解析initrd文件,在VFS(目前只有rootfs的根目录)中新建目录、常规文件、符号链接文件以及特殊文件;这样VFS就从根目录"/"成长为一棵枝繁叶茂的大树了。

 

二、函数调用过程

 

    initrd详细的加载过程在init/initramfs.c中实现的,为了更好的理解加载过程,我们给出了关键函数的调用关系图1。这里需要注意下,由于使用roofs_initcall()宏在initcallroofs段中注册了populate_rootfs()函数,因此在执行do_initcalls()函数时会隐示调用populate_rootfs()。

                                图1

三、initcall简介

 

    linux在代码段中定义了一个特殊的段initcall,该段中存放的都是函数指针;linux初始化阶段调用do_initcalls()依次执行该段的函数。关于该段的详细信息可以参见vmlinux.lds.S链接脚本。

    用户可以调用以下一组宏在initcall段中注册函数指针;initcall段分为initcall0-initcall7这8个等级,initcall0段的优先级最高,initcall7段的优先级最低,优先级高的段最先被执行;initcallrootfs段优先级介于5和6之间。

#define __define_initcall(fn, id) \
179     static initcall_t __initcall_##fn##id __used \
180     __attribute__((__section__(".initcall" #id ".init"))) = fn

 

187 #define early_initcall(fn)          __define_initcall(fn, early)

196 #define pure_initcall(fn)           __define_initcall(fn, 0)

198 #define core_initcall(fn)           __define_initcall(fn, 1)
199 #define core_initcall_sync(fn)      __define_initcall(fn, 1s)
200 #define postcore_initcall(fn)       __define_initcall(fn, 2)
201 #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
202 #define arch_initcall(fn)           __define_initcall(fn, 3)
203 #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
204 #define subsys_initcall(fn)         __define_initcall(fn, 4)
205 #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
206 #define fs_initcall(fn)             __define_initcall(fn, 5)
207 #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
208 #define rootfs_initcall(fn)         __define_initcall(fn, rootfs)
209 #define device_initcall(fn)         __define_initcall(fn, 6)
210 #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
211 #define late_initcall(fn)           __define_initcall(fn, 7)
212 #define late_initcall_sync(fn)      __define_initcall(fn, 7s)

  用户使用不同优先级的initcall宏可以很方便的在linux代码中注册函数指针;将这些函数指针存储在相应的initcall段中;最终,由do_initcalls()按照优先级依次执行段中的函数,具体的代码实现如下:

715 static initcall_t *initcall_levels[] __initdata = {
716     __initcall0_start,
717     __initcall1_start,
718     __initcall2_start,
719     __initcall3_start,
720     __initcall4_start,
721     __initcall5_start,
722     __initcall6_start,
723     __initcall7_start,
724     __initcall_end,
725 };

678 int __init_or_module do_one_initcall(initcall_t fn)
679 {
681     int ret;
686     ret = fn();
    }

739 static void __init do_initcall_level(int level)
740 {
742     initcall_t *fn;
            ...
751     for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
752         do_one_initcall(*fn);
753 }
754
755 static void __init do_initcalls(void)
756 {
757     int level;
758
759     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
760         do_initcall_level(level);
761 }

回到加载initrd这个话题中,在init/initram.c的最后使用rootfs_initcall宏注册了populate_rootfs()函数;基于以上分析,我们知道这里就是加载initrd文件的入口,下面就开始分析该函数的功能。

627 rootfs_initcall(populate_rootfs);

四、加载initrd文件

 

    系统启动阶段,bootload将initrd加载到内存起始地址为initrd_start,结束地址为initrd_end的内存中。

    populate_rootfs()调用unpack_to_rootfs()从内存中读取并解析initrd文件;根据CPIO的格式我们知道initrd文件是由很多个段组成,且段中又是由文件头、文件名和文件体组成,因此该解析程序可以使用了状态机原理处理initrd文件。

    解析程序定义了以下8种状态:Start(初始状态)、Collect(获取符号链接文件信息状态)、GotHeader(获取文件头信息状态)、SkipIt(跳过该段状态)、GotName(获取文件名并新建文件状态)、CopyFile(写文件状态)、GotSymlink(新建符号链接文件状态)、Reset(终止状态)。

376 static __initdata int (*actions[])(void) = {
377     [Start]     = do_start,
378     [Collect]   = do_collect,
379     [GotHeader] = do_header,
380     [SkipIt]    = do_skip,
381     [GotName]   = do_name,
382     [CopyFile]  = do_copy,
383     [GotSymlink]    = do_symlink,
384     [Reset]     = do_reset,
385 };

 

    为了直观理解initrd文件的解析过程,下面给出状态机跳转图2。

    从图中可以看出将文件分为符号链接和非符号链接两种情况处理,这是因为符号链接文件是一种特殊的文件,只有第一个符号链接文件的inode存储的是真实数据,而其他符号链接文件inode中存储的是第一个符号链接文件的路径名,因此需要把第一个符号链接文件的路径名缓存起来,缓存的数据结构是hash表,所以在处理符号链接文件时多了一些hash表的操作,因此分为了符号链接文件和非符号链接文件这两种情况来处理。

    initrd文件的详细解析过程如下:

    1、S0:初始状态,初始化一些全局变量;

    2、S1:获取符号链接文件的文件头和文件体;

    3、S2:根据CPIO格式的定义,获取文件头信息;

    4、S3:跳过当前CPIO格式的段,继续处理下一个段;

    5、S4:获取文件名,并在VFS中新建文件;

    6、S5:将文件内容写入到新建文件中;

    7、S6:新建符号链接文件;

    8、S7:处理完当前CPIO格式的段,继续一个段的处理。

 

    从图中还可以看出,由于目录文件和特殊文件没有文件内容,因此跳过了S5状态,直接进入S3状态。

                                                                     图2

五、总结

 

    通过以上分析,程序就可以成功解析initrd文件,并使用sys_dir()、sys_open()、sys_mknod()、sys_symlink()等系统调用新建目录、常规文件、特殊文件和符号链接文件了。此时,VFS从只有根目录"/"成长为了一棵内容丰富的大树。

 

时间: 2024-09-09 13:51:05

linux文件系统初始化过程(4)---加载initrd(中)的相关文章

linux文件系统初始化过程(5)---加载initrd(下)

一.目的     linux把文件分为常规文件.目录文件.软链接文件.硬链接文件.特殊文件(设备文件.管道文件.socket文件等)几种类型,分别对应不同的新建函数sys_open().sys_mkdir().sys_symlink().sys_link().sys_mknod().     系统初始化阶段成功加载initrd后,调用这些接口函数创建各种文件,因此这些函数在linux文件系统初始化过程中起到了重要作用,本文将详细描述这些接口函数的实现过程.     这些接口函数主要在fs/nam

linux文件系统初始化过程(3)---加载initrd(上)

一.目的       本文主要讲述linux3.10文件系统初始化过程的第二阶段:加载initrd.     initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可执行程序和驱动程序.在linux初始化的初级阶段,它提供了一个基本的运行环境.当成功加载磁盘文件系统后,系统将切换到磁盘文件系统并卸载initrd.     如果是嵌入式设备,那么最终的文件系统就是initrd.     二.cpio文件格式       initrd常用的的文件格式是cpio,cpio

linux文件系统初始化过程(1)---概述

术语表: struct task:进程 struct mnt_namespace:命名空间 struct mount:挂载点 struct vfsmount:挂载项 struct file:文件 struct super_block:超级块 struct dentry:目录 struct inode:索引节点   一.目的     linux文件系统主要分为三个部分:文件系统调用:虚拟文件系统(VFS):挂载到VFS的实际文件系统.     其中,VFS是核心,linux文件系统的本质就是在内存

linux文件系统初始化过程(6)---执行init程序

一.目的     内核加载完initrd文件后,为挂载磁盘文件系统做好了必要的准备工作,包括挂载了sysfs.proc文件系统,加载了磁盘驱动程序驱动程序等.接下来,内核跳转到用户空间的init程序,由init完成创建磁盘设备文件.加载磁盘文件系统.从rootfs切换到磁盘根文件系统等工作.     由于在不同的linux发行版中,init的实现方式差异很大,不能将所有的发行版都分析一遍,因此本文选取ubuntu12.04发行版来描述如何从rootfs切换到磁盘根文件系统.   二.创建磁盘设备

linux文件系统初始化过程(2)---挂载rootfs文件系统

一.目的     本文主要讲述linux3.10文件系统初始化过程的第一阶段:挂载rootfs文件系统.     rootfs是基于内存的文件系统,所有操作都在内存中完成:也没有实际的存储设备,所以不需要设备驱动程序的参与.基于以上原因,linux在启动阶段使用rootfs文件系统,当磁盘驱动程序和磁盘文件系统成功加载后,linux系统会将系统根目录从rootfs切换到磁盘文件系统.   二.主要函数调用过程     图1描述了挂载rootfs的函数调用关系(图中红色部分),便于后面的分析.  

《Linux设备驱动开发详解 A》一一3.4 Linux内核的编译及加载

3.4 Linux内核的编译及加载 3.4.1 Linux内核的编译 Linux驱动开发者需要牢固地掌握Linux内核的编译方法以为嵌入式系统构建可运行的Linux操作系统映像.在编译内核时,需要配置内核,可以使用下面命令中的一个: make conf?ig(基于文本的最为传统的配置界面,不推荐使用) make menuconf?ig(基于文本菜单的配置界面) make xconf?ig(要求QT被安装) make gconf?ig(要求GTK+被安装) 在配置Linux内核所使用的make c

Linux驱动的两种加载方式过程分析

一.概念简述 在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载. 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用.静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低.若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间. 动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行

linux 3.10 的模块加载机制

问题描述 linux 3.10 的模块加载机制 跪求有关linux 3.10的模块自动加载有关的见解,资料,连接等,中英文都可---以 灰常感谢各位大神咯!!! 解决方案 http://www.cnblogs.com/image-eye/archive/2011/08/19/2145858.html

linux下整合Apache+subversion加载mod_dav_svn.so报错

问题描述 linux下整合Apache+subversion加载mod_dav_svn.so报错 报错信息: httpd: Syntax error on line 219 of /etc/httpd/conf/httpd.conf: Cannot load /etc/httpd/modules/mod_dav_svn.so into server: /usr/local/subversion/lib/libsvn_subr-1.so.0: undefined symbol: apr_hash_