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を取得しているだけです。
kern_mount_data()も、mountコマンドと同じようにvfs_kern_mount()をコールします。ただし、ファイルシステムをユーザ空間にエックスポートする必要がないため、mntフラグをMS_KERNMOUNTとします。これはmountコマンドでファイルシステムをmontできません。
dentry取得もinode取得と同じです。通常は親dentry(ディレクトリ)をdentry->d_parentとし、親兄弟リストに追加しますが、kern_mount()は、パス走査されないためその必要がありません。
inodeファイルオペレーションコールバックは無視して、fileを取得時に直接ファイルオペレーションコールバックを設定してるケースもあるようです。カーネル内の処理ですから可能なわけですが・・・。
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を取得時に直接ファイルオペレーションコールバックを設定してるケースもあるようです。カーネル内の処理ですから可能なわけですが・・・。






