理想未来ってなんやねん

娘可愛い。お父さん頑張る。

tmpfs_mount関数のソースコードを読む(前半)

昨日の続き
mount処理について調べるため、tmpfs_mount関数のコードを見ていきたいと思います。

tmpfs_mount

tmpfs_mountはtmpfs_vfsops.cに記述されています。

static int
tmpfs_mount(struct mount *mp)
{
        struct tmpfs_mount *tmp;
        struct tmpfs_node *root;
        size_t pages, mem_size;
        ino_t nodes;
        int error;
        /* Size counters. */
        ino_t   nodes_max;
        size_t  size_max;

        /* Root node attributes. */
        uid_t   root_uid;
        gid_t   root_gid;
        mode_t  root_mode;

        struct vattr    va;

        if (vfs_filteropt(mp->mnt_optnew, tmpfs_opts))
                return (EINVAL);

        if (mp->mnt_flag & MNT_UPDATE) {
                /* XXX: There is no support yet to update file system
                 * settings.  Should be added. */

                return EOPNOTSUPP;
        }

        printf("WARNING: TMPFS is considered to be a highly experimental "
            "feature in FreeBSD.\n");

        vn_lock(mp->mnt_vnodecovered, LK_SHARED | LK_RETRY);
        error = VOP_GETATTR(mp->mnt_vnodecovered, &va, mp->mnt_cred);
        VOP_UNLOCK(mp->mnt_vnodecovered, 0);
        if (error)
                return (error);

        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "gid", "%d", &root_gid) != 1)
                root_gid = va.va_gid;
        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "uid", "%d", &root_uid) != 1)
                root_uid = va.va_uid;
        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "mode", "%ho", &root_mode) != 1)
                root_mode = va.va_mode;
        if (vfs_scanopt(mp->mnt_optnew, "inodes", "%d", &nodes_max) != 1)
                nodes_max = 0;
        if (vfs_scanopt(mp->mnt_optnew, "size", "%qu", &size_max) != 1)
                size_max = 0;

        /* Do not allow mounts if we do not have enough memory to preserve
         * the minimum reserved pages. */
        mem_size = cnt.v_free_count + cnt.v_inactive_count + get_swpgtotal();
        mem_size -= mem_size > cnt.v_wire_count ? cnt.v_wire_count : mem_size;
        if (mem_size < TMPFS_PAGES_RESERVED)
                return ENOSPC;

        /* Get the maximum number of memory pages this file system is
         * allowed to use, based on the maximum size the user passed in
         * the mount structure.  A value of zero is treated as if the
         * maximum available space was requested. */
        if (size_max < PAGE_SIZE || size_max >= SIZE_MAX)
                pages = SIZE_MAX;
        else
                pages = howmany(size_max, PAGE_SIZE);
        MPASS(pages > 0);

        if (nodes_max <= 3)
                nodes = 3 + pages * PAGE_SIZE / 1024;
        else
                nodes = nodes_max;
        MPASS(nodes >= 3);

        /* Allocate the tmpfs mount structure and fill it. */
        tmp = (struct tmpfs_mount *)malloc(sizeof(struct tmpfs_mount),
            M_TMPFSMNT, M_WAITOK | M_ZERO);

        mtx_init(&tmp->allnode_lock, "tmpfs allnode lock", NULL, MTX_DEF);
        tmp->tm_nodes_max = nodes;
        tmp->tm_nodes_inuse = 0;
        tmp->tm_maxfilesize = (u_int64_t)(cnt.v_page_count + get_swpgtotal()) * PAGE_SIZE;
        LIST_INIT(&tmp->tm_nodes_used);

        tmp->tm_pages_max = pages;
        tmp->tm_pages_used = 0;
        tmp->tm_ino_unr = new_unrhdr(2, INT_MAX, &tmp->allnode_lock);
        tmp->tm_dirent_pool = uma_zcreate("TMPFS dirent",
            sizeof(struct tmpfs_dirent),
            NULL, NULL, NULL, NULL,
            UMA_ALIGN_PTR, 0);
        tmp->tm_node_pool = uma_zcreate("TMPFS node",
            sizeof(struct tmpfs_node),
            tmpfs_node_ctor, tmpfs_node_dtor,
            tmpfs_node_init, tmpfs_node_fini,
            UMA_ALIGN_PTR, 0);

        /* Allocate the root node. */
        error = tmpfs_alloc_node(tmp, VDIR, root_uid,
            root_gid, root_mode & ALLPERMS, NULL, NULL,
            VNOVAL, &root);

        if (error != 0 || root == NULL) {
            uma_zdestroy(tmp->tm_node_pool);
            uma_zdestroy(tmp->tm_dirent_pool);
            delete_unrhdr(tmp->tm_ino_unr);
            free(tmp, M_TMPFSMNT);
            return error;
        }
        KASSERT(root->tn_id == 2, ("tmpfs root with invalid ino: %d", root->tn_id));
        tmp->tm_root = root;

        MNT_ILOCK(mp);
        mp->mnt_flag |= MNT_LOCAL;
        mp->mnt_kern_flag |= MNTK_MPSAFE;
        MNT_IUNLOCK(mp);

        mp->mnt_data = tmp;
        mp->mnt_stat.f_namemax = MAXNAMLEN;
        vfs_getnewfsid(mp);
        vfs_mountedfrom(mp, "tmpfs");

        return 0;
}

mount構造体

tmpfs_mount関数で始めに目につくのがmount構造体です。

static int
tmpfs_mount(struct mount *mp)
{

mount構造体はsys/mount.hにて以下のように定義されています。

/*
 * Structure per mounted filesystem.  Each mounted filesystem has an
 * array of operations and an instance record.  The filesystems are
 * put on a doubly linked list.
 *
 * Lock reference:
 *      m - mountlist_mtx
 *      i - interlock
 *
 * Unmarked fields are considered stable as long as a ref is held.
 *
 */
struct mount {
        struct mtx      mnt_mtx;                /* mount structure interlock */
        int             mnt_gen;                /* struct mount generation */
#define mnt_startzero   mnt_list
        TAILQ_ENTRY(mount) mnt_list;            /* (m) mount list */
        struct vfsops   *mnt_op;                /* operations on fs */
        struct vfsconf  *mnt_vfc;               /* configuration info */
        struct vnode    *mnt_vnodecovered;      /* vnode we mounted on */
        struct vnode    *mnt_syncer;            /* syncer vnode */
        int             mnt_ref;                /* (i) Reference count */
        struct vnodelst mnt_nvnodelist;         /* (i) list of vnodes */
        int             mnt_nvnodelistsize;     /* (i) # of vnodes */
        int             mnt_writeopcount;       /* (i) write syscalls pending */
        int             mnt_kern_flag;          /* (i) kernel only flags */
        u_int           mnt_flag;               /* (i) flags shared with user */
        u_int           mnt_xflag;              /* (i) more flags shared with user */
        u_int           mnt_noasync;            /* (i) # noasync overrides */
        struct vfsoptlist *mnt_opt;             /* current mount options */
        struct vfsoptlist *mnt_optnew;          /* new options passed to fs */
        int             mnt_maxsymlinklen;      /* max size of short symlink */
        struct statfs   mnt_stat;               /* cache of filesystem stats */
        struct ucred    *mnt_cred;              /* credentials of mounter */
        void *          mnt_data;               /* private data */
        time_t          mnt_time;               /* last time written*/
        int             mnt_iosize_max;         /* max size for clusters, etc */
        struct netexport *mnt_export;           /* export list */
        struct label    *mnt_label;             /* MAC label for the fs */
        u_int           mnt_hashseed;           /* Random seed for vfs_hash */
        int             mnt_lockref;            /* (i) Lock reference count */
        int             mnt_secondary_writes;   /* (i) # of secondary writes */
        int             mnt_secondary_accwrites;/* (i) secondary wr. starts */
        struct thread   *mnt_susp_owner;        /* (i) thread owning suspension */
#define mnt_endzero     mnt_gjprovider
        char            *mnt_gjprovider;        /* gjournal provider name */
        struct lock     mnt_explock;            /* vfs_export walkers lock */
};

それぞれ、どのように使われているのかはtmpfs_mount関数のコードを追いながら見ていきます。

vfs_filteropt関数

始めに実行されるコードはvfs_filteropt関数の呼び出しとエラー処理です。

        if (vfs_filteropt(mp->mnt_optnew, tmpfs_opts))
                return (EINVAL);
||

vfs_filteropt関数のマニュアルは下記の通りです。
[http://www.jp.freebsd.org/cgi/mroff.cgi?subdir=man&lc=1&cmd=&man=vfs_filteropt&dir=jpman-8.0.2%2Fman&sect=0:title]

一部抜粋します。
>||
〜〜〜省略〜〜〜
名称
     vfs_getopt, vfs_getopts, vfs_flagopt, vfs_scanopt, vfs_copyopt,
     vfs_filteropt -- マウントオプションとそれらの値を操作する
〜〜〜中略〜〜〜
解説
〜〜〜中略〜〜〜
vfs_filteropt() 関数は、未知のオプションが指定されなかったことを確認しま
     す。その名前が正当な名前のリスト中の名前の 1 つに一致するなら、オプション
     は、有効です。オプションは、'no' を前に付けることができ、それでも、有効で
     あると見なされます。
〜〜〜中略〜〜〜
戻り値
〜〜〜中略〜〜〜
     vfs_filteropt() 関数は、すべてのオプションが正当であるなら、0 を返しま
     す。そうでなければ、EINVAL が返されます。
〜〜〜省略〜〜〜


tmpfs_optsはtmpfs_vfsops.cファイル内に下記のように定義されています。
下記以外のオプションが指定された場合はエラーとなり、EINVALを返します。

static const char *tmpfs_opts[] = {
        "from", "size", "inodes", "uid", "gid", "mode", "export",
        NULL
};


次はMNT_UPDATEのチェックです。

        if (mp->mnt_flag & MNT_UPDATE) {
                /* XXX: There is no support yet to update file system
                 * settings.  Should be added. */

                return EOPNOTSUPP;
        }

MNT_UPDATEの意味はマニュアルのVFS_MOUNTの解説に触れられています。

MNT_UPDATE フラグが設定されていない場合には、これは、新規にマウントされた ファイルシステムです。ファイルシステムのコードは、ファイルシステムの表現 に必要な私的データの割り当てと初期化を行なうべきです (この情報を格納する ために mp->mnt_data フィールドを使用可能です)。

tmpfsはマウントされる度に初期化されるので、常に新規にマウントされる事になります。
MNT_UPDATEが設定されるのはまずいので、指定されている場合はEOPNOTSUPPを返しているということでしょう。


次に進みます。

        printf("WARNING: TMPFS is considered to be a highly experimental "
            "feature in FreeBSD.\n");

非常に実験的な機能ということでワーニングを出しているようです。


        vn_lock(mp->mnt_vnodecovered, LK_SHARED | LK_RETRY);
        error = VOP_GETATTR(mp->mnt_vnodecovered, &va, mp->mnt_cred);
        VOP_UNLOCK(mp->mnt_vnodecovered, 0);
        if (error)
                return (error);

vn_lockでロックした上で、VOP_GETATTRにて属性を取得しています。

vn_lockはLK_SHAREDとLK_RETRYが指定されていますので、共有ロックでロックされるまでリトライしています。

        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "gid", "%d", &root_gid) != 1)
                root_gid = va.va_gid;
        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "uid", "%d", &root_uid) != 1)
                root_uid = va.va_uid;
        if (mp->mnt_cred->cr_ruid != 0 ||
            vfs_scanopt(mp->mnt_optnew, "mode", "%ho", &root_mode) != 1)
                root_mode = va.va_mode;
        if (vfs_scanopt(mp->mnt_optnew, "inodes", "%d", &nodes_max) != 1)
                nodes_max = 0;
        if (vfs_scanopt(mp->mnt_optnew, "size", "%qu", &size_max) != 1)
                size_max = 0;

vfs_scanopt関数によりmnt_optnewからオプションを取得しています。
オプションが指定されていない場合は、gid、uid、modeに関してはVOP_GETATTRから取得した値をセットし、inodesとsizeは0にしています。

        /* Do not allow mounts if we do not have enough memory to preserve
         * the minimum reserved pages. */
        mem_size = cnt.v_free_count + cnt.v_inactive_count + get_swpgtotal();
        mem_size -= mem_size > cnt.v_wire_count ? cnt.v_wire_count : mem_size;
        if (mem_size < TMPFS_PAGES_RESERVED)
                return ENOSPC;

cntはvmmeter構造体のグローバル変数で、vm/vm_meter.cで宣言されています。
vmmeter構造体はsys/vmmeter.hで定義されています。

tmpfsの為の最小限の空き領域があるか確認しない場合は、エラーとしてENOSPCを返しています。

        /* Get the maximum number of memory pages this file system is
         * allowed to use, based on the maximum size the user passed in
         * the mount structure.  A value of zero is treated as if the
         * maximum available space was requested. */
        if (size_max < PAGE_SIZE || size_max >= SIZE_MAX)
                pages = SIZE_MAX;
        else
                pages = howmany(size_max, PAGE_SIZE);
        MPASS(pages > 0);

ページ数の計算を行っています。
PAGE_SIZEはi386及びamd64の場合は4KBです。
size_maxが定数PAGE_SIZEより小さいか、size_maxが定数SIZE_MAX以上の場合は、pagesをSIZE_MAXとしています。
その他の場合はsize_maxを元に、howmanyマクロでpages数を計算しています。

howmanyマクロはsys/param.hで下記のように定義されています。

#define   howmany(x, y)   (((x)+((y)-1))/(y))


次にノード数の計算を行っています。

        if (nodes_max <= 3)
                nodes = 3 + pages * PAGE_SIZE / 1024;
        else
                nodes = nodes_max;
        MPASS(nodes >= 3);


長くなってきたので今日はここまで。

tmpfs_mount関数(後半)に続く