1. 程式人生 > >根檔案系統(rootfs)梳理

根檔案系統(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();

以上程式碼中,從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;
}

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

關鍵函式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;
}

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

這裡會呼叫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);

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

程式碼路徑: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;
}

上邊說到至少實際儲存區域要準備好,這裡就稍微有點複雜,先看下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;
}

根據函式呼叫來到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)

可以看到,從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);

對於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>

這兩個函式會根據CONFIG_BLK_DEV_INITRD配置,決定是否被呼叫。對於noinitramfs,可以清楚的看到/dev目錄的建立。由於時間關係,對於initramfs、真實根檔案系統掛載和Android根檔案系統相關內容留著下次再寫。—2016.04.30 00:40

相關推薦

檔案系統rootfs梳理

引言:在linux系統中,一直對根檔案系統理解得模稜兩可,是時候徹底梳理一下了,包括根檔案系統是什麼 、如何初始化、如何應用及Android系統中的根檔案系統等問題。 首先要弄清楚根檔案系統是什麼?以下英文部分摘自 Kernel_2.3.6\Document

如何使用busybox編譯和生成最簡linux檔案系統rootfs

繼前幾天對uboot和核心編譯進行了初步瞭解之後,昨天開始研究如何製作rootfs根檔案系統。昨晚對busybox這個工具有了初步的瞭解,今天繼續深入研究,終於成功的製作出了一套完整可用的最簡linux rootfs根檔案系統。現記錄詳細步驟以備日後查閱。 一

什麼是檔案系統rootfs=Root Fils System

Linux系統中的根檔案系統,Root FileSystem,簡稱為rootfs; 關於rootfs,之前一直很迷惑,不知道所要表達的真正的含義; 即便是通過buildroot自己建立了相關的rootfs之後,還是沒能很明白的理解,到底rootfs是啥。 現在

淺談linux中的檔案系統rootfs的原理和介紹

linux中有一個讓很多初學者都不是特別清楚的概念,叫做“根檔案系統”。我接觸linux前前後後也好幾年了,但是對這個問題,至今也不是特別的清楚,至少沒法給出一個很全面很到位的解釋。於是,今天我們就來理一理這個話題。 一、先交代一下檔案系統 在開始討論根檔案

一步步製作檔案系統

                            開發環境:Ubuntu 12.04                             開發板:mini2440  256M NandFlash   64M SDRAM                              交叉編譯器:arm-

linux 核心啟動流程涉及到檔案系統的問題

Linux核心啟動及檔案系統載入過程 當u-boot開始執行bootcmd命令。就進入Linux核心啟動階段,與u-boot類似,普通Linux核心的啟動過程也能夠分為兩個階段,但針對壓縮了的核心如uImage就要包含核心自解壓過程了。本文以linux-2.6.37版原始

檔案系統linuxrc、inittab、fstab

根檔案系統 根檔案系統是除核心映像檔案外,init程式、shell程式、各種程式執行需要的的庫等不可缺少檔案的集合。 構建根檔案系統時,一般遵循FHS標準,詳見維基百科 (https://en.wikipedia.org/wiki/Filesyst

檔案製作檔案系統樹 和 Initramfs檔案系統的製作

 根檔案系統樹製作 首先要明白的是“什麼是檔案系統”,檔案系統是對一個儲存裝置上的資料和元資料進行組織的機制。 這種機制有利於使用者和作業系統的互動。 根檔案系統之所以在前面加一個”根“,說明它是載入其它檔案系統的”根“,既然是根的話,那麼如果沒有這個根,其它的檔案

使用python的hdfs包操作分散式檔案系統HDFS

===================================================================================== 寫在前邊的話:         之前做的Hadoop叢集,組合

bash學習之檔案系統

linux系統的一個特點就是將所有的內容都看作是檔案。因此至少要了解linux的檔案也是一個重要的內容。下面將介紹一下linux裡面的檔案: 1.按照檔案型別進行劃分 文字檔案: 檔案的組成主要是ASCII碼,也就是能夠直接識別成我能能夠讀懂內容是什麼的檔案(windows的tx

/proc檔案系統:/proc/<pid>/stat

0. 前言 /proc 檔案系統是一個偽檔案系統,它只存在記憶體當中,而不佔用外存空間。 它以檔案系統的方式為核心與程序提供通訊的介面。使用者和應用程式可以通過/proc得到系統的資訊,並可以改變核心的某些引數。 由於系統的資訊,如程序,是動態改變的,所以使用者或應用程式讀取/proc

FastDFS輕量級分散式檔案系統安裝

FastDFS--tracker安裝 在192.168.101.3上安裝tracker。 下載 tracker和storage使用相同的安裝包,下載地址:http://sourceforge.net/projects/FastDFS/ 或https://github

星際檔案系統IPFS

IPFS: InterPlanetary File System  是一個面向全球的、點對點的分散式檔案系統。目標是為了補充(甚至是取代)目前非常流行的HTTP協議,會將所有具有相同檔案協議的計算裝置連線在一起。 原理:基於內容的地址替代基於域名的地址。 Url,

Linux套接字與虛擬檔案系統1:初始化和建立

引言    在Unix的世界裡,萬物皆檔案,通過虛擬檔案系統VFS,程式可以用標準的Unix系統呼叫對不同的檔案系統,甚至不同介質上的檔案系統進行讀寫操作。對於網路套接字socket也是如此,除了專屬的Berkeley Sockets API,還支援一些標準的檔案IO系統呼叫如read(v)、w

BigData_A_A_01-hdfs分散式檔案系統2高可用

楔子 Hadoop 3 高可用搭建記錄 1 zookeeper叢集 zoo.cfg 檔案配置資料檔案位置等資訊 #其他使用預設 dataDir=/opt/data/zk server.1=had2:2888:3888 server.2=had3:2888:3

BigData_A_A_01-hdfs分散式檔案系統1全分散式

楔子 學習 hadoop(基於hadoop3.1.1),學習視訊可以參看大資料第二階段:hadoop體系之離線計算 1 前期準備 1.1 hosts修改 win10修改hosts許可權 虛擬機器4 臺 ,分配如下

nodeJs檔案系統fs與流stream

檔案系統(File System): 在Node中,檔案系統的互動是非常重要的,伺服器的本質就是將本地的檔案傳送給客戶端, Node通過fs模組來和檔案系統進行互動,該模組提供了一些標準的檔案訪問API類開啟、讀取、寫入檔案、以及與其互動。 要是用fs模組,首先要從核心模組中載入; 使用 co

SD卡中的FAT32檔案系統

2.1 FAT檔案系統簡介 FAT(File Allocation Table,檔案分配表)檔案系統是windows作業系統所使用的一種檔案系統,它的發展過程經歷了FAT12、FAT16、FAT32三個階段。FAT檔案系統用“簇”作為資料單元。一個“簇”由一組連續的扇區

Linux套接字與虛擬檔案系統2:操作和銷燬

   接上篇初始化與建立,本篇闡述Socket操作和銷燬兩部分的實現。 Socket操作    系統呼叫read(v)、write(v)是使用者空間讀寫socket的一種方法,為了弄清楚它們是怎麼通過VFS將請求轉發到特定協議的實現,下面以read為例(write同理),並假定檔案描述

五分鐘瞭解什麼是Google檔案系統GFS

簡介 Google檔案系統是構建在廉價伺服器之上的大型分散式檔案系統。它將伺服器故障視為正常現象,通過軟體的方式自動容錯,在保證系統可靠性和可用性的同時,大大降低系統的成本 系統架構 GFS系統的節點可以分為三種角色 GFS Master(主控伺服器) GFS