根文件系统(rootfs)梳理

引言:在linux系统中,一直对根文件系统理解得模棱两可,是时候彻底梳理一下了,包括根文件系统是什么 、如何初始化、如何应用及Android系统中的根文件系统等问题。

首先要弄清楚根文件系统是什么?以下英文部分摘自 
Kernel_2.3.6\Documentation\filesystemsramfs-rootfs-initramfs.txt

What is rootfs? 
Rootfs is a special instance of ramfs (or tmpfs, if that’s enabled), which is always present in 2.6 systems. You can’t unmount rootfs for approximately the same reason you can’t kill the init process; rather than having special code to check for and handle an empty list, it’s smaller and simpler for the kernel to just make sure certain lists can’t become empty. 
Most systems just mount another filesystem over rootfs and ignore it. The amount of space an empty instance of ramfs takes up is tiny.

What is ramfs? 
Ramfs is a very simple filesystem that exports Linux’s disk caching mechanisms (the page cache and dentry cache) as a dynamically resizable RAM-based filesystem.

根据linux文档可以看出,rootfs是ramfs或tmpfs的一种实例,它不能被umount,对于内核而言,rootfs体积小且简单,主要用于确保某些lists不能为空。大部分系统将在rootfs挂载另一种文件系统,ramfs一个空实例的占用空间的总量花销很少。 
其中,lists 不好翻译,我的理解为目录。这里提到了ramfs和tmpfs,ramfs是一种非常简单的RAM系统,它基于linux系统硬盘缓冲机制,可以动态改变大小。

这里可以看出根文件系统的一些简单特性,首先它也是一种文件系统,即提供读、写、挂载等常规操作。其作用主要管理一些目录(目录也是一种文件),而Linux系统正常运行严重依赖这些目录。如内核0.11对rootfs至少需要/etc,/bin,/dev,/home等。

1.根文件系统初始化

对rootfs有了基本的了解后,需要进一步知道rootfs运行起来的流程。从内核init进程出发开始探索:

asmlinkage void __init start_kernel(void)
    vfs_caches_init_early();
        dcache_init_early();
        inode_init_early();
    --> vfs_caches_init(num_physpages);
        dcache_init();
        inode_init();
        files_init(mempages);
        --> mnt_init();
            --> init_rootfs();
            --> init_mount_tree();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以上代码中,从start_kernel开始,初始化虚拟文件系统,包括dcache、inode初始,创建内核对象fs,然后开始初始化rootfs。

Inode.c (fs\ramfs):int __init init_rootfs(void)
int __init init_rootfs(void)
{
    int err;
    err = bdi_init(&ramfs_backing_dev_info);
    err = register_filesystem(&rootfs_fs_type);
    return err;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

init_rootfs()中,注册rootfs文件类型,主要作用是把rootfs加入内核维护的一个文件系统类型的链表中,同时供其它模块进行系统调用。接着再看函数init_mount_tree()

static void __init init_mount_tree(void)
{
    struct vfsmount *mnt;
    struct mnt_namespace *ns;
    struct path root;

    --> mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
    if (IS_ERR(mnt))
        panic("Can't create rootfs");
    ns = kmalloc(sizeof(*ns), GFP_KERNEL);
    if (!ns)
        panic("Can't allocate initial namespace");
    atomic_set(&ns->count, 1);
    INIT_LIST_HEAD(&ns->list);
    init_waitqueue_head(&ns->poll);
    ns->event = 0;
    list_add(&mnt->mnt_list, &ns->list);
    ns->root = mnt;
    mnt->mnt_ns = ns;

    init_task.nsproxy->mnt_ns = ns;
    get_mnt_ns(ns);

    root.mnt = ns->root;
    root.dentry = ns->root->mnt_root;

    --> set_fs_pwd(current->fs, &root);
    --> set_fs_root(current->fs, &root);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

关键函数do_kern_mount

struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
    struct file_system_type *type = get_fs_type(fstype);
    struct vfsmount *mnt;
    if (!type)
        return ERR_PTR(-ENODEV);
    --> mnt = vfs_kern_mount(type, flags, name, data);
    if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
        !mnt->mnt_sb->s_subtype)
        mnt = fs_set_subtype(mnt, fstype);
    put_filesystem(type);
    return mnt;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

do_kern_mount这个过程,调用vfs_kern_mount得到一个vfsmount结构,其中type为rootfs_fs_type

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
    struct vfsmount *mnt;
    --> error = type->get_sb(type, flags, name, data, mnt);

    error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);
    if (error)
        goto out_sb;

    mnt->mnt_mountpoint = mnt->mnt_root;
    mnt->mnt_parent = mnt;
    up_write(&mnt->mnt_sb->s_umount);
    free_secdata(secdata);
    return mnt;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里会调用Inode.c (fs\ramfs):static int rootfs_get_sb(struct file_system_type *fs_type),进而调用到get_sb_nodev

int get_sb_nodev(struct file_system_type *fs_type,
    int flags, void *data,
    int (*fill_super)(struct super_block *, void *, int),
    struct vfsmount *mnt)
{
    int error;
    --> struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
    s->s_flags = flags;
    --> error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
    if (error) {
        up_write(&s->s_umount);
        deactivate_super(s);
        return error;
    }
    s->s_flags |= MS_ACTIVE;
    return simple_set_mnt(mnt, s);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在get_sb_nodev中,用sget获得一个超级块s,并初始化超级块s的flags,然后调用fill_super

static int fill_super(struct super_block *sb, void *data, int silent)
{
    static struct tree_descr files[] = {{""}};
    --> return simple_fill_super(sb, SECURITYFS_MAGIC, files);
}
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

代码路径:Inode.c (security):static int fill_super() 
在对超级块填充过程中,首先会分配一个inode对像,然后分配根目录 /, 这就是我们看到的根目录符号。后边的目录项分配会以根目录开始,呈树状延伸。再次回到这两个函数: 
set_fs_pwd(current->fs, &root); 
set_fs_root(current->fs, &root); 
作用是把init进程的当前目录和root目录设为文件系统的根目录,即根文件系统。至此rootfs流程完毕,但此时的rootfs是ramfs,这些数据掉电丢失,在根文件系统下要永久保存一些文件,就需要把根文件系统安装到实际硬盘或flash中。

2.虚拟根文件系统与真实根文件系统

简单的说,一个文件系统对应着一块存储区域,虚拟根文件系统对应的存储区域为内存ram,而直根文件系统对应的存储区域为 硬盘或flash,根据前面介绍,在系统初始化期间,先挂载的是虚拟根文件系统。那么这里有两个疑问,为什么不直接挂载真实根文件系统?真实根文件系统又是什么时机挂载上去的?

对根文件系统和整个内核的启动,这两个问题都显得相当重要。对于第一个问题,先举个例子,假如直接将硬盘的一个ext4格式的分区(/dev/sda1)挂载为根文件系统,可以这样示意:mount -t ext4 /dev/sda1 / 
但是在系统初始化时,/dev这个目录从何而来?sda1这个分区是否就绪了?这样就从反面说明了内核初始化时只能先挂载虚拟根文件系统,然后在一个合适的时机挂载真实的根文件系统。这个时机至少是当/dev/sda1准备就绪。

继续查看kernel_init代码:

static int __init kernel_init(void * unused)
{
    --> do_basic_setup();

    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work
     */

    if (!ramdisk_execute_command)
        --> ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        --> prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     */

    init_post();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

上边说到至少实际存储区域要准备好,这里就稍微有点复杂,先看下do_basic_setup这个函数:

static void __init do_basic_setup(void)
{
    rcu_init_sched(); /* needed by module_init stage. */
    init_workqueues();
    usermodehelper_init();
    --> river_init();
    init_irq_proc();
    do_initcalls();
}
void __init driver_init(void)
{
    /* These are the core pieces */
    --> devices_init();
    buses_init();
    classes_init();
}
int __init devices_init(void)
{
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    if (!devices_kset)
        return -ENOMEM;
    --> dev_kobj = kobject_create_and_add("dev", NULL);
    if (!dev_kobj)
        goto dev_kobj_err;
    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    if (!sysfs_dev_block_kobj)
        goto block_kobj_err;
    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
    if (!sysfs_dev_char_kobj)
        goto char_kobj_err;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

根据函数调用来到devices_init中,可以看到devices、dev、block、char等内核对象的创建,但这些对象是创建在/sys目录下,而根目录下的/dev仍没出现。关键在于这个函数do_initcalls,

static void __init do_initcalls(void)
{
    initcall_t *call;

    for (call = __early_initcall_end; call < __initcall_end; call++)
        --> do_one_initcall(*call);
}

define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看到,从t__early_initcall_end到__initcall_end段的函数会被调用。经过一些处理rootfs_initcall(populate_rootfs)这类函数会被调用。在代码init路径下有两个文件initramfs.c和noinitramfs.c,对于noinitramfs.c有:

static int __init default_rootfs(void)
{
    int err;

    --> err = sys_mkdir("/dev", 0755);
    if (err < 0)
        goto out;

    err = sys_mknod((const char __user *) "/dev/console",
            S_IFCHR | S_IRUSR | S_IWUSR,
            new_encode_dev(MKDEV(5, 1)));
    if (err < 0)
        goto out;

    err = sys_mkdir("/root", 0700);
    if (err < 0)
        goto out;

    return 0;
}
rootfs_initcall(default_rootfs);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

对于initramfs.c有:

static int __init populate_rootfs(void)
{
    char *err = unpack_to_rootfs(__initramfs_start,
             __initramfs_end - __initramfs_start, 0);
    if (err)
        panic(err);
    if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
        int fd;
        printk(KERN_INFO "checking if image is initramfs...");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start, 1);
        if (!err) {
            printk(" it is\n");
            unpack_to_rootfs((char *)initrd_start,
                initrd_end - initrd_start, 0);
            free_initrd();
            return 0;
        }
        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();
        }
#else
        printk(KERN_INFO "Unpacking initramfs...");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start, 0);
        if (err)
            panic(err);
        printk(" done\n");
        free_initrd();
#endif
    }
    return 0;
}
rootfs_initcall(populate_rootfs);</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

这两个函数会根据CONFIG_BLK_DEV_INITRD配置,决定是否被调用。对于noinitramfs,可以清楚的看到/dev目录的建立。由于时间关系,对于initramfs、真实根文件系统挂载和Android根文件系统相关内容留着下次再写。—2016.04.30 00:40

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值