レガシーガジェット研究所

気になったことのあれこれ。

Linux Kernel ~ 仮想ファイルシステムの操作 ~

概要

「詳解Linux Kernel」を参考にVersion 2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelx86とする。

今回はプロセスの仮想ファイルシステム(VFS)の操作について見ていく。

名前空間

全てのプロセスは独立したファイルシステムツリーを保持することが可能でこれがプロセスでの一種の名前空間となる。ほとんどの場合プロセスは同じ名前空間を共有し、それはinitプロセスのファイルシステムツリーであるシステムのルートファイルシステムとなる。

cloneシステムコールCLONE_NEWNSフラグを設定した場合プロセスは新しい名前空間を取得する。現行プロセスの名前空間を変更する際にはLinux固有のpivot_rootシステムコールなどで名前空間のルートファイルシステムを変更することも可能。

プロセスの名前空間はプロセスディスクリプタnamespaceメンバが指すnamespace構造体によって表現される。

// include/linux/namespace.h
struct namespace {
    atomic_t        count; /* 参照カウンタ */
    struct vfsmount *  root; /* この名前空間のルートファイルシステムディスクリプタ */
    struct list_head   list; /* マウント済みの全てのファイルシステムディスクリプタリストの先頭 */
    struct rw_semaphore    sem; /* この構造体を保護するための読み書きセマフォ */
};

ファイルシステムのマウント

カーネルはマウントする度にマウントする対象のファイルシステムとマウント済みファイルシステムの関係を記録する。その情報はvfsmount型のマウント済みファイルシステムディスクリプタに記録される。

// include/linux/mount.h
struct vfsmount
{
    struct list_head mnt_hash; /* ハッシュテーブルリスト用のポインタ */
    struct vfsmount *mnt_parent;   /* マウントされる親ファイルシステムへのポインタ */
    struct dentry *mnt_mountpoint; /* マウントポイントのdエントリ */
    struct dentry *mnt_root;   /* マウントするファイルシステムのルートディレクトリに対応するdエントリ */
    struct super_block *mnt_sb;    /* マウントするファイルシステムのスーパーブロックオブジェクト */
    struct list_head mnt_mounts;   /* マウントするファイルシステムの全てのファイルシステムディスクリプタリストの先頭 */
    struct list_head mnt_child;    /* マウント済みファイルシステムディスクリプタのmnt_mountsリストへのポインタ */
    atomic_t mnt_count; /* 利用カウンタ(ファイルシステムのアンマウントをきんしするために増加させる) */
    int mnt_flags; /* フラグ */
    int mnt_expiry_mark; /* ファイルシステムが有効期限切れかどうか */
    char *mnt_devname;     /* デバイスファイル名 */
    struct list_head mnt_list; /* マウント済みのファイルシステムディスクリプタの名前空間リストへのポインタ */
    struct list_head mnt_fslink;   /* ファイルシステム固有の期限管理リストのポインタ */
    struct namespace *mnt_namespace; /* ファイルシステムをマウントしたプロセスの名前空間へのポインタ */
};

vfsmountデータ構造はいくつかの双方向リストで管理される。

vfsmount_lockはスピンロックで同時アクセスからファイルシステムオブジェクトのリストを保護する。

// include/linux/mount.h
extern spinlock_t vfsmount_lock;

vfsmountディスクリプタmnt_flagsメンバはそのファイルシステムにある特定のファイル処理方法を指定する。フラグ値として以下のような値が定義されている。

#define MNT_NOSUID  1 /* setuid及びsetgidを禁止 */
#define MNT_NODEV  2 /* デバイスファイルへのアクセスを禁止 */
#define MNT_NOEXEC 4 /* プログラムの実行を禁止 */

一般的なファイルシステムのマウント

ファイルシステムをマウントするにはmountシステムコールを使用する。当該システムコールは以下のようなフラグを引数に取る。

// include/linux/mount.h
#define MS_RDONLY   1 /* ファイルは読み取り専用 */
#define MS_NOSUID   2 /* setuid及びsetgidを無視 */
#define MS_NODEV    4 /* デバイスファイルへのアクセスを禁止 */
#define MS_NOEXEC   8 /* プログラムの実行不可 */
#define MS_SYNCHRONOUS 16 /* ディレクトリやファイルに対しての同期書き込み */
#define MS_REMOUNT 32 /* ファイルシステムの再マウント(フラグ変更) */
#define MS_MANDLOCK    64 /* 強制ロック可能 */
#define MS_DIRSYNC 128    /* ディレクトリに対しての同期書き込み */
#define MS_NOATIME 1024   /* アクセス時刻を更新しない */
#define MS_NODIRATIME  2048   /* ディレクトリのアクセス時刻を更新しない */
#define MS_BIND        4096 /* バインドマウント */
#define MS_MOVE        8192 /* マウント済みのファイルシステムをアトミックに他のマウントポイントへ移動 */
#define MS_REC     16384 /* 再帰的なバインドマウント */
#define MS_VERBOSE 32768  /* マウント時のエラーメッセージを出力 */

mountシステムコールsys_mountとして以下のように定義されている。

// fs/namespace.c
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
              char __user * type, unsigned long flags,
              void __user * data)
{
    int retval;
    unsigned long data_page;
    unsigned long type_page;
    unsigned long dev_page;
    char *dir_page;

    // ユーザ空間からデータをコピー
    retval = copy_mount_options (type, &type_page);
    if (retval < 0)
        return retval;

    dir_page = getname(dir_name);
    retval = PTR_ERR(dir_page);
    if (IS_ERR(dir_page))
        goto out1;

    // ユーザ空間からデータをコピー
    retval = copy_mount_options (dev_name, &dev_page);
    if (retval < 0)
        goto out2;

    // ユーザ空間からデータをコピー
    retval = copy_mount_options (data, &data_page);
    if (retval < 0)
        goto out3;

    lock_kernel(); // カーネルロック
    retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
              flags, (void*)data_page);
    unlock_kernel(); // カーネルロック解除
    free_page(data_page); // ページの解放

// コピーしたデータが存在するページの開放する
out3:
    free_page(dev_page);
out2:
    putname(dir_page);
out1:
    free_page(type_page);
    return retval;
}

内部ではdo_mount関数が呼ばれているのがわかる。

// fs/namespace.c
long do_mount(char * dev_name, char * dir_name, char *type_page,
          unsigned long flags, void *data_page)
{
    struct nameidata nd;
    int retval = 0;
    int mnt_flags = 0;

    /* マジックナンバーを破棄 */
    if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
        flags &= ~MS_MGC_MSK;

    /* 基本的なバリデーション */
    if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
        return -EINVAL;
    if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
        return -EINVAL;

    if (data_page)
        ((char *)data_page)[PAGE_SIZE - 1] = 0;

    /* マウントフラグをコピー */
    if (flags & MS_NOSUID)
        mnt_flags |= MNT_NOSUID;
    if (flags & MS_NODEV)
        mnt_flags |= MNT_NODEV;
    if (flags & MS_NOEXEC)
        mnt_flags |= MNT_NOEXEC;
    flags &= ~(MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_ACTIVE);

    /* パス名の検索*/
    retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
    if (retval)
        return retval;

    retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
    if (retval)
        goto dput_out;

    /* マウントフラグ固有の処理 */
    if (flags & MS_REMOUNT) /* マウントフラグ及びファイルシステムフラグの変更 */
        retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
                    data_page);
    else if (flags & MS_BIND) /* バインドマウント */
        retval = do_loopback(&nd, dev_name, flags & MS_REC);
    else if (flags & MS_MOVE) /* マウントポイントの変更 */
        retval = do_move_mount(&nd, dev_name);
    else
        /* 通常のマウント(特殊ファイルシステムやディスク上にデータを持つファイルシステム) */
        retval = do_new_mount(&nd, type_page, flags, mnt_flags,
                      dev_name, data_page);
dput_out:
    path_release(&nd);
    return retval;
}

通常のマウントではdo_new_mountが内部で呼び出されることがわかる。

// fs/namespace.c
static int do_new_mount(struct nameidata *nd, char *type, int flags,
            int mnt_flags, char *name, void *data)
{
    struct vfsmount *mnt;

    if (!type || !memchr(type, 0, PAGE_SIZE))
        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);

    // 名前空間のファイルシステムツリーにマウントしたファイルシステムを追加
    return do_add_mount(mnt, nd, mnt_flags, NULL);
}

内部で呼び出されているdo_kern_mountが実際のマウント処理を行っており以下のような処理を行う。

// fs/super.c
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
    struct file_system_type *type = get_fs_type(fstype); // ファイルシステム名から対応するfile_system_typeディスクリプタを取得
    struct super_block *sb = ERR_PTR(-ENOMEM);
    struct vfsmount *mnt;
    int error;
    char *secdata = NULL;

    // ファイルシステムの樹別が指定されていない
    if (!type)
        return ERR_PTR(-ENODEV);

    // ファイルシステムディスクリプタを初期化(各リストや参照カウンタ、デバイス名など)
    mnt = alloc_vfsmnt(name);
    if (!mnt)
        goto out;
    
    // 渡すデータがある場合
    if (data) {
        secdata = alloc_secdata(); // 0クリアされたページを確保
        if (!secdata) {
            sb = ERR_PTR(-ENOMEM);
            goto out_mnt;
        }

        // データをコピー
        error = security_sb_copy_data(type, data, secdata);
        if (error) {
            sb = ERR_PTR(error);
            goto out_free_secdata;
        }
    }

    // 初期化したスーパーブロックオブジェクトを取得
    sb = type->get_sb(type, flags, name, data);
    if (IS_ERR(sb))
        goto out_free_secdata;
    error = security_sb_kern_mount(sb, secdata);
    if (error)
        goto out_sb;
    // マウント情報を初期化していく
    mnt->mnt_sb = sb;
    mnt->mnt_root = dget(sb->s_root);
    mnt->mnt_mountpoint = sb->s_root;
    mnt->mnt_parent = mnt;
    mnt->mnt_namespace = current->namespace;
    up_write(&sb->s_umount); // スーパーブロックオブジェクトの解放
    put_filesystem(type);
    return mnt;
// エラー処理
out_sb:
    up_write(&sb->s_umount);
    deactivate_super(sb);
    sb = ERR_PTR(error);
out_free_secdata:
    free_secdata(secdata);
out_mnt:
    free_vfsmnt(mnt);
out:
    put_filesystem(type);
    return (struct vfsmount *)sb;
}

スーパーブロックオブジェクトの取得

do_kern_mount関数で呼び出されていたtype->get_sb関数はスーパーブロックオブジェクトを取得するためのもので、スーパーブロック自体がファイルシステムに依存するためファイルシステム固有の関数が呼び出される。

sb = type->get_sb(type, flags, name, data);

Ext2ではget_sbメンバに以下のように固有の関数が設定されている。

// fs/ext2/super.c
static struct file_system_type ext2_fs_type = {
    .owner      = THIS_MODULE,
    .name       = "ext2",
    .get_sb     = ext2_get_sb,
    .kill_sb    = kill_block_super,
    .fs_flags   = FS_REQUIRES_DEV,
};

実際の処理は以下に行われる。

// fs/ext2/super.c
static struct super_block *ext2_get_sb(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data)
{
    return get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super);
}

内部でget_db_bdevが呼びだれているが重要なのはext2_fill_superが引数として渡されている部分で当該関数はExt2のディスクパーティションからスーパーブロックを読み取る。

get_sb_bdev関数は以下のように定義されている。

// fs/super.c
struct super_block *get_sb_bdev(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data,
    int (*fill_super)(struct super_block *, void *, int))
{
    struct block_device *bdev;
    struct super_block *s;
    int error = 0;

    // ブロックデバイスをオープン
    bdev = open_bdev_excl(dev_name, flags, fs_type);
    if (IS_ERR(bdev))
        return (struct super_block *)bdev;

    down(&bdev->bd_mount_sem); // セマフォ
    // スーパーブロックオブジェクトの確保及び取得
    s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);
    up(&bdev->bd_mount_sem);
    if (IS_ERR(s))
        goto out;

    if (s->s_root) {
        if ((flags ^ s->s_flags) & MS_RDONLY) {
            up_write(&s->s_umount);
            deactivate_super(s);
            s = ERR_PTR(-EBUSY);
        }
        goto out;
    } else {
        char b[BDEVNAME_SIZE];

        s->s_flags = flags; // フラグを設定
        strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id)); // ブロックデバイス名をコピー
        s->s_old_blocksize = block_size(bdev);
        sb_set_blocksize(s, s->s_old_blocksize);
        error = fill_super(s, data, flags & MS_VERBOSE ? 1 : 0); // スーパーブロックの読み出し
        if (error) {
            up_write(&s->s_umount);
            deactivate_super(s);
            s = ERR_PTR(error);
        } else {
            s->s_flags |= MS_ACTIVE;
            bdev_uevent(bdev, KOBJ_MOUNT);
        }
    }

    return s; // スーパーブロックオブジェクトを返す

out:
    close_bdev_excl(bdev);
    return s;
}

ルートファイルシステムのマウント

ルートファイルシステムは様々な場所に置くことが可能で、ハードディスクのパーティションフロッピーディスクNFSなどのネットワークファイルシステム、RAMディスクなど多岐に渡る。

ルートファイルシステムのマウントでは主に以下の2つの処理を行う。

rootfsのマウントはルートファイルシステムを容易に変更するための仕組みで、一例として起動時に最小のドライバを保持するカーネルをRAMをに読み込みrootfsがシステムのハードウェアを検出した後で必要なカーネルモジュールを読み込み、ブロックデバイスからルートファイルシステムを再マウントするなどの処理が行われる。

rootfsのマウント

rootfsのマウントはinit_rootfs関数とinit_mount_tree関数でシステム初期化中に実行されることで行われる。特殊ファイルシステムinit_rootfsによって初期化される。

static struct file_system_type rootfs_fs_type = {
    .name       = "rootfs",
    .get_sb     = rootfs_get_sb,
    .kill_sb    = kill_litter_super,
};

int __init init_rootfs(void)
{
    return register_filesystem(&rootfs_fs_type);
}

register_filesystem関数はグローバル変数であるfile_systems変数を参照し、まだ同一種別のファイルシステムの登録がない場合には新たな種別として登録する。

// fs/filesystems.c
int register_filesystem(struct file_system_type * fs)
{
    int res = 0;
    struct file_system_type ** p;

    // fsが指定されていない
    if (!fs)
        return -EINVAL;
    if (fs->next)
        return -EBUSY;
    
    INIT_LIST_HEAD(&fs->fs_supers); // 同一種別のファイルシステムリストを初期化
    write_lock(&file_systems_lock); // ロック
    p = find_filesystem(fs->name);
    if (*p) // 既に登録されている場合はエラー
        res = -EBUSY;
    else
        *p = fs; // file_sysytemsグローバル変数の末尾に新たなファイルシステム種別を登録
    write_unlock(&file_systems_lock); // アンロック
    return res;
}

次にinit_mount_tree関数でrootfsをマウントする。

// fs/namespace.c
static void __init init_mount_tree(void)
{
    struct vfsmount *mnt;
    struct namespace *namespace;
    struct task_struct *g, *p;

    // rootfsのマウント
    mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
    if (IS_ERR(mnt))
        panic("Can't create rootfs");

    // initプロセスのための名前空間オブジェクトの取得及び初期化
    namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
    if (!namespace)
        panic("Can't allocate initial namespace");
    atomic_set(&namespace->count, 1);
    INIT_LIST_HEAD(&namespace->list);
    init_rwsem(&namespace->sem);
    list_add(&mnt->mnt_list, &namespace->list);
    namespace->root = mnt;
    mnt->mnt_namespace = namespace;

    init_task.namespace = namespace;

    // 他のプロセスにも名前空間オブジェクトを設定 
    read_lock(&tasklist_lock);
    do_each_thread(g, p) {
        get_namespace(namespace);
        p->namespace = namespace;
    } while_each_thread(g, p);
    read_unlock(&tasklist_lock);

    // カレントプロセスのワーキングディレクトリとルートディレクトリを設定
    set_fs_pwd(current->fs, namespace->root, namespace->root->mnt_root);
    set_fs_root(current->fs, namespace->root, namespace->root->mnt_root);
}

ルートファイルシステムのマウント

ルートファイルシステムのマウント処理はシステム初期化の終盤で行われマウント方法にもいくつか存在する。今回の場合はルートファイルシステムに特殊ファイルシステムを用いていないと仮定する。

ルートファイルシステムのマウントを行うprepare_namespace 関数は以下のように定義されている。

// init/do_mounts.c
void __init prepare_namespace(void)
{
    int is_floppy;

    // devfsのマウント
    mount_devfs();

    // ルートデバイスのマウント完了待ち
    if (root_delay) {
        printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
               root_delay);
        ssleep(root_delay);
    }

    md_run_setup();

    // ここではデバイスファイル名が指定されていると仮定する
    if (saved_root_name[0]) {
        root_device_name = saved_root_name;
        // デバイス名をデバイス番号に変換
        ROOT_DEV = name_to_dev_t(root_device_name);
        // デバイス名に"/dev/"の文字列を先頭に加える。
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }

    // 今回はフロッピーでないと仮定
    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

    // 初期RAMディスクをロード
    if (initrd_load())
        goto out;

    // 今回はフロッピーでないと仮定
    if (is_floppy && rd_doload && rd_load_disk(0))
        ROOT_DEV = Root_RAM0;

    mount_root(); // 後述
out:
    umount_devfs("/dev"); // devfsのアンマウント
    sys_mount(".", "/", NULL, MS_MOVE, NULL); // カレントディレクトリをルートにマウント
    sys_chroot("."); // それをルートディレクトリに変更
    security_sb_post_mountroot();
    mount_devfs_fs();
}

カレントディレクトリをルートディレクトリに変更している時点でカレントディレクトリにはルートファイルシステムがマウントされており、そのマウント処理をmount_root関数が行っている。

void __init mount_root(void)
{
    create_dev("/dev/root", ROOT_DEV, root_device_name); // ルートファイルシステムの存在するデバイスファイルを作成
    // デバイスファイルのマウント及びプロセスのカレントディレクトリを/rootに
    mount_block_root("/dev/root", root_mountflags); 
}

上記では/dev/rootの作成後、mount_block_root内で/dev/root/rootにマウントする。

上記から特殊ファイルシステム上に実際のディスクベースのファイルシステムがマウントされているのがわかる。

パス検索

パス検索はカレントプロセスが保持している情報を基に行われ、絶対パスの場合にはcurrent->fs->root相対パスの場合にはcurrent->fs-pwdから検索を開始する。実際にはdエントリからiノードを辿るように行われるがdエントリキャッシュが存在するためディスクアクセスは頻繁には発生せず高速に行われる。しかしディレクトリのアクセス権やシンボリックリンク、マウントポイントなどチェック項目は多数存在する。

パズ名の検索結果はnameidata想像体変数に格納される。

// include/linux/namei.h
struct nameidata {
    struct dentry  *dentry; // dエントリ
    struct vfsmount *mnt; // ファイルシステムオブジェクトのアドレス
    struct qstr    last; // パス名の最後の要素
    unsigned int  flags; // 検索のためのフラグ
    int        last_type; // パス名の最後の要素の種類
    unsigned   depth; // シンボリックリンクのネスト数(<=5)
    char *saved_names[MAX_NESTED_LINKS + 1]; // シンボリックリンクのネストしたパス名の配列

    /* Intent data */
    union {
        struct open_intent open; // ファイルがアクセスされる方法を指定
    } intent;
};

実際の検索はpath_lookup関数で行われる。

// include/linux/namei.h
#define LOOKUP_FOLLOW       1 // 検索結果がシンボリックリンクである場合に検索を続行
#define LOOKUP_DIRECTORY    2 // 最後の要素はディレクトリでなければならない
#define LOOKUP_CONTINUE         4 // パス名に調べるべきファイル名が残っている
#define LOOKUP_PARENT      16 // パス名の最後の要素を含むディレクトリを検索
#define LOOKUP_NOALT       32 // エミュレートされたルートディレクトリを考慮しない
#define LOOKUP_OPEN        (0x0100) // ファイルをオープンする目的での検索
#define LOOKUP_CREATE      (0x0200) // ファイルを生成する目的での検索
#define LOOKUP_ACCESS      (0x0400) // ファイルに対するユーザ権限を調べる目的での検索

// fs/namei.c
int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
{
    int retval;

    nd->last_type = LAST_ROOT; /* if there are only slashes... */
    nd->flags = flags;
    nd->depth = 0;

    read_lock(&current->fs->lock); // ロック取得
    if (*name=='/') { // 絶対パス
        if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
            nd->mnt = mntget(current->fs->altrootmnt);
            nd->dentry = dget(current->fs->altroot);;
            read_unlock(&current->fs->lock);
            if (__emul_lookup_dentry(name,nd))
                return 0;
            read_lock(&current->fs->lock);
        }
        nd->mnt = mntget(current->fs->rootmnt); // マウント情報と
        nd->dentry = dget(current->fs->root); // dエントリを設定
    } else {
        nd->mnt = mntget(current->fs->pwdmnt); // マウント情報と
        nd->dentry = dget(current->fs->pwd); // dエントリを設定
    }
    read_unlock(&current->fs->lock); // ロックを解除
    current->total_link_count = 0; // リンクの総カウンタを初期化
    retval = link_path_walk(name, nd); // 検索処理
    if (unlikely(current->audit_context
             && nd && nd->dentry && nd->dentry->d_inode))
        audit_inode(name,
                nd->dentry->d_inode->i_ino,
                nd->dentry->d_inode->i_rdev);
    return retval;
}

実際の検索処理はlink_path_walk関数で行われ、dエントリからiノードを辿り目的のファイル情報を求める。具体的な検索処理はデータ構造から容易に想像できるものだったので詳細は割愛する。

VFSシステムコール

open

openシステムコール(sys_open)はファイルディスクリプタを戻り値として返す。当該値はカレントプロセスのファイルオブジェクトへのポインタ配列(current->files->fd)のインデックスとして使用される。

当該関数の定義は以下。

// fs/open.c
void fastcall fd_install(unsigned int fd, struct file * file)
{
    struct files_struct *files = current->files; // ファイルオブジェクトのポインタ配列
    spin_lock(&files->file_lock); // ロック
    if (unlikely(files->fd[fd] != NULL)) // 既に使用されていないか
        BUG();
    files->fd[fd] = file; // ファイルオブジェクトのアドレスを設定
    spin_unlock(&files->file_lock); // アンロック
}

asmlinkage long sys_open(const char __user * filename, int flags, int mode)
{
    char * tmp;
    int fd, error;

    tmp = getname(filename); // ユーザプロセス空間からファイル名を取得
    fd = PTR_ERR(tmp);
    if (!IS_ERR(tmp)) { // 無効な値でない場合
        fd = get_unused_fd(); // 未使用のファイルディレクリプタ番号を取得
        if (fd >= 0) { // ファイルディレクリプタが存在する場合
            struct file *f = filp_open(tmp, flags, mode); // ファイルオブジェクトを取得
            // エラー処理
            error = PTR_ERR(f);
            if (IS_ERR(f))
                goto out_error;
            fd_install(fd, f); // 取得した未使用のファイルディスクリプタ番号にファイルオブジェクトをバインド
        }
out:
        putname(tmp);
    }
    return fd;

out_error:
    put_unused_fd(fd);
    fd = error;
    goto out;
}

上記から未使用のディスクリプタ番号を取得後、file_openで指定ファイルのオブジェクトを取得し、そのファイルディスクリプタ番号をインデックスにファイルオブジェクトのアドレスを配列に設定しているのがわかる。

ファイルオブジェクトの取得を行っているfile_open関数は以下のように定義されている。

// fs/open.c
struct file *filp_open(const char * filename, int flags, int mode)
{
    int namei_flags, error;
    struct nameidata nd;

    namei_flags = flags;
    if ((namei_flags+1) & O_ACCMODE)
        namei_flags++;
    if (namei_flags & O_TRUNC)
        namei_flags |= 2;

    // パス検索
    error = open_namei(filename, namei_flags, mode, &nd);
    if (!error)
        // dエントリ及びマウント情報からファイルオブジェクトを初期化し返す
        return dentry_open(nd.dentry, nd.mnt, flags);

    return ERR_PTR(error);
}

パス検索の結果(nameidata)が保持しているdエントリ及びマウント情報からファイルオブジェクトを初期化しそれを戻り値として返す。

read

当該システムコール(sys_read)はファイルディスクリプタ番号、ユーザ空間に存在するバッファのアドレス、読み込むサイズを引数に取り、ファイルディレクリプタ番号に対応するファイルオブジェクトからデータを指定のサイズ分読み込みバッファに設定する。

実際の定義は以下のようにされている。

// fs/read_write.c
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;

    // ファイルディレクリプタ番号から対応するファイルオブジェクトを取得する
    file = fget_light(fd, &fput_needed);
    if (file) { // ファイルオブジェクトの取得に成功
        loff_t pos = file_pos_read(file); // ファイルオブジェクトのオフセットを取得
        ret = vfs_read(file, buf, count, &pos);
        file_pos_write(file, pos); // オフセットの更新
        fput_light(file, fput_needed);
    }

    return ret;
}

上記から実際のデータの読み込みはvfs_readが行っていることがわかる。

// fs/read_write.c
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    // バリデーション
    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
        return -EINVAL;
    if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
        return -EFAULT;

    ret = rw_verify_area(READ, file, pos, count); // ロックがかかっているかどうかの確認
    if (!ret) {
        ret = security_file_permission (file, MAY_READ); // 権限の確認
        if (!ret) {
            // readがある場合
            if (file->f_op->read)
                ret = file->f_op->read(file, buf, count, pos);
            else
                ret = do_sync_read(file, buf, count, pos); // 内部でaio_readを呼び出す
            if (ret > 0) { // 読み込みに成功した場合
                dnotify_parent(file->f_dentry, DN_ACCESS); // ファイルアクセスイベントを通知
                current->rchar += ret;
            }
            current->syscr++;
        }
    }

    return ret; // 読み出したバイト数
}

バリデーションを行った後ロックや権限の確認を行い、その後ファイルオブジェクトのメソッド群に設定されたreadもしくはaio_readを呼び出している。

write

当該システムコール(sys_write)も同様にファイルディスクリプタ番号、ユーザ空間に存在するバッファのアドレス、書き込むサイズを引数に取り、ファイルディレクリプタ番号に対応するファイルオブジェクトにバッファに設定されたデータを指定のサイズ分書き込む。

実際の定義は以下のようにされている。

// fs/read_write.c
asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;

    // ファイルディレクリプタ番号から対応するファイルオブジェクトを取得する
    file = fget_light(fd, &fput_needed);
    if (file) { // ファイルオブジェクトの取得に成功
        loff_t pos = file_pos_read(file);
        ret = vfs_write(file, buf, count, &pos); // ファイルオブジェクトのオフセットを取得
        file_pos_write(file, pos); // オフセットの更新
        fput_light(file, fput_needed); // 利用数カウンタのデクリメント
    }

    return ret;
}

上記から実際のデータの書き込みはvfs_writeが行っていることがわかる。

// fs/read_write.c
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    // バリデーション
    if (!(file->f_mode & FMODE_WRITE))
        return -EBADF;
    if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))
        return -EINVAL;
    if (unlikely(!access_ok(VERIFY_READ, buf, count)))
        return -EFAULT;

    ret = rw_verify_area(WRITE, file, pos, count); // ロックの有無を確認
    if (!ret) {
        ret = security_file_permission (file, MAY_WRITE); // 権限の確認
        if (!ret) {
            // writeが設定されている場合
            if (file->f_op->write)
                ret = file->f_op->write(file, buf, count, pos);
            else
                ret = do_sync_write(file, buf, count, pos); // aio_writeを呼び出す
            if (ret > 0) {
                dnotify_parent(file->f_dentry, DN_MODIFY); // ファイルの変更イベントを通知
                current->wchar += ret;
            }
            current->syscw++;
        }
    }

    return ret; // 書き込んだバイト数
}

バリデーションを行った後ロックや権限の確認を行い、その後ファイルオブジェクトのメソッド群に設定されたwriteもしくはaio_writeを呼び出している。

close

ファイルディスクリプタ番号を引数に取り、対応するカレントプロセスが保持しているファイルオブジェクトを開放する。当該システムコール(sys_clonse)は以下のように定義されている。

// fs/open.c
asmlinkage long sys_close(unsigned int fd)
{
    struct file * filp;
    struct files_struct *files = current->files;

    spin_lock(&files->file_lock); // files_structオブジェクトのロック
    if (fd >= files->max_fds) // 最大値を超過している場合
        goto out_unlock;
    filp = files->fd[fd]; // ファイルオブジェクトの取得
    if (!filp) // 無ければ終了
        goto out_unlock;
    files->fd[fd] = NULL; // オブジェクトのポインタをクリア
    FD_CLR(fd, files->close_on_exec);
    __put_unused_fd(files, fd); // 次の割り当て対象のファイルディスクリプタ番号を戻す
    spin_unlock(&files->file_lock);
    return filp_close(filp, files); // ファイルオブジェクトの解放

out_unlock:
    spin_unlock(&files->file_lock); // アンロック
    return -EBADF;
}

上記では簡単なバリデーションとカレントプロセスが保持するファイルオブジェクトのポインタ配列の要素をクリアしている。実際のファイルオブジェクトの解放はfilp_closeが行っている。

// fs/open.c
int filp_close(struct file *filp, fl_owner_t id)
{
    int retval;

    /* Report and clear outstanding errors */
    retval = filp->f_error; // エラー番号
    if (retval)
        filp->f_error = 0; // エラーをクリア

    if (!file_count(filp)) { // 参照カウンタが0
        printk(KERN_ERR "VFS: Close: file count is 0\n");
        return retval;
    }

    // flushメソッドが設定されている場合には実行
    if (filp->f_op && filp->f_op->flush) {
        int err = filp->f_op->flush(filp);
        if (!retval)
            retval = err;
    }

    dnotify_flush(filp, id); // フラッシュの通知
    locks_remove_posix(filp, id);
    fput(filp); // オブジェクトの解放
    return retval;
}

参考