kern_mount


socket/pipe/shmem等は、vfs下で実装されており、socket()/shmget()/pipe()の返り値は、FILE IDその物です。従ってこの様な実装では、掛かるファイルシステムがマウントされてなければなりません。

fileのベースはdentry、dentryのベースはinode、inodeのベースはファイルシステムのスーパブロックです。このスーパブロックを管理しているのがvfsmntだからです。

通常のmountは、vfsmntを取得後マウント先にバインドしますが、これらのmountはその必要がありません。この処理を行うのがkern_mount()です。

mountコマンドは、sys_mount()からdo_mount()をコールし、マウントフラグにより do_remount()/do_loopback()/do_change_type()/do_move_mount()/ do_new_mount()がコールされます。新規マウントならdo_new_mount()がコールされ、do_kern_mount()でファイルシステムのvfsmounを取得した後、do_add_mount()でマウント先にバインドします。do_kern_mount()はvfs_kern_mount()でvfsmountを取得しているだけです。
static int do_new_mount(struct path *path, char *type, int flags,
                       int mnt_flags, char *name, void *data)
{
       struct vfsmount *mnt;
       int err;

       if (!type)
               return -EINVAL;

       if (!capable(CAP_SYS_ADMIN))
               return -EPERM;

       mnt = do_kern_mount(type, flags, name, data);
       if (IS_ERR(mnt))
               return PTR_ERR(mnt);

       err = do_add_mount(real_mount(mnt), path, mnt_flags);
       if (err)
               mntput(mnt);
       return err;
}
kern_mount()はkern_mount_data()をdata=NULLでコールします。dataはファイルシステム毎のmountコールバック関数のパラメータです。

kern_mount_data()も、mountコマンドと同じようにvfs_kern_mount()をコールします。ただし、ファイルシステムをユーザ空間にエックスポートする必要がないため、mntフラグをMS_KERNMOUNTとします。これはmountコマンドでファイルシステムをmontできません。
#define kern_mount(type) kern_mount_data(type, NULL)

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

struct vfsmount *kern_mount_data(struct file_system_type *type, void *data)
{
       struct vfsmount *mnt;
       mnt = vfs_kern_mount(type, MS_KERNMOUNT, type->name, data);
       if (!IS_ERR(mnt)) {
               mnt_make_longterm(mnt);
       }
       return mnt;
}
inode取得は、通常はnew_inode()がコールされ、new_inode_pseudo()でinodeを取得した後、スーパブロックにリストしますが、kern_mount()のinode取得は、直接new_inode_pseudo()がコールしinodeを取得するのみです。たぶんinodeキャッシュをスワップ対象でないからでは思います。
struct inode *new_inode(struct super_block *sb)
{
       struct inode *inode;

       spin_lock_prefetch(&inode_sb_list_lock);

       inode = new_inode_pseudo(sb);
       if (inode)
               inode_sb_list_add(inode);
       return inode;
}

struct inode *new_inode_pseudo(struct super_block *sb)
{
       struct inode *inode = alloc_inode(sb);

       if (inode) {
               spin_lock(&inode->i_lock);
               inode->i_state = 0;
               spin_unlock(&inode->i_lock);
               INIT_LIST_HEAD(&inode->i_sb_list);
       }
       return inode;
}
alloc_inode()でinodeを取得します。struct inodeはvfs下のinodeで、ファイルシステムに関係なく同じものです。実inodeはファイルシステムに応じて違っています。故にスーパブロックalloc_inode()コールバックで実inodeを取得します。この実inodeのメンバーのvfs_indeに、vfsのinodeを有しています。vfs inodeからcontein_of()マクロで実inodeを容易に取得できます。
static struct inode *alloc_inode(struct super_block *sb)
{
       struct inode *inode;

       if (sb->s_op->alloc_inode)
               inode = sb->s_op->alloc_inode(sb);
       else
               inode = kmem_cache_alloc(inode_cachep, GFP_KERNEL);

       if (!inode)
               return NULL;

       if (unlikely(inode_init_always(sb, inode))) {
               if (inode->i_sb->s_op->destroy_inode)
                       inode->i_sb->s_op->destroy_inode(inode);
               else
                       kmem_cache_free(inode_cachep, inode);
               return NULL;
       }

       return inode;
}
dentry取得は、通常d_alloc()がコールされ、kern_mount()のdentry取得は、d_alloc_pseudo()をコールします。
dentry取得もinode取得と同じです。通常は親dentry(ディレクトリ)をdentry->d_parentとし、親兄弟リストに追加しますが、kern_mount()は、パス走査されないためその必要がありません。
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
       struct dentry *dentry = __d_alloc(parent->d_sb, name);
       if (!dentry)
               return NULL;

       spin_lock(&parent->d_lock);

       __dget_dlock(parent);
       dentry->d_parent = parent;
       list_add(&dentry->d_u.d_child, &parent->d_subdirs);
       spin_unlock(&parent->d_lock);

       return dentry;
}

struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
{
       struct dentry *dentry = __d_alloc(sb, name);
       if (dentry)
               dentry->d_flags |= DCACHE_DISCONNECTED;
       return dentry;
}

補足

socket()/shmget()/pipe()をコールすると、かかるpseudo(見せ掛け)inode/dentryを取得し、struct fileにこのdentryを設定すると同時に、inodeのファイルオペレーションコールバックをfileに設定した後、カレントプロセスの構造体から空きのファイルIDにこのfileを設定することで、FILE IDでかかる操作が可能となります。FILE IDそのものですから、リモート端末のようなsshなんかは、そのsockeの返り値を標準入力/出力にリダイレクトすればいいわけです。(たぶん)

inodeファイルオペレーションコールバックは無視して、fileを取得時に直接ファイルオペレーションコールバックを設定してるケースもあるようです。カーネル内の処理ですから可能なわけですが・・・。


最終更新 2014/11/14 05:29:00 - north
(2014/11/14 05:29:00 作成)


検索

アクセス数
3689307
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。