WEB开发网
开发学院操作系统Linux/Unix 开发 AIX 文件系统 阅读

开发 AIX 文件系统

 2008-11-10 08:28:39 来源:WEB开发网   
核心提示:引言AIX 5L™ 是一种优秀的操作系统,它提供了出众的可伸缩性、可靠性和可管理性,开发 AIX 文件系统,它是市场上一些功能最强大的 IBM UNIX® 服务器的缺省操作系统,通常,这样做能够简化所需的工作,设计优良的文件系统应该是很容易移植的,可以将文件系统 定义为一种软件,它负责对物理存储介质

引言

AIX 5L™ 是一种优秀的操作系统,它提供了出众的可伸缩性、可靠性和可管理性。它是市场上一些功能最强大的 IBM UNIX® 服务器的缺省操作系统。

通常,可以将文件系统 定义为一种软件,它负责对物理存储介质中的数据进行存储、组织和检索,物理存储介质包括硬盘驱动器、CD-ROM 或者任何其他存储设备。从本质上说,用于进行这种数据组织的代码应该是可移植的。在现实世界中,尽管每个操作系统都提供了它自己的接口,以此请求特定的文件系统操作,并且期待软件基础部分以该操作系统所希望的格式返回结果。对于不同的操作系统,其接口也各不相同,并且需要由具体的文件系统提供,以使得特定的操作系统对该文件系统提供支持。

在本文中,您将了解 AIX® 操作系统文件系统框架。您还将获得有关 IO 层的概述,以及对一些重要概念的解释。本文还对在开发新的文件系统或者将现有的文件系统移植到 AIX 操作系统时所使用的接口和方法进行了简单的说明。

与许多 UNIX 版本一样,AIX 将文件系统作为一种内核扩展。本文假定您对 UNIX 编程和文件系统的概念有基本的了解。如果您还了解如何为 AIX 编写内核扩展,那将是很有帮助的。

了解逻辑文件系统和虚拟文件系统

逻辑文件系统层是一个抽象层,用户可以通过它请求各种文件操作,如读、写、获得相关信息,等等。逻辑文件系统接口支持 UNIX 类型的文件访问语义。逻辑文件系统层是虚拟文件系统的超集,而后者封装了各种不同的文件系统,这些文件系统为内核提供了基础目录树的一致的视图。逻辑文件系统还负责管理内核的打开文件表和每个进程的文件描述符信息。

虚拟文件系统是基础物理文件系统的抽象。虚拟文件系统提供了一组应该支持的标准接口,以使得您的文件系统可用于 AIX 操作系统。虚拟文件系统建立了不同的基础物理文件系统到逻辑文件系统之间的桥梁,为操作系统中的其他部分提供了一致的目录树层次结构。

文件系统对象的每个唯一的装入实例通过一个虚拟文件系统结构进行表示。虚拟文件系统可以为物理文件系统、网络文件系统、或者逻辑文件系统(没有物理后备存储,如 ramfs)。图 1 显示了 AIX 文件系统的层次结构。

图 1. AIX 文件系统层次结构

开发 AIX 文件系统

如清单 1 所示,由成员 vfs_next 指定,虚拟文件系统作为 struct vfs 的链表进行维护。

清单 1. 虚拟文件系统结构

<sys/vfs.h>
struct vfs {
 struct vfs   *vfs_next;   
 struct gfs   *vfs_gfs;   
 struct vnode  *vfs_mntd;   
                    
 struct vnode  *vfs_mntdover; 
                    
 struct vnode  *vfs_vnodes;  
 int       vfs_count;   
 caddr_t     vfs_data;   
 unsigned int  vfs_number;  
 int       vfs_bsize;   
#ifdef _SUN
 short      vfs_exflags;  
 unsigned short vfs_exroot;  
#else
 short      vfs_rsvd1;   
 unsigned short vfs_rsvd2;   
#endif /* _SUN */
 struct vmount  *vfs_mdata;  
 Simple_lock   vfs_lock;   
};

列表中的每个条目代表一个装入的文件系统对象。

vfs_mntd vfs_mntd 表示这个文件系统在进行装入时的装入点 vnode。对于 '/' 根文件系统,这将是根的 vnode。 vfs_vnodes vfs_vnodes 是这个装入实例的所有 vnodes 组成的链表。 vfs_lock 您可以使用 vfs_lock 串行化对 vnode 的访问。 vfs_gfs vfs_gfs 指向对应的文件系统的 struct gfs 结构。

struct gfs 包含独立于这些装入实例的文件系统相关信息。它包含文件系统布局的所有常见特征,如下面的清单 2 所示。操作系统中注册的每个文件系统有且仅有一个 struct gfs、一个或多个 struct vfs,后者分别对应于每个装入实例。gfs_ops 和 gn_ops 是 struct gfs 中的重要成员,它们表示该文件系统的虚拟文件系统操作和 vnode 操作。您应该为该文件系统提供虚拟文件系统操作和 vnode 操作,以便能够得到 AIX 操作系统的支持。

清单 2. gfs 结构

<sys/vfs.h>
struct gfs {
 struct vfsops  *gfs_ops;
 struct vnodeops *gn_ops;
 int       gfs_type;   
 char      gfs_name[16]; 
 int       (*gfs_init)(struct gfs *); 
 int       gfs_flags;   
 caddr_t     gfs_data;   
 int       (*gfs_rinit)(void);
 int       gfs_hold;   
};

vnode、gnode 和 inode

vnode 表示文件系统中打开的对象。每次试图创建或者打开一个文件时,将为该文件创建一个 vnode 对象。如果一个 vnode 已经存在,那么引用计数将递增。vnode 在虚拟文件系统中处于活动状态,直到最后的持有者离开,从而使得其引用计数变为 0。vnode 的主要任务是将路径名转换为基础文件系统中的一个物理对象。一个对象可以拥有多个路径名,如果:

文件系统使用不同的装入点进行多次装入。

存在到该对象的软链接或硬链接。

然而,试图打开匿名的路径不会创建多个 vnodes。一个 gnode 可以由多个 vnodes 引用,仅当文件系统存在多个装入实例时。对于任何给定的实例,至少有一个 vnode 引用 gnode。清单 3 显示了 vnode 结构。

清单 3. vnode 结构

<sys/vnode.h>
struct vnode {
 ushort     v_flag;    
 ushort     v_flag2;    
 ulong32int64_t v_count;
 int      v_vfsgen;   
 Simple_lock  v_lock;  
 struct vfs   *v_vfsp;  
 struct vfs   *v_mvfsp;  
               
 struct gnode  *v_gnode; 
 struct vnode  *v_next; 
 struct vnode  *v_vfsnext;
 struct vnode  *v_vfsprev;
 union v_data {
  void     * _v_socket;   
  struct vnode * _v_pfsvnode;  
 } _v_data;
 char      *v_audit;    
};

v_vfsp v_vfsp 表示包含的 vfs 对象。如果这个 vnode 是另一个不同的文件系统的装入点,那么 v_mvfsp 中保存了包含的文件系统的 struct vfs。 v_gnode v_gnode 指回到该对象的 gnode。文件系统中的每个物理对象都使用唯一的 gnode 进行表示。与 vnode 不同,每个对象仅有一个 gnode,无论有多少个文件系统装入实例。在 gnode 和磁盘上的文件之间,存在一一对应的关系。

每个装入实例中的打开对象都使用唯一的 vnode 进行表示。因此,可以认为 vnode 包含打开的、实例特定的信息,而 gnode 封装了对象本身。

清单 4. gnode 结构

<sys/vnode.h>
struct gnode {
 enum vtype   gn_type;      
 short      gn_flags;       
 vmid_t     gn_seg;        
 long32int64_t  gn_mwrcnt;   
 long32int64_t  gn_mrdcnt;  
 long32int64_t  gn_rdcnt;  
 long32int64_t  gn_wrcnt;  
 long32int64_t  gn_excnt;  
 long32int64_t  gn_rshcnt;  
 struct vnodeops *gn_ops;
 struct vnode  *gn_vnode;
 dev_t      gn_rdev;   
 chan_t     gn_chan;   
 Simple_lock   gn_reclk_lock; 
 int       gn_reclk_event;
 struct filock  *gn_filocks;  
 caddr_t     gn_data;   
};

下面是 struct gnode 中的一些重要的成员:

gn_vnode gn_vnode 指向引用这个 gnode 的 vnodes 所组成的列表。 gn_ops gn_ops 是标准的 vnode 操作,您应该为您的文件系统注册这些标准操作。 gn_data gn_data 指向该对象的文件系统特定信息,通常是 inode。

inode 对象是一种文件系统数据结构,它用于表示与该文件相关联的信息,仅适用于该文件系统。在文件系统中,每个 gnode 都有一个与其相关联的 inode。gnode 存储了该文件的通用字符,而 inode 则用于存储文件系统特定的信息。inode 封装在 gnode 的 gn_data 成员变量中。

AIX 并没有提供任何接口用于设计 inode。由您来设计自己的 inode 并在每种设计中对其进行解释。通常,inode 提供了各种信息,如所有权(组和用户)、访问权限、创建或修改时间,等等。

虚拟文件系统操作

AIX vnode 和虚拟文件系统框架提供了经过良好定义的接口,称为虚拟文件系统和 vnode 操作。虚拟文件系统操作用于支持文件系统管理操作,如装入和卸载文件系统,在卸载操作的过程中同步文件系统及其后备存储,以及查询文件系统提供的各种辅助信息。清单 5 显示了装入和卸载操作。

所有成功完成的虚拟文件系统操作都返回 0。如果出现了故障,那么将返回 /usr/include/sy/errno.h 中定义的错误编号。

清单 5. 装入和卸载操作

int (*vfs_mount)(struct vfs *, struct ucred *);
int (*vfs_unmount)(struct vfs *, int, struct ucred *);

vfs_mount 逻辑文件系统层调用这个接口以便对请求的文件系统进行装入。将经过初始化的 struct vfs 传递给它,并由该例程填充并返回这个结构。对于成功的装入,vfs_mount 接口返回 0,对于故障,返回一个错误。 vfs_umount 可以调用这个例程来卸载文件系统,在显式地调用 umount 命令或在操作系统关闭时将调用该例程。

如下面的清单 6 所示,可以调用 vfs_sync 例程,以便对内存中的文件系统数据与其后备存储进行同步。

清单 6. 同步操作

#if defined(__64BIT_KERNEL) || defined(__FULL_PROTO)
 int (*vfs_sync)(struct gfs *);
#else
 int (*vfs_sync)();
#endif
int (*vfs_syncvfs)(struct gfs *, struct vfs *, int, struct ucred *);

vfs_sync 与在其他的虚拟文件系统操作中相同,将描述文件系统类型的 struct gfs 而不是 struct vfs 传递给这个接口。对每种文件系统类型而不是每个 vfs 实例调用一次 sync 例程。 vfs_syncfs vfs_syncfs 用于同步特定的装入实例,这与 vfs_sync 不同,后者分别为特定虚拟文件系统类型的所有装入实例调用一次。可以使用一些控制同步操作的选项来调用 vfs_syncfs。

清单 7. 配额和访问控制列表管理

int (*vfs_quotactl)(struct vfs *, int, uid_t, caddr_t, struct ucred *);
int (*vfs_aclxcntl)(struct vfs *, struct vnode *, int, struct uio *, size_t *,
          struct ucred *);

vfs_quotactl 逻辑文件系统层调用 vfs_quotactl 接口以执行文件系统中与配额相关的控制操作。 vfs_aclxcntl 可以调用 vfs_aclxcntl 以执行各种访问控制列表 (ACL),以及对文件系统执行特定的控制操作。如果文件系统支持配额和 ACL,那么它需要遵循为 AIX 操作系统定义的框架。

对于成功的操作,这些例程将返回 0。如果基础文件系统不支持某些例程,那么这些例程应该返回 EINVAL。

清单 8. 其他控制操作

int (*vfs_root)(struct vfs *, struct vnode **, struct ucred *);
int (*vfs_statfs)(struct vfs *, struct statfs *, struct ucred *);
int (*vfs_vget)(struct vfs *, struct vnode **, struct fileid *, struct ucred *);
int (*vfs_cntl)(struct vfs *, int, caddr_t, size_t, struct ucred *);  

vfs_root vfs_root 用于获得文件系统的 vnode 根指针。这个例程在 struct vnode ** 中返回 vnode 根。 vfs_statfs vfs_statfs 用于获得文件系统的特性。如果成功完成,适用的文件系统特性将填充到 struct statfs 中。表 1 描述了各种文件系统特性。

表 1. 文件系统特性

f_blocks指定块的数目
f_files指定文件系统对象的总数
f_bsize指定文件系统块大小
f_bfree指定空闲块的数目
f_ffree指定空闲文件系统对象的数目
f_fname指定一个 32 字节的字符串,用于表示该文件系统的名称
f_fpack指定一个 32 字节的字符串,用于表示包 ID
f_name_max指定对象名的最大长度
vfs_vget vfs_vget 用于获得文件系统中由虚拟文件系统和 fileid 所标识的对象的 vnode。在调用 vn_fid vnode 操作的过程中,将创建 fileid。对于相应的对象,如果虚拟文件系统中存在一个 vnode,那么会将其引用计数递增,并返回这个 vnode。否则,通过 vn_get 内核服务创建一个引用该对象的新的 vnode,并将引用计数设置为 1,然后返回它。任何给定实例的 fileid 参数用于唯一地标识文件系统中的一个对象。 vfs_cntl vfs_cntl 用于实现各种各样的用户指定的控制操作。逻辑文件系统使用控制 ID 和所需的参数调用 vfs_cntl。由 fscntl 子例程调用 vfs_cntl。用户最多可以指定 32768 种控制操作。

vnode 操作

与虚拟文件系统操作类似,AIX 文件系统框架提供了一组接口,以执行各种文件系统操作,如读、写、获得相关信息,等等。这些接口都是 vnode 操作。AIX Version 5.3 内核一共提供了 56 种接口以进行各种文件系统操作。并不需要支持所有的这些接口,这取决于您的文件系统驱动程序的具体目标。所提供的某些扩展仅用于提供对 AIX 操作系统较早版本的向后兼容性,而其他的扩展为调出接口,用于帮助虚拟内存管理程序进行分页操作。其余的例程为各种 UNIX 文件访问语义提供了重要的支持。

清单 9. 创建、命名和删除操作

int (*vn_link)(struct vnode *dvp, struct vnode *vp, char *name, struct ucred *cred);
int (*vn_mkdir)(struct vnode *dvp, char *name, int32long64_t mode, struct ucred *cred);
int (*vn_mknod)(struct vnode *dvp, caddr_t name, int32long64_t mode, dev_t dev,
        struct ucred *cred);
        
int (*vn_remove)(struct vnode *vp, struct vnode *dvp, char *name, struct ucred *cred);
int (*vn_rename)(struct vnode *srcVp, struct vnode *srcDvp, caddr_t oldName,
         struct vnode *destVp, struct vnode *destDvp, caddr_t newName,
         struct ucred *cred);
        
int (*vn_rmdir)(struct vnode *vp, struct vnode *dvp, char *name, struct ucred *cred);

这些例程用于创建、命名和删除基础文件系统中的对象。对于所有的这些例程,至少需要传递要对其子对象执行操作的父目录的 vnode。逻辑文件系统层将确保 vnode 父目录不处于一个只读的文件系统中。对应的入口点为:

vn_link 可用调用 vn_link 创建一个新的、到现有的对象的硬链接,这是链接子例程的任务的一部分。这个例程在 dvp 目录中为 vnode vp 创建一个名为 name 的硬链接。逻辑文件系统将确保 dvp 和 vp 参数所引用的对象位于同一个虚拟文件系统中。 vn_mkdir vn_mkdir 用于在 vnode dvp 所指定的目录中创建一个新的命名目录。目录模式权限作为 int32long64_t 变量传递,新目录的名称则在 name 参数中传递。 vn_mknod vn_mknod 用于在 dvp 目录中创建一个名为 name 的新文件。mode 中包含文件的类型(常规、特殊文件)以及文件访问权限(位掩码的组合)。对于特殊文件(如设备文件),dev 参数包含了设备编号。 vn_remove 由逻辑文件系统层以取消链接的子例程的名义调用 vn_remove。这个接口用于删除 dvp 目录中的一项目录条目或者由 vnode vp 指定的链接。用户需要调用 vn_rele 函数以释放对 vnode 的引用。如果这是对该对象的最后一项引用,那么将释放这个文件所占的物理磁盘资源。 vn_rename 由逻辑文件系统调用 vn_rename,以便对文件或目录进行重命名。vnode srvVp 引用了 srcDvp 目录中的源对象 (srcName)。由 newName 参数指定新的名称,并由 destDvp 指定新的目标目录。如果在目标路径中已经存在一个具有相同名称的对象,那么将在 destVp 中传递这个对象的 vnode。 vn_rmdir vn_rmdir 用于删除目录 dvp 中由 vnode vp 指定的目录条目。逻辑文件系统将确保要删除的 vnode 是一个目录,并且不是当前目录或者根目录。要删除一个目录,该目录应该为空,并且不应该包含任何子对象。

清单 10. 查找和文件句柄操作

int (*vn_lookup)(struct vnode *dvp, struct vnode **vpp, char *name,
         int32long64_t vflag, struct vattr *attr, struct ucred *cred);
        
int (*vn_fid)(struct vnode *vp, struct fileid *fidp, struct ucred *cred);

vn_lookup 逻辑文件系统使用这个入口点查找 dvp 目录中名为 name 的文件。如果找到,在 vpp 中返回该文件的 vnode。

每次对文件的查找都会使得引用计数递增。所以,您应该确保在 lookup 操作成功完成之后执行关闭操作,以此对引用计数进行递减。如果 attr 字段不为 null,那么该例程还应该在 attr 参数中返回文件的各种属性。

vn_fid 可以调用 vn_fid 为 vnode vp 构建文件标识符。vfs_vget 例程使用这个文件标识符以获得与所构造的文件标识符表示相同对象的 vnode。因此,这个文件标识符应该包含足够的信息以便成功地标识合适的对象。

清单 11. 文件访问操作

int (*vn_open)(struct vnode *vp, int32long64_t flag, ext_t dev, caddr_t * vinfo,
        struct ucred *cred);
       
int (*vn_create)(struct vnode *dvp, struct vnode **vpp, int32long64_t flags,
         caddr_t name, int32long64_t mode, caddr_t *vinfo,
         struct ucred *cred);
        
int (*vn_hold)(struct vnode *vp);
int (*vn_rele)(struct vnode *vp);
int (*vn_close)(struct vnode *vp, int32long64_t flag, caddr_t vinfo,
        struct ucred *cred);
        
int (*vn_map)(struct vnode *vp, caddr_t addr, uint32long64_t length,
       uint32long64_t offset, uint32long64_t flags, struct ucred *cred);
       
int (*vn_unmap)(struct vnode *vp, int32long64_t flag, struct ucred *cred);

vn_open 这个入口点使用 flag 参数中指定的文件打开参数打开文件。通过 vp 参数传递要打开的文件的 vnode。

通常,在执行文件查找操作或文件创建操作(如果使用 O_CREAT 标志调用打开操作)时创建 vnode。由您来决定在 vn_open 调用的过程中需要进行哪些操作。成功的打开操作将导致引用计数的递增。在打开设备文件时,dev 参数中包含了设备特定的信息。

vn_create 可以调用这个例程以便在 dvp 目录中创建一个名为 name 的常规文件。在 mode 参数中传递新的文件的访问模式。flag 参数包含用于随后打开调用的打开标志选项。对于成功的创建操作,将在虚拟文件系统中创建一个新的 vnode 条目,并将其引用计数设置为 1,并在 vpp 参数中返回刚创建的 vnode。 vn_hold / vn_rele vn_hold 例程用于增加 vnode 的引用计数。这样做是为了确保不会在调用者不知情的情况下意外地删除该 vnode。在调用 vn_hold 的时候,应该在完成了相应的任务之后立即调用 vn_rele,以确保对引用计数进行递减。 vn_close vn_close 是由关闭子例程调用以便关闭 vnode vp 的入口点。仅在对 vnode 的最后一项引用释放的时候,才会调用这个例程。一旦对 vnode 调用了 vn_close,那么将无法对 vnode 执行任何进一步的操作。 vn_map vn_map 是用于对文件映射请求进行验证的例程,这些文件映射请求来自于对 vnode vp 所引用的对象进行的 mmap 调用或者 shmat 调用。addr 参数指定了要将对象映射到请求进程地址空间的什么地址。length、offset 和 flags 分别定义了映射的长度、从文件中的什么偏移处开始进行映射、定义映射类型的位掩码。

在正常情况下,基础文件系统将存储要进行映射的文件的 gnode 的 gn_seg 字段。如果这个虚拟内存对象不存在,那么逻辑文件系统层将创建该对象,并对其计数进行递增。

vn_unmap 可以调用 vn_unmap 取消已经映射到进程地址空间文件的映射。vnode vp 指定了需要取消映射的目标文件。flag 参数指定了定义映射类型的标志位掩码。

要对 vn_map 和 vn_unmap 调用仅执行文件系统特定的操作,那么需要提供文件系统的实现。逻辑文件系统层负责处理虚拟内存操作。

清单 12. 属性操作

int (*vn_access)(struct vnode *vp, int32long64_t mode, int32long64_t who,
         struct ucred *cred);
        
int (*vn_getattr)(struct vnode *vp, struct vattr *attr, struct ucred *cred);
int (*vn_setattr)(struct vnode *vp, int32long64_t cmd, int32long64_t arg1,
         int32long64_t arg2, int32long64_t arg3, struct ucred *cred);

vn_access vn_access 是逻辑卷文件系统用来验证对 vnode vp 的访问的入口点。这个入口点用于实现访问子例程,并对权限进行检查,如读、写、执行,等等。

who 参数指定了需要对哪些用户进行检查工作。mode 参数指定了需要执行的检查类型。

vn_getattr vn_getattr 是用于获得给定 vnode 的各种文件属性(在 vattr 结构中指定)的入口点。这个入口点支持 stat、fstat 和 lsta 子例程。 vn_setattr vn_setattr 是用于设置 vnode vp 的各种文件属性(在 vattr 结构中指定)的入口点。arg1、arg2 和 arg3 参数的值依赖于 cmd 参数。

表 2 指定了这些参数命令值的解释。这个入口点支持 chmod、chownx 和 utime 子例程。

表 2. 可能用于 vn_setattr 的命令值

CommandV_OWNV_UTIMEV_MODE
arg 1int fag;int flag;int mode;
arg 2int uid;timestruct_t *atime;Unused
arg 3int gid;timestruct_t *mtime;Unused

清单 13. 数据更新操作

int (*vn_fclear)(struct vnode *vp, int32long64_t flags, offset_t offset, offset_t len,
         caddr_t vinfo, struct ucred *cred cred);
        
int (*vn_fsync)(struct vnode *vp, int32long64_t flags, int32long64_t fd,
        struct ucred *cred);
        
int (*vn_ftrunc)(struct vnode *vp, int32long64_t flags, offset_t length, caddr_t vinfo,
         struct ucred *cred);
        
int (*vn_rdwr)(struct vnode *vp, enum uio_rw op, int32long64_t flag,
        struct uio *uiop, caddr_t dev, struct vattr *attr,
        struct ucred *cred);
       
int (*vn_lockctl)(struct vnode *vp, offset_t offset, struct eflock *lckdata,
         int32long64_t cmd, int (*retry_fcn)(), ulong *retry_id,
         struct ucred *cred); 

vn_fclear vn_fclear 是用于清除部分文件内容并将所有的空闲块归还给基础文件系统的入口点。offset 参数定义了开始进行清除工作的偏移位置。len 参数指定了需要清除的字节数。在 flag 参数中传递打开文件时所使用的标志。逻辑文件系统将更新该文件的大小,以反映清除了若干字节的操作。 vn_fsync vn_fsync 例程用于请求文件系统,对于给定的 vnode vp,将所有修改过的数据写回到后备存储。这个调用必须以同步的方式完成,以便调用者能够确保所有的 I/O 都已经成功地完成。flag 参数指定了各种同步选项。fcntl.h Header 文件中提供了各种不同的同步选项。 vn_ftrunc vn_ftrunc 是用于截断由 vnode vp 所指定的文件的入口点。length 参数表示截断操作完成后文件的大小。如果新的长度小于以前的长度,那么将删除两者之间的数据。如果新的长度大于现有的长度,那么将添加若干个 0 以增加该文件的大小。

当截断操作完成时,所有的块都归还给文件系统,并对文件大小进行更新。如果要进行截断的部分被锁定了,那么该操作将失败。

vn_rdwr vn_rdwr 是受到最广泛支持的 vnode 操作中的一种。vn_rdwr 用于对 vnode vp 所指定的文件执行文件 IO 操作。op 参数表示该请求是读操作 (UIO_READ) 还是写操作 (UIO_WRITE)。uio 参数包含用户 IO 数据结构,这个数据结构描述了数据传输中将使用的内存缓冲区。如果将 IO 定向到特殊文件,dev 参数则包含了设备特定的信息。如果 attr 参数不为 NULL,那么应该在 attr 参数中传递回该文件的各种属性。 vn_lockctl vn_lockctl 是用于规定由 vnode vp 所指定的文件的基于记录的锁定的入口点。struct eflock (lckdata) 定义了完成记录锁定所需的锁定信息。cmd 参数定义了要执行的锁定操作的类型。

可以在 retry_fcn 参数中传递可选的重试函数,如果不能立即授予锁,可以使用这个函数进行重试。retry_id 存储了将重试操作关联于一组特定锁的值。锁定实现调用操作系统提供的 common_reclock() 例程,后者将为最终用户隐藏锁定操作的复杂性。

清单 14. 扩展操作

int (*vn_ioctl)(struct vnode *vp, int32long64_t cmd, caddr_t arg, size_t flags,
        ext_t dev, struct ucred *cred);
        
int (*vn_readlink)(struct vnode *vp, struct uio *uio, struct ucred *cred);
int (*vn_select)(struct vnode *vp, int32long64_t corel_id, ushort req_event,
         ushort *ret_event, void (*notify)(), caddr_t vinfo,
         struct ucred *cred);
        
int (*vn_symlink)(struct vnode *dvp, char *link, char *target, struct ucred *cred);
int (*vn_readdir)(struct vnode *vp, struct uio *uio, struct ucred *cred);

vn_ioctl vn_ioctl 是逻辑文件系统用来对特殊文件执行各种各样的用户指定的 IO 控制操作的入口点。如果文件系统支持特殊文件,则将该信息传递给 vnode vp 所引用的既定的设备驱动程序。

cmd 参数标识了这个例程调用哪个 IOCTL (IO Control)。arg 参数中包含了所需的各种参数。

vn_readlink vn_readlink 是用于读取到 vnode vp 的符号链接的内容的入口点。逻辑文件系统负责根据链接定位 vnode。这个例程仅读取链接的数据块。 vn_select vn_select 入口点由逻辑文件系统进行调用,以便轮询 vnode vp 以确定它是否做好了进行 I/O 的准备。它用于实现选择和轮询子例程。文件系统实现可以支持诸如设备或管道这样支持选择语义的结构。

fp_select 内核服务提供了有关选择和轮询请求的更多信息。req_event 指定了要进行轮询的事件请求,而 notify 是在事件发生时将进行回调的回调例程。

vn_symlink vn_symlink 用于为一个对象创建符号链接,在 target 参数中指定了该对象的绝对路径。在 dvp 目录中创建名为 linkname 的新的链接。 vn_readdir vn_readdir 是用于读取由 vnode vp 所引用的目录中的目录条目的接口。在 uio 结构的 struct dirent 中返回这些条目。读操作从 struct uio 中的 uio_offset 成员所指定的地址处的第一项目录条目开始。

当 uio 缓冲区满时,将使用没有推入到当前缓冲区的目录条目的起始地址对 uio_offset 进行更新。使用读入到 uio 结构的字节数对 uiop->uio_resid 字段进行更新。当读入 uio 结构的内容为空时,结束读取操作。

清单 15. 缓冲区操作

int (*vn_strategy)(struct vnode *vp, struct buf *buf, struct ucred *cred);

vn_startegy vn_startegy 是负责从块设备读取数据的例程。这个入口点的目的是为服务器提供一种面向块的接口,以进行高效的分页。vnode vp 提供了需要进行块读取操作的文件信息。buf 参数包含描述缓冲区的 struct buf。

清单 16. 安全相关操作

int (*vn_revoke)(struct vnode *vp, int32long64_t cmd, int32long64_t flags,
         struct vattr *attr, struct ucred *cred);
        
int (*vn_getacl)(struct vnode *vp, struct uio *uio, struct ucred *cred);
int (*vn_setacl)(struct vnode *vp, struct uio *uio, struct ucred *cred);
int (*vn_getpcl)(struct vnode *vp, struct uio *uio, struct ucred *cred);
int (*vn_setpcl)(struct vnode *vp, struct uio *uio, struct ucred *cred);
int (*vn_seek)(struct vnode *vp, offset_t *offset, struct ucred *cred);

vn_revoke vn_revoke 是用于撤销由 vnode vp 指定的对象的所有访问权限的入口点。cmd 参数定义了调用进程是否打开了文件,它可能为下列值:

0—该进程没有打开文件。

1—该进程打开了文件。

2—该进程打开了文件,并且文件结构中的引用计数大于 1。

vn_getacl / vn_setacl vn_getacl / vn_setacl 是逻辑文件系统用来检索文件 ACL 以实现 getacl 子例程的入口点。vn_setacl 则用于设置文件的 ACL。这些例程为 chacl、chown、chmod 和 statacl 子例程提供了重要的支持。 vn_getpcl / vn_setpcl vn_getpcl / vn_setpcl 是逻辑文件系统用来检索文件的权限控制列表 (PCL) 以实现 getpcl 子例程的入口点。vn_setpcl 用于设置文件权限控制列表,并且支持 setpcl 子例程。 vn_seek vn_seek 是用于验证 seek 操作偏移位置的入口点。对于要进行 seek 操作验证的 vnode,在 vp 中进行传递,并在 offset 中传递偏移量。通常,如果偏移量大于 0 并且小于该文件的最大长度,那么这个例程将返回 EOK,否则将返回 EINVAL。

清单 17. 外部分页程序调出操作

int (*pagerBackRange)(struct gnode *gnp, offset_t offset, caddr_t dest,
           size_t *nBytesOfRange, size_t *nBytesBacked, uint *flags);
           
int64_t (*pagerGetFileSize)(struct gnode *gnp);
void (*pagerReadAhead)(struct gnode *gnp, vpn_t pFault, vpn_t * pFirst,
            vpn_t *nPage, vpn_t *pTripWire, boolean_t tripWire);
           
void (*pagerReadWriteBehind)(struct gnode *gnp, int64_t offset, int64_t length,
               uint flags);
              
void (*pagerEndCopy)(struct gnode *gnp, offset_t offset, size_t nBytesMoved,
           size_t nBytesBacked, uint flags);

AIX 提供了一些外部分页程序调出例程,在对特定文件系统中的文件分页入/出的时候,虚拟内存管理程序将与这些例程进行协商。

pagerBackRange 回调操作用于请求文件系统将核心页面中的内容写回到后备物理存储中。在调用了 pagerBackRange 之后,接着调用 pagerEndCopy 回调函数。 pagerEndCopy pagerEndCopy 用于进行复制操作完成后的处理工作。 pagerReadAhead,pagerReadWriteBehind pagerReadWriteBehind 回调可以方便虚拟内存管理程序与文件系统之间协商预读入和延迟写的策略。 所有的这些调出操作都是可选的。完全由您来决定需要哪些内容。

除了这些操作之外,vnodeops_t 提供了大量的接口,它们称为 421 扩展,为 AIX Version 4.2.1 提供了向后兼容性。

文件系统 Helper 和装入 Helper

为了支持多种文件系统,许多文件系统例程并不自行处理相关命令。相反,它们收集传递给命令的各种参数,然后将其发送给文件系统中特定的后端程序,由这些程序真正地执行命令的处理。这些后端程序称为文件系统 Helper 和装入 Helper;文件系统开发人员必须提供这些程序。

Helper 程序位于 /sbin/helpers/<vfs_type>,其中的 vfs_type 与调用该命令的文件系统类型相匹配。程序名必须与所执行的命令的名称相匹配。

mount 命令是每种文件系统提供的装入 Helper 例程的前端程序。用于装入和卸载命令的后端支持程序是装入 Helper。前端装入程序收集传递到装入程序的各种参数。然后,它查看 /etc/filesystems 文件以确定目标文件系统的虚拟文件系统类型。它使用收集到参数来调用 /sbin/helpers/<vfs_type>/mount 以处理该命令。/etc/filesystems 配置文件中文件系统的典型条目与清单 18 所示类似。

清单 18. /etc/filesystems 中的示例条目

/data:
  dev       = /dev/fslv00
  vfs       = jfs2
  log       = /dev/hd8
  mount      = true
  options     = rw

vfs 属性确定了文件系统 (<vfs_type>) 的虚拟文件系统类型。mount 属性确定了这个文件系统的缺省装入行为。它可以具有下列的值。

Automatic当系统启动时,自动地装入该文件系统。
False在缺省情况下,不装入该文件系统。
Readonly该文件系统以只读的方式装入。
True通过 mount all 命令装入该文件系统,并通过 unmount all 命令卸载该文件系统。
Nodenamemount 命令使用节点名来确定包含远程文件系统的节点。如果没有提供这个属性,则表示该装入对象是本地装入。

options 属性指定了要传递给后端装入处理程序的任何附加的选项。mount 和 unmount 命令有六个参数;前四个是公共的,而后两个则是与特定的命令相关的。

构建和配置文件系统

文件系统组件作为 AIX 中的内核扩展进行构建。由内核负责维护系统中注册的活动文件系统类型的列表。要使 AIX 操作系统支持您的文件系统,首先必须在 AIX 中进行注册。AIX 提供了两种内核服务,gfsadd 和 gfsdel,分别用于添加和删除文件系统。

每个文件系统都应该提供一个配置例程,以便调用它将内核扩展配置为文件系统。这个例程应该在 struct gfs 中填写文件系统信息,然后调用 gfsadd。gfsadd 内核服务使用 struct gfs 中的信息将其注册到全局文件系统表中,然后调用 gfs_init 中指定的初始化例程。这个初始化例程将完成剩下的文件系统初始化工作。

从用户的角度出发,要加载文件系统需要完成下面的工作:

提供一个用户程序或者脚本以调用 sysconfig 子例程来加载内核扩展。

再次调用 sysconfig 子例程,通过指定配置例程,将内核扩展配置为虚拟文件系统。

该配置例程调用 gfsadd 内核服务将内核扩展注册为 AIX 能够识别的文件系统。

在完成这些工作之后,该文件系统就可以正常运行了。

结束语

文件系统的开发是内核开发中最具挑战的工作之一。从本质上说,文件系统中用于进行数据管理的代码应该是可移植的、独立于平台的。每种操作系统都具有自己的 IO 框架和接口,可作为您的文件系统与内核之间的桥梁。要使您的文件系统能够运行于特定的操作系统,您应该深入彻底地了解这个框架。

您还应该对操作系统为各种支持操作(如内存分配、数据复制例程等)所提供的内核设计和接口有基本的了解。任何单个的库或者标准都无法定义这些支持例程。作为一般规则,为常见的支持例程设计通用接口,并且在平台特定的文件中为每种平台定义这些操作,这样做能够简化所需的工作。设计优良的文件系统应该是很容易移植的,至少在各种 UNIX 版本之间。

Tags:开发 AIX 文件

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接