理想未来ってなんやねん

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

FreeBSDのファイルシステムのソースコードを読む 其の4

昨日の続き
vfsops構造体から続けて調べていきます。

vfsops構造体

vfsops構造体はsys/mount.hで定義されています。

struct vfsops {
        vfs_mount_t             *vfs_mount;
        vfs_cmount_t            *vfs_cmount;
        vfs_unmount_t           *vfs_unmount;
        vfs_root_t              *vfs_root;
        vfs_quotactl_t          *vfs_quotactl;
        vfs_statfs_t            *vfs_statfs;
        vfs_sync_t              *vfs_sync;
        vfs_vget_t              *vfs_vget;
        vfs_fhtovp_t            *vfs_fhtovp;
        vfs_checkexp_t          *vfs_checkexp;
        vfs_init_t              *vfs_init;
        vfs_uninit_t            *vfs_uninit;
        vfs_extattrctl_t        *vfs_extattrctl;
        vfs_sysctl_t            *vfs_sysctl;
        vfs_susp_clean_t        *vfs_susp_clean;
};

上記は構造体の定義ですがtmpfsのvfsops構造体変数は次の通り初期化されています。

struct vfsops tmpfs_vfsops = {
        .vfs_mount =                    tmpfs_mount,
        .vfs_unmount =                  tmpfs_unmount,
        .vfs_root =                     tmpfs_root,
        .vfs_statfs =                   tmpfs_statfs,
        .vfs_fhtovp =                   tmpfs_fhtovp,
};

構造体変数にセットしている関数が使われることは分かりますが、セットしていないものについてはどうなるのか気になります。
VFS_SETのマニュアルをみると、以下のとおりに記載されていました。

  • 疑似コード
     /*
      * 使用するものを記述し、残りは vfs_std を使用します。
      */
     static struct vfsops myfs_vfsops = {
             myfs_mount,
             vfs_stdstart,
             myfs_unmount,
             myfs_root,
             vfs_stdquotactl,
             myfs_statfs,
             vfs_stdsync,
             vfs_stdvget,
             vfs_stdfhtovp,
             vfs_stdcheckexp,
             vfs_stdvptofh,
             vfs_stdinit,
             vfs_stduninit,
             vfs_stdextattrctl,
     };

     VFS_SET(myfs_vfsops, skelfs, 0);

vfs_stdが使用されると書かれています。

vfs_std

vfs_stdでgrepすると、kern/vfs_default.cにvfs_stdで始まる関数が定義されているのが確認できました。

kern/vfs_default.cのコードを一部抜粋します。

/*
 * vfs default ops
 * used to fill the vfs function table to get reasonable default return values.
 */
int
vfs_stdroot (mp, flags, vpp)
        struct mount *mp;
        int flags;
        struct vnode **vpp;
{

        return (EOPNOTSUPP);
}

int
vfs_stdstatfs (mp, sbp)
        struct mount *mp;
        struct statfs *sbp;
{

        return (EOPNOTSUPP);
}
〜〜〜以下省略〜〜〜


また、kern/vfs_init.cのvfs_register関数でvfsops構造体変数の各メンバがNULLの場合に、vfs_default.cで定義されている関数をセットしているようです。
該当のコードを下記に抜粋します。

/* Register a new filesystem type in the global table */
static int
vfs_register(struct vfsconf *vfc)
{
〜〜〜中略〜〜〜
        /*
         * Initialise unused ``struct vfsops'' fields, to use
         * the vfs_std*() functions.  Note, we need the mount
         * and unmount operations, at the least.  The check
         * for vfsops available is just a debugging aid.
         */
        KASSERT(vfc->vfc_vfsops != NULL,
            ("Filesystem %s has no vfsops", vfc->vfc_name));
        /*
         * Check the mount and unmount operations.
         */
        vfsops = vfc->vfc_vfsops;
        KASSERT(vfsops->vfs_mount != NULL,
            ("Filesystem %s has no mount op", vfc->vfc_name));
        KASSERT(vfsops->vfs_unmount != NULL,
            ("Filesystem %s has no unmount op", vfc->vfc_name));

        if (vfsops->vfs_root == NULL)
                /* return file system's root vnode */
                vfsops->vfs_root =      vfs_stdroot;
        if (vfsops->vfs_quotactl == NULL)
                /* quota control */
                vfsops->vfs_quotactl =  vfs_stdquotactl;
        if (vfsops->vfs_statfs == NULL)
                /* return file system's status */
                vfsops->vfs_statfs =    vfs_stdstatfs;
        if (vfsops->vfs_sync == NULL)
                /*
                 * flush unwritten data (nosync)
                 * file systems can use vfs_stdsync
                 * explicitly by setting it in the
                 * vfsop vector.
                 */
                vfsops->vfs_sync =      vfs_stdnosync;
        if (vfsops->vfs_vget == NULL)
                /* convert an inode number to a vnode */
                vfsops->vfs_vget =      vfs_stdvget;
        if (vfsops->vfs_fhtovp == NULL)
                /* turn an NFS file handle into a vnode */
                vfsops->vfs_fhtovp =    vfs_stdfhtovp;
        if (vfsops->vfs_checkexp == NULL)
                /* check if file system is exported */
                vfsops->vfs_checkexp =  vfs_stdcheckexp;
        if (vfsops->vfs_init == NULL)
                /* file system specific initialisation */
                vfsops->vfs_init =      vfs_stdinit;
        if (vfsops->vfs_uninit == NULL)
                /* file system specific uninitialisation */
                vfsops->vfs_uninit =    vfs_stduninit;
        if (vfsops->vfs_extattrctl == NULL)
                /* extended attribute control */
                vfsops->vfs_extattrctl = vfs_stdextattrctl;
        if (vfsops->vfs_sysctl == NULL)
                vfsops->vfs_sysctl = vfs_stdsysctl;
        
        /*
         * Call init function for this VFS...
         */
        (*(vfc->vfc_vfsops->vfs_init))(vfc);

        return 0;
}


vfs_mountとvfs_unmountについてはNULLの場合はKASSERTを呼び出して停止しています。
必ずセットしなければならないようですが、当然といえば当然かもしれません。

vfs_register関数

ここで見つけたvfs_register関数ですが、マニュアルに非常にヒントになることが書いてありました。
http://www.jp.freebsd.org/cgi/mroff.cgi?sect=9&subdir=man&lc=1&cmd=&dir=jpman-7.1.2/man&man=vfs_register

VFSCONF(9)             FreeBSD カーネル開発者マニュアル             VFSCONF(9)

名称
     vfsconf -- vfs 設定情報

書式
     #include <sys/param.h>
     #include <sys/mount.h>

     int
     vfs_register(struct vfsconf *vfc);

     int
     vfs_unregister(struct vfsconf *vfc);

     int
     vfs_modevent(module_t mod, int type, void *data);

解説
     カーネルに知られている各ファイルシステムタイプには、そのファイルシステム
     タイプの新しいマウントを作成するために必要とされる情報を含む vfsconf 構造
     体があります。

     struct vfsconf {
             struct  vfsops *vfc_vfsops;     /* ファイルシステム操作ベクタ */
             char    vfc_name[MFSNAMELEN];   /* ファイルシステムタイプ名 */
             int     vfc_typenum;            /* 歴史的なファイルシステムタイプ
                                                番号 */
             int     vfc_refcount;           /* このタイプのマウントされた数 */
             int     vfc_flags;              /* 永久フラグ */
             struct  vfsconf *vfc_next;      /* リストの次 */
     };

     新しいファイルシステムがマウントされるとき、vfs_mount(9) はその名前によっ
     て vfsconf 構造体の検索を行い、それがまだ登録されていないなら、そのカーネ
     ルモジュールのロードを試みます。新しいマウントポイントのためのファイルシ
     ステム操作は vfc_vfsops から取られ、mount 構造体の mnt_vfc はファイルシス
     テムタイプのために vfsconf 構造体で直接指します。ファイルシステムタイプ番
     号は vfs_register() で割り当てられた vfc_typenum から取られ、マウントフラ
     グは vfc_flags のマスクから取られます。与えられたタイプのファイルシステム
     がマウントされるたびに vfc_refcount は増加されます。

     vfs_register() は新しい vfsconf 構造体を取り、それを既存のファイルシステ
     ムのリストに追加します。タイプがまだ登録されていないなら、それは、ファイ
     ルシステム操作ベクタで vfs_init() 関数を呼び出すことによって、初期化され
     ます。vfs_register() は、新たに割り当てられたタイプ番号と同じとなるよう
     に、このファイルシステムタイプのための oid のどんな sysctl ノードも更新し
     ます。訳注: oid の意味不明。タイポか。

     vfs_unregister() は、現在、マウントされなかったインスタンスがあれば、登録
     されたファイルシステムタイプのリストから vfc をアンリンクします。ファイル
     システム初期化ベクトルの vfs_uninit() 関数が定義されるなら、それは呼び出
     されます。

     vfs_modevent() は、ファイルシステムカーネルモジュールのロードとアンロード
     の操作のために VFS_SET() によって登録されます。MOD_LOAD の場合には、
     vfs_register() が呼び出されます。MOD_UNLOAD の場合には、vfs_unregister()
     が呼び出されます。

戻り値
     vfs_register() は成功すれば、0 を返し、そうでなければファイルシステムタイ
     プが既に登録されていることを示す EEXIST が返されます。

     vfs_unregister() は成功すれば、0 を返します。vfsconf エントリが vfc に一
     致する名前を見つけることができないなら、EINVAL が返されます。ファイルシス
     テムタイプのマウントされたインスタンスの参照カウントが 0 でないなら、
     EBUSY が返されます。vfs_uninit() が呼び出されるなら、それが返すどんなエ
     ラーも vfs_unregister() によって返されます。

     vfs_modevent() は、いずれにせよ vfs_register() または vfs_unregister() へ
     の呼び出しの結果を返します。

関連項目
     vfs_mount(9), vfs_rootmountalloc(9), VFS_SET(9)

vfs_mountが呼び出された際に、まだ登録されていない場合はカーネルモジュールのロードを試み、カーネルモジュールをロードした際にvfs_register関数内でチェック及び初期化したあと、vfs_initが呼ばれ、カーネルモジュールがアンロードされる際にはvfs_unregister関数からvfs_uninitが呼ばれるようです。

vfs_unregister関数

vfs_unregister関数は以下のようになっています。

/* Remove registration of a filesystem type */
static int
vfs_unregister(struct vfsconf *vfc)
{
        struct vfsconf *vfsp;
        int error, i, maxtypenum;

        i = vfc->vfc_typenum;

        vfsp = vfs_byname(vfc->vfc_name);
        if (vfsp == NULL)
                return EINVAL;
        if (vfsp->vfc_refcount)
                return EBUSY;
        if (vfc->vfc_vfsops->vfs_uninit != NULL) {
                error = (*vfc->vfc_vfsops->vfs_uninit)(vfsp);
                if (error)
                        return (error);
        }
        TAILQ_REMOVE(&vfsconf, vfsp, vfc_list);
        maxtypenum = VFS_GENERIC;
        TAILQ_FOREACH(vfsp, &vfsconf, vfc_list)
                if (maxtypenum < vfsp->vfc_typenum)
                        maxtypenum = vfsp->vfc_typenum;
        maxvfsconf = maxtypenum + 1;
        return 0;
}

確かにvfs_uninitがセットされている場合はコールしています。

vfs_modevent関数

vfs_register関数とvfs_unregister関数はvfs_modevent関数で呼び出されています。

/*
 * Standard kernel module handling code for filesystem modules.
 * Referenced from VFS_SET().
 */
int
vfs_modevent(module_t mod, int type, void *data)
{
        struct vfsconf *vfc;
        int error = 0;

        vfc = (struct vfsconf *)data;

        switch (type) {
        case MOD_LOAD:
                if (vfc)
                        error = vfs_register(vfc);
                break;

        case MOD_UNLOAD:
                if (vfc)
                        error = vfs_unregister(vfc);
                break;
        default:
                error = EOPNOTSUPP;
                break;
        }
        return (error);
}

vfs_modeventがどこから呼び出されているか見てみると、sys/mount.h内で以下のようにマクロが定義されています。

#define VFS_SET(vfsops, fsname, flags) \
        static struct vfsconf fsname ## _vfsconf = {            \
                .vfc_version = VFS_VERSION,                     \
                .vfc_name = #fsname,                            \
                .vfc_vfsops = &vfsops,                          \
                .vfc_typenum = -1,                              \
                .vfc_flags = flags,                             \
        };                                                      \
        static moduledata_t fsname ## _mod = {                  \
                #fsname,                                        \
                vfs_modevent,                                   \
                & fsname ## _vfsconf                            \
        };                                                      \
        DECLARE_MODULE(fsname, fsname ## _mod, SI_SUB_VFS, SI_ORDER_MIDDLE)

VFS_SETまで戻ってきました。
DECLARE_MODULEによって、カーネルモジュールとして宣言されているようですね。


以上、ファイルシステムモジュールのロードまでの流れについて大筋みえてきたので、次回はvfs_mountから追いかけていきたいと思います。