1. 程式人生 > >rootfs註冊掛載過程分析

rootfs註冊掛載過程分析

最終 entry user oid pts write exe _id har

參考:Linux Filesystem: 解析 Linux 中的 VFS 文件系統機制

主要代碼,

init_rootfs();
init_mount_tree();

1.init_rootfs()解析

init_rootfs()
    -->bdi_init(&ramfs_backing_dev_info);
    /*
    註冊過程實際上將表示各實際文件系統的 struct file_system_type 數據結構的實例化,
    將rootfs_fs_type加入到static struct file_system_type *file_systems為首的單鏈表。
    
*/ -->register_filesystem(&rootfs_fs_type);

struct backing_dev_info結構是顯示設備信息的描述符,定義如下:

static struct backing_dev_info ramfs_backing_dev_info = {
    .ra_pages    = 0,    /* No readahead */
    .capabilities    = BDI_CAP_NO_ACCT_AND_WRITEBACK |
              BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY |
              BDI_CAP_READ_MAP 
| BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP, };

2.init_mount_tree()解析

init_mount_tree() 這個函數為 VFS 建立了根目錄 "/"

init_mount_tree()
    -->struct vfsmount *mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
        /*
        1.根據“rootfs”在static struct file_system_type *file_systems鏈表中
        找到代表文件系統的file_system_type結構體
        
*/ -->struct file_system_type *type = get_fs_type("rootfs"); -->struct vfsmount *mnt = vfs_kern_mount(type, 0, "rootfs", NULL); /*2.從mnt_cache緩存分配vfsmount結構體並初始化*/ -->struct vfsmount *mnt = alloc_vfsmnt(name); -->type->get_sb(type, 0, "rootfs", NULL, mnt); 即rootfs_get_sb(type, 0, "rootfs", NULL, mnt); -->get_sb_nodev(type, MS_NOUSER, NULL, ramfs_fill_super,mnt); -->struct super_block *s = sget(type, NULL, set_anon_super, NULL); /*2.分配並初始化super_block結構體*/ -->struct super_block *s = alloc_super(type); -->set(s, NULL); 即set_anon_super(s,NULL) /*設置super_block結構體的s_dev成員*/ -->s->s_dev = MKDEV(0, dev & MINORMASK); -->s->s_type = type; -->strlcpy(s->s_id, type->name, sizeof(s->s_id)); /*系統中所有的super_block結構體都連接到super_blocks為首的list_head鏈表*/ -->list_add_tail(&s->s_list, &super_blocks); /*屬於file_system_type指定文件系統的super_block結構體都連接到type->fs_supers鏈表*/ -->list_add(&s->s_instances, &type->fs_supers); -->s->s_flags = MS_NOUSER; -->fill_super(s, NULL, 0) 即ramfs_fill_super(s, NULL, 0) -->sb->s_op = &ramfs_ops; /*3.分配inode結構體並初始化*/ -->struct inode *inode = ramfs_get_inode(sb, S_IFDIR | fsi->mount_opts.mode, 0); -->struct inode * inode = new_inode(sb); -->設置inode成員 /*4.分配dentry並初始化,“/”*/ -->struct dentry *root = d_alloc_root(inode); -->static const struct qstr name = { .name = "/", .len = 1 } -->struct dentry *res = d_alloc(NULL, &name); -->res->d_sb = inode->i_sb; -->res->d_parent = res; -->d_instantiate(res, inode); -->list_add(&res->d_alias, &inode->i_dentry); -->res->d_inode = inode; -->s->s_flags |= MS_ACTIVE; -->simple_set_mnt(mnt, s); -->mnt->mnt_sb = s; -->mnt->mnt_root = dget(sb->s_root); mnt->mnt_mountpoint = mnt->mnt_root; mnt->mnt_parent = mnt; /* 為系統最開始的進程(即 init_task 進程)準備它的進程數據塊中的namespace 域, 主要目的是將 do_kern_mount() 函數中建立的 mnt 和 dentry 信息記錄在了 init_task 進程的進程數據塊中, 這樣所有以後從 init_task 進程 fork 出來的進程也都先天地繼承了這一信息 */ -->struct mnt_namespace *ns = kmalloc(sizeof(*ns), GFP_KERNEL); -->list_add(&mnt->mnt_list, &ns->list); -->ns->root = mnt; -->mnt->mnt_ns = ns; -->struct path root.mnt = ns->root; struct path root.dentry = ns->root->mnt_root; -->set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root);

3.在根目錄下增加目錄

Linux 下用系統調用 sys_mkdir 來在 VFS 目錄樹中增加新的節點。為配合路徑搜索,引入了下面一個數據結構:

struct path {
    struct vfsmount *mnt;
    struct dentry *dentry;
};
/*
這個數據結構在路徑搜索的過程中用來記錄相關信息,起著類似"路標"的作用
其中前path.dentry記錄的是要建目錄的父目錄的信息
last,flags,last_type三項記錄的是所查找路徑的最後一個節點(即待建目錄或文件)的信息。
*/
struct nameidata {
    struct path    path;
    struct qstr    last;
    unsigned int    flags;
    int        last_type;
    unsigned    depth;
    char *saved_names[MAX_NESTED_LINKS + 1];

    /* Intent data */
    union {
        struct open_intent open;
    } intent;
};

4.在 VFS 樹中掛載文件系統

這一過程可簡單描述為:將某一設備(dev_name)上某一文件系統(file_system_type)安裝到VFS目錄樹上的某一安裝點(dir_name)。它要解決的問題是:將對 VFS 目錄樹中某一目錄的操作轉化為具體安裝到其上的實際文件系統的對應操作。

比如說,如果將 hda2 上的根文件系統(假設文件系統類型為 ext2)安裝到 "/dev" 目錄上(此時,"/dev" 目錄就成為了安裝點),那麽安裝成功之後應達到這樣的目的,即:對 VFS 文件系統的 "/dev" 目錄執行 "ls" 指令,該條指令應能列出 hda2 上 ext2 文件系統的根目錄下所有的目錄和文件。

記住:對目錄或文件的操作將最終由目錄或文件所對應的 inode 結構中的 i_op 和 i_fop 所指向的函數表中對應的函數來執行。所以,不管最終解決方案如何,都可以設想必然要通過將對 "/dev" 目錄所對應的 inode 中 i_op 和 i_fop 的調用轉換到hda2 上根文件系統 ext2 中根目錄所對應的 inode 中 i_op 和 i_fop 的操作。

初始過程由 sys_mount() 系統調用函數發起,該函數原型聲明如下:
long sys_mount(char * dev_name, char * dir_name, char * type,unsigned long flags, void * data);
參數 char *type 為標識將要安裝的文件系統類型字符串,對於 ext2 文件系統而言,就是"ext2"。

為了更好地理解這一過程,用一個具體的例子來說明:我們準備將來自主硬盤第 2 分區(hda2)上的 ext2 文件系統安裝到前面創建的 "/dev" 目錄中。那麽對於 sys_mount() 函數的調用便具體為:

sys_mount("hda2","/dev ","ext2",…)
    /*
    將來自用戶內存空間(user space)的參數拷貝到內核空間後,
    便調用 do_mount() 函數開始真正的安裝文件系統的工作
    */
    -->do_mount()
        -->/*調用 path_lookup() 函數來得到安裝點的相關信息,
        如同創建目錄過程中敘述的那樣,該安裝點的信息最終記錄在 struct nameidata 類型的一個變量當中,
        為敘述方便,記該變量為nd
        */
        /*調用 do_add_mount() 函數來向 VFS 樹中安裝點 "/dev " 安裝一個實際的文件系統。*/
        -->do_add_mount()
            /*建立一新的安裝區域塊*/
            -->do_kern_mount()
            -->graft_tree()
                -->將 do_kern_mount() 函數返回的一 struct vfsmount 類型的變量加入到安裝系統鏈表中
                -->將新分配的 struct vfsmount 類型的變量加入到一個hash表中

rootfs註冊掛載過程分析