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§=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);
長くなってきたので今日はここまで。