AIX 内核的虚拟文件系统框架 Author: sinister
Email: sinister@whitecell.org
Homepage:http://www.whitecell.org
Date: 2005-11-16
2005-10-25
虚拟文件系统(Virtual File System)作为上层系统调用(System call)接口以
及下层实际文件系统的中间层支持框架被广泛应用再各 UNIX 操作系统中。不论是
System V 系列的 AIX,SOLARIS 还是 BSD 系列的 Free/Net/Open BSD 操作系统都
支持虚拟文件系统(Virutal File System 以下简称 VFS)框架,为什么要在 System
call 与 File System 之间抽象出一层 VFS?其主要目的是为了支持多种类型的下层
实际文件系统,并对上层系统调用有一个统一完整的接口。为了更好的达到目的,VFS
是按照面向对象的方法设计实现。
本文主要以AIX 内核为基础介绍 VFS框架,如 AIX 内核特有的 GFS。象文件系统缓
存及相关的内存映射机制与内核文件读/写流程则不在本文的介绍范围之内。我会另写
篇文章来和大家探讨。
AIX 内核在启动时只有一个根文件系统的 vfs 对象,由 rootvfs 指向这个根 vfs
对象。rootvfs 是一个链头,之后安装的下层实际文件系统所对应的 vfs 结构都会添
加到由 rootvfs 所指向的 vfs 链中。每个vfs 结构都代表一个下层实际文件系统,
如 AIX 的 JFS 文件系统。每个下层实际文件系统至少有一个或可能多个(一个文件
系统被多次安装在不同目录上) vfs 结构对应。那么系统如何来连接每个 vfs 对象
与下层实际文件系统?又是如何区分每个 vfs 对象对应哪个下层实际文件系统的呢?
解决这个问题是靠内核维护的一个gfs 来实现的。结构描述如下:
struct gfs {
struct vfsops *gfs_ops;
struct vnodeops *gn_ops;
int gfs_type; /* type of gfs (from ) */
char gfs_name[16]; /* name of vfs (eg. "jfs","nfs", ...)*/
int (*gfs_init)(); /* ( gfsp ) - if ! NULL, */
/* called once to init gfs */
int gfs_flags; /* flags for gfs capabilities */
caddr_t gfs_data; /* ptr to gfs's private config data */
int (*gfs_rinit)();
int gfs_hold; /* count of mounts of this gfs type */
};
系统上安装的所有下层实际文件系统都可以在 gfs 结构中找到。也就是说下层实际文
件系统驱动要想在 AIX 内核顺利运行,都必须在第一次安装时通过调用一个 gfsadd()
函数来填充 gfs 结构中的相关信息。系统维护了一个 gfs 数组,根据下层实际文件系
统的类型作为数组的下标,来选择填充哪个 gfs 结构。这样一来,一个 gfs 结构就代
表一个下层实际文件系统,也就是说,gfs 与下层实际文件系统是一一对应的,有几个
gfs 就有几个下层实际文件系统,不会存在多个 vfs 代表一个下层实际文件系统的情
况。我们来看一下 gfs 中的重要数据结构。gfs 结构中的 gfs_type 是下层实际文件
系统在 gfs 结构中的一个索引,用来指定下层实际文件系统的类型,相关定义在 vmount
.h 中的,定义如下:
#define MNT_AIX 0 /* AIX physical fs "oaix" */
#define MNT_NFS 2 /* SUN Network File System "nfs" */
#define MNT_JFS 3 /* AIX R3 physical fs "jfs" */
#define MNT_CDROM 5 /* CDROM File System "cdrom" */
#define MNT_SFS 16 /* AIX Special FS (STREAM mounts) */
#define MNT_CACHEFS 17 /* Cachefs file system */
#define MNT_NFS3 18 /* NFSv3 file system */
#define MNT_AUTOFS 19 /* Automount file system */
gfs_name 指示了下层实际文件系统的名称,如下层实际文件系统是 JFS 那么 gfs->gfs
_name 则为 jfs。而 gfs_init 函数指针指向下层实际文件系统的初始化列程 如 jfs_i
nit()。gfs_flags 指定下层实际文件系统可否被强行卸载,和是否为远程文件系统,还
有 gfs 所支持的当前内核版本。列:gfs-> gfs_flags = GFS_FUMNT | GFS_VERSION4;
gfs_data应该是指下层实际文件系统专有的特性,如分区及元数据(meta data)。对于
gfs_data 我也不是很确定,可能理解有误,还希望得到你的指正。gfs_rinit() 函数指
针指向下层实际文件系统的,根节点初始化列程,如 jfs_rootinit()。还有两个重要的
结构就是 struct vfsops *gfs_ops, 和 struct vnodeops *gn_ops, 它们是vfs/vnode
用来连接下层实际文件系统相关操作函数的。这样就系统就可以很容易通过 vfs->
gfs<-jfs这种映射关系找到每个 vfs 对应的下层实际文件系统,并调用 vfs 相关函数
对其进行操作。下面我们来看下 AIX 内核定义的 struct vfs 结构,结构描述如下:
struct vfs {
struct vfs *vfs_next; /* vfs's are a linked list */
struct gfs *vfs_gfs; /* ptr to gfs of vfs */
struct vnode *vfs_mntd; /* pointer to mounted vnode, */
/* the root of this vfs */
struct vnode *vfs_mntdover; /* pointer to mounted-over */
/* vnode */
struct vnode *vfs_vnodes; /* all vnodes in this vfs */
int vfs_count; /* number of users of this vfs */
caddr_t vfs_data; /* private data area pointer */
unsigned int vfs_number; /* serial number to help */
/* distinguish between */
/* different mounts of the */
/* same object */
int vfs_bsize; /* native block size */
#ifdef _SUN
short vfs_exflags; /* for SUN, exported fs flags */
unsigned short vfs_exroot; /* for SUN, " fs uid 0 mapping */
#else
short vfs_rsvd1; /* Reserved */
unsigned short vfs_rsvd2; /* Reserved */
#endif /* _SUN */
struct vmount *vfs_mdata; /* record of mount arguments */
Simple_lock vfs_lock; /* lock to serialize vnode list */
};
typedef struct vfs vfs_t;
/* these defines are for backwards compatibility */
#define vfs_fsid vfs_mdata->vmt_fsid
#define vfs_date vfs_mdata->vmt_time
#define vfs_flag vfs_mdata->vmt_flags
#define vfs_type vfs_gfs->gfs_type
#define vfs_ops vfs_gfs->gfs_ops
从上面结构中可以看出 struct vfs 是一个链表。由 struct vfs *vfs_next 来指向
下一个vfs 结构,由上面提到的 gfs 结构中的 struct gfs *vfs_gfs 来找到下层实
际文件系统。以此来连接系统中的所有下层实际文件系统,并对应到其相关的 vfs。
为了更好的理解 VFS 实际操作过程及每个vfs->gfs<-physical file system 的对应
关系,我们用 AIX 的 kdb 来观察一下。
(0)> vfs
GFS MNTD MNTDOVER VNODES DATA TYPE FLAGS
1 30A0783C 001C9F30 1341E3F8 00000000 131F1E68 30A11C28 JFS DEVMOUNT
... /dev/hd4 mounted over /
2 30A078A4 001C9F30 13FDF9F8 1354B2F8 137D2968 30A11BC0 JFS DEVMOUNT
... /dev/hd2 mounted over /usr
3 30A07870 001C9F30 137A5260 132F1660 13902838 30A118E8 JFS DEVMOUNT
... /dev/hd9var mounted over /var
4 30A07808 001C9F30 1383BCB0 14239960 14111A88 30A119B8 JFS DEVMOUNT
... /dev/hd3 mounted over /tmp
5 30A078D8 001C9F30 13A01388 1354D788 13D0B678 30A11880 JFS DEVMOUNT
... /dev/hd1 mounted over /home
在 kdb 下输入 vfs 显示系统中所有的 vfs 对象。我们可以看到,系统中一共有 5 个
vfs 对象,每个 vfs 对象的第一项代表 vfs 地址,第二项代表 gfs 对象地址,
第三项是根 vnode。 第四项是所安装目录的 vnode,第五个地址是下层实际文件系
统相关的数据地址。第六项表示,下层实际文件系统的类型。第七项表示下层实际文件
系统的标志,DEVMOUNT 表示是本地文件系统。如果是网络文件系统,则标志为 REMOTE
如,nfs 文件系统。从上面所列出的信息可以看到,第一项 vfs 地址中应该是指向系
统的下一个 vfs 地址,即 vfs->vfs_next。我们看到每项 vfs 地址都是不同的。但它
所对应的第六项(TYPE)下层实际文件系统却是相同的 jfs。这也就证明了上面提到的,
每个 vfs 对象对应一个下层实际文件系统,而下层实际文件系统可能对应多个 vfs 对
象。第二项 gfs 对象地址则都是一样的,这也证明上面提到的每个 gfs 对象和下层实
际文件系统是一一对应的,有几个下层实际文件系统就有几个 gfs 对象。上面所列出的
下层实际文件系统只有 jfs,那么也就只有一个 gfs 对象。我们输入 vfs 30A0783C 来
看一下第一个 vfs 对象的内容。
(0)> vfs 30A0783C
GFS MNTD MNTDOVER VNODES DATA TYPE FLAGS
0 30A0783C 001C9F30 1341E3F8 00000000 131F1E68 30A11C28 JFS DEVMOUNT
... /dev/hd4 mounted over /
vfs_next..... 30A078A4 vfs_gfs...... 001C9F30 vfs_mntd..... 1341E3F8
vfs_mntdover. 00000000 vfs_vnodes... 131F1E68 vfs_count.... 00000001
vfs_number... 00000005 vfs_bsize.... 00001000 vfs_mdata.... 30A10200
vmt_revision. 00000001 vmt_length... 0000006C vfs_fsid..... 000A0004 00000003
vmt_vfsnumber 00000005 vfs_date..... 4357D873 vfs_flag..... 00000004
vmt_gfstype.. 00000003 @vmt_data.... 30A10224 vfs_lock..... 00000000
vfs_lock@.... 30A07868 vfs_type..... 00000003 vfs_ops...... jfs_vfsops
(因篇幅所限,只截取 vfs 对象内容)
我们可以看到 vfs_next 所指的内容正是用 vfs 命令列出来的第二个 vfs 对象的地址。
vfs_gfs 指向的 gfs 对象地址也都是一个地址。vfs_type 为 3 正是 vmount.h 中定
义的 MNT_JFS,jfs 文件系统。这时可以更明确的看到 vfs_ops 与 jfs_vfsops 的对应
关系,我们可以再来看下一个 vfs 对象。输入 vfs 30A078A4
(0)> vfs 30A078A4
GFS MNTD MNTDOVER VNODES DATA TYPE FLAGS
0 30A078A4 001C9F30 13FDF9F8 1354B2F8 137D2968 30A11BC0 JFS DEVMOUNT
... /dev/hd2 mounted over /usr
vfs_next..... 30A07870 vfs_gfs...... 001C9F30 vfs_mntd..... 13FDF9F8
vfs_mntdover. 1354B2F8 vfs_vnodes... 137D2968 vfs_count.... 00000001
vfs_number... 00000006 vfs_bsize.... 00001000 vfs_mdata.... 30A10380
vmt_revision. 00000001 vmt_length... 00000070 vfs_fsid..... 000A0005 00000003
vmt_vfsnumber 00000006 vfs_date..... 4357D874 vfs_flag..... 00000004
vmt_gfstype.. 00000003 @vmt_data.... 30A103A4 vfs_lock..... 00000000
vfs_lock@.... 30A078D0 vfs_type..... 00000003 vfs_ops...... jfs_vfsops
可以看到 vfs->vfs_gfs,vfs->vfs_type, vfs->jfs_vfsops 值和上面所显示的是一样
的。我们一直按照 vfs->vfs_next 所显示的地址输入,直到 vfs->vfs_next 为 0。这样
即遍历了所有 vfs 对象。
(0)> vfs 30A078D8
GFS MNTD MNTDOVER VNODES DATA TYPE FLAGS
0 30A078D8 001C9F30 13A01388 1354D788 13D0B678 30A11880 JFS DEVMOUNT
... /dev/hd1 mounted over /home
vfs_next..... 00000000 vfs_gfs...... 001C9F30 vfs_mntd..... 13A01388
vfs_mntdover. 1354D788 vfs_vnodes... 13D0B678 vfs_count.... 00000001
vfs_number... 00000009 vfs_bsize.... 00001000 vfs_mdata.... 30A10700
vmt_revision. 00000001 vmt_length... 00000070 vfs_fsid..... 000A0008 00000003
vmt_vfsnumber 00000009 vfs_date..... 4357D898 vfs_flag..... 00000004
vmt_gfstype.. 00000003 @vmt_data.... 30A10724 vfs_lock..... 00000000
vfs_lock@.... 30A07904 vfs_type..... 00000003 vfs_ops...... jfs_vfsops
上面的 vfs_next 值为0,表示这是系统中最后一个 vfs 对象。这时再来看一下 gfs
对象,通过观察 gfs 对象在每个 vfs 对象 vfs->vfs_gfs 中的值,它们所指的地址是
一样的,我们通过 gfs 001C9F30命令来查看 gfs 对象。
(0)> gfs 001C9F30
gfs_data. 00000000 gfs_flag. INIT VERSION4 VERSION42 VERSION421 VERSION43
gfs_ops.. jfs_vfsops gn_ops... jfs_vops gfs_name. jfs
gfs_init. jfs_init gfs_rinit jfs_rootinit gfs_type. JFS
gfs_hold. 00000005
我们可以看到,gfs 结构中每项都已对应到了下层实际文件系统 jfs 中的各项。上面
看到的 vfs 对象正是通过 vfs->vfs_gfs 连接到 gfs 对象中得到的 jfs 各项。
如 vfs_ops 与 jfs_vfsops 的映射关系,正是通过 gfs->gfs_ops 来连接的。这也就
证明了文章开始提到的 vfs->gfs<-jfs 那种通过 gfs 对象建立起来的对应关系。再看
来下一个 gfs 对象,通过 gfs 001C9F30 + 30 获得。
(0)> gfs 001C9F30 + 30
gfs_data. 00000000 gfs_flag. INIT VERSION4 VERSION42 VERSION421 VERSION43
gfs_ops.. spec_vfsops gn_ops... spec_vnops gfs_name. sfs
gfs_init. spec_init gfs_rinit nodev gfs_type. SFS
gfs_hold. 00000000
在第二个 gfs 对象中可以看到下层实际文件系统为 sfs。我们再向下找 gfs 对象。
(0)> gfs 001C9F30 + 60
gfs_data. 00000000 gfs_flag.
gfs_ops.. 00000000 gn_ops... 00000000 gfs_name.
gfs_init. 00000000 gfs_rinit 00000000 gfs_type. AIX
gfs_hold. 00000000
当 gfs 各项为 0 时则表明已经没有下层实际文件系统存在。
(0)> q
退出 kdb。
介绍完 vfs/gfs 对象与下层实际文件系统关系后,再来看看 vfs/gfs 对象中与文件
系统相关函数。在上面介绍 gfs 对象时提到的struct vfsops 结构是 vfs 用来对应下
层实际文件系统与文件系统相关操作函数的,这样上层系统调用操作与文件系统相关函
数时,只需要调用 vfs->vfsops 里指定的函数,而不必关心具体的文件系统。vfsops
结构如下:
struct vfsops {
int (*vfs_mount)(vfs_t *, vnode_t *, struct mounta *, cred_t *);
int (*vfs_unmount)(vfs_t *, int, cred_t *);
int (*vfs_root)(vfs_t *, vnode_t **);
int (*vfs_statvfs)(vfs_t *, statvfs64_t *);
int (*vfs_sync)(vfs_t *, short, cred_t *);
int (*vfs_vget)(vfs_t *, vnode_t **, fid_t *);
int (*vfs_mountroot)(vfs_t *, enum whymountroot);
int (*vfs_freevfs)(vfs_t *);
int (*vfs_vnstate)(vfs_t *, vnode_t *, vntrans_t);
};
为了更好的说明 vfsops 结构中的函数作用,我们举一个实际的例子。如当有一个实际文
件系安装时,如 JFS,系统调用 mount() 首先分配一个新的 vfs 结构来对应 JFS 文件
系统,上面讲到一个下层实际文件系统驱动是如何与 gfs 对应起来,那么一个新分配的
vfs又是如何找到相关 gfs 并关联下层实际文件系统的相关函数呢?上面提过要用到 vfs
结构中的 vfs->vfs_gfs,那么它又是被谁赋值的呢?这个值是由系统调用 mount() 的
vmount 参数所提供的,vmount 中的 vmt_gfstype 其实就是在 gfs 数组中的一个索引,
用来对应某个 vfs 结构在 gfs 中的位置。如 vfs->vfs_gfs = gfsindex[vmount->vmt_
gfstype] 这样便定位到了相应的 gfs, 以便来初始化新vfs 中的虚拟文件系统相关函数
与 JFS 文件系统相关函数连接,从上面的 vfs 结构中可以看到,每个 vfs 结构中并不
是直接的存有虚拟文件系统相关函数,如 vfs_mount() 是通过 vfs->vfs_gfs 定位到相
关的 gfs 结构,在gfs 结构中由 struct vfsops gfs_ops 所指定的。这样来完成 vfs->
vfs_gfs->gfs_ops->vfs_mount 与 jfs_mount 关联。设置好新 vfs 的相关项后,接着调
用 lookupname() 来得到安装目录的 vnode,(我们知道当 mount 一个文件系统时,需
要一个目录做为安装节点)。并将返回的目录 vnode 保存到vfs->vfs_mntdover 项中,
并调用 vn_access() 检查是否对此目录vnode 有访问权限。最后调用 vfs->vfs_gfs结构
中 gfs_ops 所指向的 vfs_mount() 函数,在 vfs_mount() 函数开始首先调用 vfs_root
() 得到根接点,再向下调用 jfs_mount() 函数来完成实际的安装工作。当调用返回成功
时,会将其 vfs 加到系统 rootvfs 所指的vfs链中并设置 vfs->vfs_flag 标志通知系统
已经安装成功。再如 NFS 文件系统安装时流程如上,只不过 vfs_mount() 调用 的是 nf
s_mount() 来完成安装工作。卸载一个文件系统 umount() 系统调用的流程基本上是moun
t() 系统调用的一个反向过程,它首先从 rootvfs 链头开始遍历,通过比较 vfsp->vfs_
number 找到需要卸栽的 vfs 并设置 vfsp->vfs_flag 为 VFS_UNMOUNTING。接着调用 vn
_access() 判断 vfs->vfs_mntdover 确保对安装文件系统的目录有访问权限。如果可以
访问,则设置 vfs->vfs_mntdover->v_mvfsp 为 NULL,以免多个进程来卸载。最后调用
vfs_umount() -> jfs_umount() 来完成卸栽。卸栽完成后设置 vfs->vfs_flag 为 VFS_
UNMOUNTED 并检查 vfsp->vfs_count 是否为 0,如果为 0 则将此 vfs 从 rootvfs 链中
删掉,并释放掉 vfs 中所有资源。
本文在编写过程中不断修改调整,所以显的有些凌乱。发出来是为了抛砖引玉,好让
熟悉 AIX 内核的高手们多发表些文章。我也好从中学习。
参考资源:
Kernel Extensions and Device Support Programming Concepts
http://publib.boulder.ibm.com/infocenter/pseries/topic/com.ibm.aix.doc/aixprggd/kernextc/kernextc.pdf
KDB Kernel debugger and kdb command
http://publib.boulder.ibm.com/infocenter/pseries/topic/com.ibm.aix.doc/aixprggd/kdb/kdb.pdf
感谢 lgx 与我探讨。
WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。
WSS 主页:http://www.whitecell.org/
WSS 论坛:http://www.whitecell.org/forums/