bluehacker

4.4bsd中的虚拟文件系统

0
阅读(2842)

这个51假期快完了,我一点精神没有,想想活着究竟为什么?这个话题太大,其实我也不相信从小到大接受的教育说的什么为国家为社会做贡献,其实我一直信奉R.Feynman的哲学:"我不必为这个社会负任何责任".想想也许我的生命并没有什么意义, 至少对于我自己没有什么意义,我经常觉得自己的生命对自己没有意义,生死本没什么差别,可能唯一的差别就是临死的一些恐惧和心中一些感情放不下吧,很可能我的生命只对别人有意义,对爱我的人和我爱的人有意义.心空空荡荡的过了个5.1,既然无聊,就做了件无聊的事情,看了下4.4bsd的代码,写几个帖子无聊一下:

从4.4bsd开始,bsd支持vfs(虚拟文件系统),这样的好处是方便的在一个系统上支持多个不同的文件系统。这一思想现在成功的应用于linux和freebsd等操作系统中。vfs最初是sun microsystem在其SunOS操作系统上实现的,sun总是会有些创新的点子确实很有特色,很有远见。象slab,nfs就是sun发明的。
vfs中关键的数据结构是vnode,在4.4bsd中定义如下:
struct vnode {
u_long v_flag; /* vnode flags (see below) */
short v_usecount; /* reference count of users */
short v_writecount; /* reference count of writers */
long v_holdcnt; /* page & buffer references */
daddr_t v_lastr; /* last read (read-ahead) */
u_long v_id; /* capability identifier */
struct mount *v_mount; /* ptr to vfs we are in */
int  (**v_op)(); /* vnode operations vector */
TAILQ_ENTRY(vnode) v_freelist; /* vnode freelist */
LIST_ENTRY(vnode) v_mntvnodes; /* vnodes for mount point */
struct buflists v_cleanblkhd; /* clean blocklist head */
struct buflists v_dirtyblkhd; /* dirty blocklist head */
long v_numoutput; /* num of writes in progress */
enum vtype v_type; /* vnode type */
union {
struct mount *vu_mountedhere;/* ptr to mounted vfs (VDIR) */
struct socket *vu_socket; /* unix ipc (VSOCK) */
caddr_t vu_vmdata; /* private data for vm (VREG) */
struct specinfo *vu_specinfo; /* device (VCHR, VBLK) */
struct fifoinfo *vu_fifoinfo; /* fifo (VFIFO) */
} v_un;
struct nqlease *v_lease; /* Soft reference to lease */
daddr_t v_lastw; /* last write (write cluster) */
daddr_t v_cstart; /* start block of cluster */
daddr_t v_lasta; /* last allocation */
int v_clen; /* length of current cluster */
int v_ralen; /* Read-ahead length */
daddr_t v_maxra; /* last readahead block */
struct simplelock v_interlock; /* lock on usecount and flag */
struct lock *v_vnlock; /* used for non-locking fs's */
long v_spare[5]; /* round to 128 bytes */
enum vtagtype v_tag; /* type of underlying data */
void  *v_data; /* private data for fs */
};
简单说来,vnode就是描述那些独立于底层具体的文件系统的那些文件系统对象的信息,这些对象包括文件、目录等。这些信息包括:
1.v_flag:表示通用的属性,比如如果v_flag=VROOT,则表示这个vnode是一个文件系统的root。而v_flag=VISTTY表示它描述的是个tty设备。
2.一系列的计数count.如v_usecount--用户引用计数(即有多少个没有close的open操作),v_writecount:表示有多少个以write模式open的计数
3.v_mount:指向该vnode所属的文件系统的mount 结构,每个mounted的文件系统都有个struct mount结构
4.v_op:指向该vnode的操作函数
5.v_un:一些与特殊文件(socket,fifo etc)相关的信息
6.v_lastw,v_cstart,v_lasta,v_clen,v_ralen等等则是一些和文件访问相关的信息,包括最后一次写的cluster号和与read_ahead相关的乱七八糟的信息
7.v_type:说明这个vnode的类型,是regular file还是dir等等
8.v_cleanblkhd和v_dirtyblkhd:分别是与该vnode相关的干净和脏buffer,所谓的dirty buffer指那些被修改过但还没有写回硬盘中的buffer。
一个mounted的文件系统通过其struct mount结构中的mnt_vnodelist成员把所有属于它的vnode链成链表。而系统中所有的已经mount的文件系统则通过strucy mount中的mnt_list域连接成个链表。其他与文件系统相关的specfic信息也保存在这个struct mount中,该结构定义如下:
struct mount {
CIRCLEQ_ENTRY(mount) mnt_list; /* mount list */
struct vfsops *mnt_op; /* operations on fs */
struct vfsconf *mnt_vfc; /* configuration info */
struct vnode *mnt_vnodecovered; /* vnode we mounted on */
struct vnodelst mnt_vnodelist; /* list of vnodes this mount */
struct lock mnt_lock; /* mount structure lock */
int mnt_flag; /* flags */
int mnt_maxsymlinklen; /* max size of short symlink */
struct statfs mnt_stat; /* cache of filesystem stats */
qaddr_t mnt_data; /* private data */
};
用户程序通过C函数库或者syscall调用诸如write,read,open等对文件的操作,这些操作最后都通过下面的流程得到处理:首先调用vfs层的operation函数中对应的函数(write,read,open..)作与具体文件系统无关的处理,这些处理主要就是参数检查,权限检查等,如果这些检查都通过vfs层再调用文件系统特定的write,read,open函数做具体的处理,最后将结果反向一层层的返回。这种分层的架构思想很简单实用。
比如vn_open的实现,这个就是vfs层对open操作的处理函数:
vn_open(ndp, fmode, cmode)
register struct nameidata *ndp;
int fmode, cmode;
{
register struct vnode *vp;
register struct proc *p = ndp->ni_cnd.cn_proc;
register struct ucred *cred = p->p_ucred;
struct vattr vat;
struct vattr *vap = &vat;
int error;
if (fmode & O_CREAT) {
1. ndp->ni_cnd.cn_nameiop = CREATE;
2 ndp->ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF;
3 if ((fmode & O_EXCL) == 0)
4 ndp->ni_cnd.cn_flags |= FOLLOW;
5  if (error = namei(ndp))
6 return (error);
7 if (ndp->ni_vp == NULL) {
8 VATTR_NULL(vap);
9 vap->va_type = VREG;
10 vap->va_mode = cmode;
11 if (fmode & O_EXCL)
12 vap->va_vaflags |= VA_EXCLUSIVE;
13 VOP_LEASE(ndp->ni_dvp, p, cred, LEASE_WRITE);
14 if (error = VOP_CREATE(ndp->ni_dvp, &ndp->ni_vp,
15     &ndp->ni_cnd, vap))
16 return (error);
17 fmode &= ~O_TRUNC;
18 vp = ndp->ni_vp;
19 } else {
20 VOP_ABORTOP(ndp->ni_dvp, &ndp->ni_cnd);
21 if (ndp->ni_dvp == ndp->ni_vp)
22 vrele(ndp->ni_dvp);
23 else
24 vput(ndp->ni_dvp);
25 ndp->ni_dvp = NULL;
26 vp = ndp->ni_vp;
27 if (fmode & O_EXCL) {
28 error = EEXIST;
29 goto bad;
30 }
31 fmode &= ~O_CREAT;
32 }
33 } else {
34 ndp->ni_cnd.cn_nameiop = LOOKUP;
35 ndp->ni_cnd.cn_flags = FOLLOW | LOCKLEAF;
36 if (error = namei(ndp))
37 return (error);
38 vp = ndp->ni_vp;
39 }
40 if (vp->v_type == VSOCK) {
41 error = EOPNOTSUPP;
42 goto bad;
43 }
44 if ((fmode & O_CREAT) == 0) {
45 if (fmode & FREAD) {
46 if (error = VOP_ACCESS(vp, VREAD, cred, p))
47 goto bad;
48 }
49 if (fmode & (FWRITE | O_TRUNC)) {
50 if (vp->v_type == VDIR) {
51 error = EISDIR;
52 goto bad;
53 }
54 if ((error = vn_writechk(vp)) ||
55     (error = VOP_ACCESS(vp, VWRITE, cred, p)))
56 goto bad;
57 }
58 }
59 if (fmode & O_TRUNC) {
60 VOP_UNLOCK(vp, 0, p); /* XXX */
61 VOP_LEASE(vp, p, cred, LEASE_WRITE);
62 vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p); /* XXX */
63 VATTR_NULL(vap);
64 vap->va_size = 0;
65 if (error = VOP_SETATTR(vp, vap, cred, p))
66 goto bad;
67 }
68 if (error = VOP_OPEN(v
69 p, fmode, cred, p))
70 goto bad;
71 if (fmode & FWRITE)
72 vp->v_writecount++;
73 return (0);
74 bad:
vput(vp);
return (error);
}
fmode是打开文件的方式,cmode只用在创建文件时,表示创建的文件的访问权限,比如是用户可读可写?组可读可写?这个可以参考gcc的文档或者apue。与要打开的文件相关的信息包含在ndp参数中,它是个结构,定义为:
struct nameidata {
 // Arguments to namei/lookup.
caddr_t ni_dirp; /* pathname pointer */
enum uio_seg ni_segflg; /* location of pathname */
    // Arguments to lookup.
  struct vnode *ni_startdir; /* starting directory */
struct vnode *ni_rootdir; /* logical root directory */
// Results: returned from/manipulated by lookup
struct vnode *ni_vp; /* vnode of result */
struct vnode *ni_dvp; /* vnode of intermediate directory */
 // Shared between namei and lookup/commit routines.
long ni_pathlen; /* remaining chars in path */
char *ni_next; /* next location in pathname */
u_long ni_loopcnt; /* count of symlinks encountered */
/* Lookup parameters: this structure describes the subset of information from the nameidata structure that is passed
 * through the VOP interface */
struct componentname {
// Arguments to lookup.
u_long cn_nameiop; /* namei operation */
u_long cn_flags; /* flags to namei */
struct proc *cn_proc; /* process requesting lookup */
struct ucred *cn_cred; /* credentials */
//Shared between lookup and commit routines.
char *cn_pnbuf; /* pathname buffer */
char *cn_nameptr; /* pointer to looked up name */
long cn_namelen; /* length of looked up component */
u_long cn_hash; /* hash value of looked up name */
long cn_consume; /* chars to consume in lookup() */
} ni_cnd;
};
这个结构大多数和查找一个文件系统中的文件名(目录名)相关的,pathname解析后面再说。只说其中几个关键的域,第一个是ni_vp这个最终返回我们创建和打开的文件的vnode,ni_dvp则是我们想打开的文件所再的那个目录vnode。ni_cnd结构中的cn_pnbuf则保存了准备打开的文件的名称,cn_proc是一个进程描述符,指向打开文件的进程。
根据传近来的参数fmode,如果fmode指定了 O_CREAT,表示如果不存在则创建该文件,1句设置创建文件标志,2句表示最后要返回locked的文件inode和parent vnode(locked的),3-4句:如果fmode指定了O_EXECL,则设置namei flag为FOLLOW,表示要follow symbolic link。然后5句调用namei lookup这个文件的vnode,如果失败退出;如果成功则分两种情况:7-19句:如果没有找到这个文件的vnode,则调用VOP_CREATE创建这个文件,并设置相应的属性:9句设定文件为regular file,10句则设置文件的access right为cmode。创建的vnode放在vp和 ndp->ni_vp中。
如果找到了这个文件,则说明不需要创建它了,通过VOP_ABORTOP调用底层文件系统的abortop函数,通常这个函数只做一件事情:释放ndp->ni_cnd->cn_pnbuf。19-32句做的事情就是调整引用计数,如果fmode指定了O_EXCL(error if exist),则返回EEXIST错误退出.
如果fmode没有指定O_CREAT,则调用namei查找这个文件(目录),出错退出(33-38句)。40-43句判断vnode类型是否sock,如果是,返回EOPNOTSUPP错误代码退出。接下来44-67句则根据fmode做一系列判断,主要有是否有相应的权限?(读写?)是否要TRUNCATE这个文件,如果是则TRUNCATE它(通过调用 VOP_SETATTR)。最后68句调用VOP_OPEN打开这个文件。如果是以写模式打开的,则69-70增加writercount计数。
vn_close(vp, flags, cred, p)
register struct vnode *vp;
int flags;
struct ucred *cred;
struct proc *p;
{
int error;
if (flags & FWRITE)
vp->v_writecount--;
error = VOP_CLOSE(vp, flags, cred, p);
vrele(vp);
return (error);
}
这个与open对应,是close的vnode层实现。很简单,直接通过VOP_CLOSE调用底层的文件系统close函数完成关闭的操作。再调用vrele减少vp的引用计数。如果是以write方式打开的文件,则这里要decrementwritecount计数。
相应的read,write函数如下,只以read为例子
vn_read(fp, uio, cred)
struct file *fp;
struct uio *uio;
struct ucred *cred;
{
struct vnode *vp = (struct vnode *)fp->f_data;
struct proc *p = uio->uio_procp;
int count, error;
VOP_LEASE(vp, p, cred, LEASE_READ);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p);
uio->uio_offset = fp->f_offset;
count = uio->uio_resid;
error = VOP_READ(vp, uio, (fp->f_flag & FNONBLOCK) ? IO_NDELAY : 0,
cred);
fp->f_offset += count - uio->uio_resid;
VOP_UNLOCK(vp, 0, p);
return (error);
}
调用vn_lock锁定文件,调用VOP_READ完成实际的操作,再VOP_UNLOCK解锁。传递的参数中uio是一个指定用户buffer的数据结构,定义为:
struct uio {
struct iovec *uio_iov;
int uio_iovcnt;
off_t uio_offset;
int uio_resid;
enum uio_seg uio_segflg;
enum uio_rw uio_rw;
struct proc *uio_procp;
};
其中uio_iov真正指向user指定的缓冲区,下面再说,uio_iovcnt表示的是uio_iov中有多少个iovec结构,uio_rw表示该uio是用于read还是write操作,uio_procp则指向发起read/write操作的进程。uio_segflg表示段属性,是用户数据空间还是系统空间?uio_offset则是当前read/write操作的偏移量,就象c标准库中的fwrite()中那个文件的offset一样。
struct iovec用来表示一段buffer,定义很简单:
struct iovec {
char *iov_base; /* Base address. */
size_t  iov_len; /* Length. */
};
最后还有vn_select,vn_ioctl,vn_lock等实现,都是差不多的,代码如下,不多说了。
vn_ioctl(fp, com, data, p)
struct file *fp;
u_long com;
caddr_t data;
struct proc *p;
{
register struct vnode *vp = ((struct vnode *)fp->f_data);
struct vattr vattr;
int error;
switch (vp->v_type) {
case VREG:
case VDIR:
if (com == FIONREAD) {
if (error = VOP_GETATTR(vp, &vattr, p->p_ucred, p))
return (error);
*(int *)data = vattr.va_size - fp->f_offset;
return (0);
}
if (com == FIONBIO || com == FIOASYNC) /* XXX */
return (0); /* XXX */
/* fall into ... */
default:
return (ENOTTY);
case VFIFO:
case VCHR:
case VBLK:
error = VOP_IOCTL(vp, com, data, fp->f_flag, p->p_ucred, p);
if (error == 0 && com == TIOCSCTTY) {
if (p->p_session->s_ttyvp)
vrele(p->p_session->s_ttyvp);
p->p_session->s_ttyvp = vp;
VREF(vp);
}
return (error);
}
}
/*
 * File table vnode select routine.
 */
vn_select(fp, which, p)
struct file *fp;
int which;
struct proc *p;
{
return (VOP_SELECT(((struct vnode *)fp->f_data), which, fp->f_flag,
fp->f_cred, p));
}

Vfs的初始化
内核启动的时候,调用vfs_init()完成虚拟文件系统的初始化:
vfsinit()
{
struct vfsconf *vfsp;
int i, maxtypenum;
/*
 * Initialize the vnode table
 */
vntblinit();
/*
 * Initialize the vnode name cache
 */
nchinit();
/*
 * Build vnode operation vectors.
 */
vfs_op_init();
vfs_opv_init();   /* finish the job */
/*
 * Initialize each file system type.
 */
vattr_null(&va_null);
maxtypenum = 0;
for (vfsp = vfsconf, i = 1; i <= maxvfsconf; i++, vfsp++) {
if (i < maxvfsconf)
vfsp->vfc_next = vfsp + 1;
if (maxtypenum <= vfsp->vfc_typenum)
maxtypenum = vfsp->vfc_typenum + 1;
(*vfsp->vfc_vfsops->vfs_init)(vfsp);
}
/* next vfc_typenum to be used */
maxvfsconf = maxtypenum;
}
这段代码注释的很清楚,不多说。这里面有个struct vfsconf结构,它描述文件系统的配置信息。定义为:
struct vfsconf {
struct vfsops *vfc_vfsops; /* filesystem operations vector */
char vfc_name[MFSNAMELEN]; /* filesystem type name */
int vfc_typenum; /* historic filesystem type number */
int vfc_refcount; /* number mounted of this type */
int vfc_flags; /* permanent flags */
int (*vfc_mountroot)(void); /* if != NULL, routine to mount root */
struct vfsconf *vfc_next; /* next in list */
};注释很清楚,不多说
vfs_init就是初始化vnode表,name cache,和vnode operation vectors,最后对每种文件系统调用对应的vfsp->vfc_vfsops->vfs_init完成初始化。
struct vfsops定义vfs上的操作函数:
struct vfsops {
int (*vfs_mount) __P((struct mount *mp, char *path, caddr_t data,
    struct nameidata *ndp, struct proc *p));
int (*vfs_start) __P((struct mount *mp, int flags,
    struct proc *p));
int (*vfs_unmount) __P((struct mount *mp, int mntflags,
    struct proc *p));
int (*vfs_root) __P((struct mount *mp, struct vnode **vpp));
int (*vfs_quotactl) __P((struct mount *mp, int cmds, uid_t uid,
    caddr_t arg, struct proc *p));
int (*vfs_statfs) __P((struct mount *mp, struct statfs *sbp,
    struct proc *p));
int (*vfs_sync) __P((struct mount *mp, int waitfor,
    struct ucred *cred, struct proc *p));
int (*vfs_vget) __P((struct mount *mp, ino_t ino,
    struct vnode **vpp));
int (*vfs_fhtovp) __P((struct mount *mp, struct fid *fhp,
    struct mbuf *nam, struct vnode **vpp,
    int *exflagsp, struct ucred **credanonp));
int (*vfs_vptofh) __P((struct vnode *vp, struct fid *fhp));
int (*vfs_init) __P((struct vfsconf *));
int (*vfs_sysctl) __P((int *, u_int, void *, size_t *, void *,
    size_t, struct proc *));
};
下面对这段代码种调用的几个主要函数说明:
void vntblinit()
{
simple_lock_init(&mntvnode_slock);
simple_lock_init(&mntid_slock);
simple_lock_init(&spechash_slock);
TAILQ_INIT(&vnode_free_list);
simple_lock_init(&vnode_free_list_slock);
CIRCLEQ_INIT(&mountlist);
}
这个函数完成vnode管理相关数据结构的初始化,分别初始化mntvnode_slock,mntid_slock,spechash_slock等simple lock,这些都是系统全局的lock,主要用来保护mount文件系统的操作,但spechash_slock这个lock干什么用我还不清楚。
接下来初始化系统全局的vnode_free_list,所有free的vnode均挂在vnode_free_list上。再初始化vnode_free_list_slock,这个lock用来同步对vnode_free_list的访问。最后初始化mountlist,这是系统所有mounted fs的连表。
void nchinit()
{
TAILQ_INIT(&nclruhead);
nchashtbl = hashinit(desiredvnodes, M_CACHE, &nchash);
}
这个函数调用hashinit初始化了name cache的hash table。name cache的目的是加速通过文件名查找对应vnode的操作。每次一个lookup操作结束后系统都将查找到的name和对应的vnode以及vnode的capability(即vnode数据结构种的v_id域)copy到name cache中。这样下次lookup的时候,首先会查找这个name cache,如果找到相同名称的文件,则马上比较capability,如果与name cache中保存的值相同则成功返回。同样如果一次lookup在某目录下没找到对应文件,则也会在name cache 中保存一个所谓的negative cache,即把下面结构的nc_name符给该文件名,但把nc_vp设为null,这样下次lookup这个文件的时候,内核通过name cache中的这个negative cache就知道在某某目录下没有这个文件,就可以直接跳过。
struct namecache {
LIST_ENTRY(namecache) nc_hash; /* hash chain */
TAILQ_ENTRY(namecache) nc_lru; /* LRU chain */
struct vnode *nc_dvp; /* vnode of parent of name */
u_long nc_dvpid; /* capability number of nc_dvp */
struct vnode *nc_vp; /* vnode the name refers to */
u_long nc_vpid; /* capability number of nc_vp */
char nc_nlen; /* length of name */
char nc_name[NCHNAMLEN]; /* segment name */
};
然后是vfs_op_init():它完成vnode operations vectors的初始化,但这里仅仅做了将系统全局的vfs vnode op vector数据结果请零,并没有真正设定每个文件系统对应的操作函数。
void vfs_op_init()
{
int i;
// Set all vnode vectors to a well known value.
for (i = 0; vfs_opv_descs[i]; i++)
*(vfs_opv_descs[i]->opv_desc_vector_p) = NULL;
/*
 * Figure out how many ops there are by counting the table,
 * and assign each its offset.
 */
for (vfs_opv_numops = 0, i = 0; vfs_op_descs[i]; i++) {
vfs_op_descs[i]->vdesc_offset = vfs_opv_numops;
vfs_opv_numops++;
}
}
系统中每个文件系统都有一个vnodeopv_desc来描述它的vnode操作函数
//This structure is used to configure the new vnodeops vector.
struct vnodeopv_entry_desc {
struct vnodeop_desc *opve_op;   /* which operation this is */
int (*opve_impl)(); /* code implementing this operation */
};
struct vnodeopv_desc {
/* ptr to the ptr to the vector where op should go */
int (***opv_desc_vector_p)();
struct vnodeopv_entry_desc *opv_desc_ops;   /* null terminated list */
};
第一个opv_desc_vector_p很奇怪,好象没什么用处,不知道什么意思,好象唯一的作用就是定位每个文件系统的vnodeopv_entry_desc结构地址。主要还是第2个域opv_desc_ops,它定义了一个文件系统支持的所有操作,对每个operation都有一个描述它的vnodeopv_entry_desc.比如对于berkeley的ffs文件系统,对应的op vector就是:
int (**ffs_vnodeop_p)();
struct vnodeopv_entry_desc ffs_vnodeop_entries[] = {
{ &vop_default_desc, vn_default_error },
{ &vop_lookup_desc, ufs_lookup }, /* lookup */
{ &vop_create_desc, ufs_create }, /* create */
{ &vop_whiteout_desc, ufs_whiteout }, /* whiteout */
{ &vop_mknod_desc, ufs_mknod }, /* mknod */
{ &vop_open_desc, ufs_open }, /* open */
{ &vop_close_desc, ufs_close }, /* close */
{ &vop_access_desc, ufs_access }, /* access */
{ &vop_getattr_desc, ufs_getattr }, /* getattr */
{ &vop_setattr_desc, ufs_setattr }, /* setattr */
{ &vop_read_desc, ffs_read }, /* read */
{ &vop_write_desc, ffs_write }, /* write */
{ &vop_lease_desc, ufs_lease_check }, /* lease */
{ &vop_ioctl_desc, ufs_ioctl }, /* ioctl */
{ &vop_select_desc, ufs_select }, /* select */
{ &vop_revoke_desc, ufs_revoke }, /* revoke */
{ &vop_mmap_desc, ufs_mmap }, /* mmap */
{ &vop_fsync_desc, ffs_fsync }, /* fsync */
{ &vop_seek_desc, ufs_seek }, /* seek */
{ &vop_remove_desc, ufs_remove }, /* remove */
{ &vop_link_desc, ufs_link }, /* link */
{ &vop_rename_desc, ufs_rename }, /* rename */
{ &vop_mkdir_desc, ufs_mkdir }, /* mkdir */
{ &vop_rmdir_desc, ufs_rmdir }, /* rmdir */
{ &vop_symlink_desc, ufs_symlink }, /* symlink */
{ &vop_readdir_desc, ufs_readdir }, /* readdir */
{ &vop_readlink_desc, ufs_readlink }, /* readlink */
{ &vop_abortop_desc, ufs_abortop }, /* abortop */
{ &vop_inactive_desc, ufs_inactive }, /* inactive */
{ &vop_reclaim_desc, ffs_reclaim }, /* reclaim */
{ &vop_lock_desc, ufs_lock }, /* lock */
{ &vop_unlock_desc, ufs_unlock }, /* unlock */
{ &vop_bmap_desc, ufs_bmap }, /* bmap */
{ &vop_strategy_desc, ufs_strategy }, /* strategy */
{ &vop_print_desc, ufs_print }, /* print */
{ &vop_islocked_desc, ufs_islocked }, /* islocked */
{ &vop_pathconf_desc, ufs_pathconf }, /* pathconf */
{ &vop_advlock_desc, ufs_advlock }, /* advlock */
{ &vop_blkatoff_desc, ffs_blkatoff }, /* blkatoff */
{ &vop_valloc_desc, ffs_valloc }, /* valloc */
{ &vop_reallocblks_desc, ffs_reallocblks }, /* reallocblks */
{ &vop_vfree_desc, ffs_vfree }, /* vfree */
{ &vop_truncate_desc, ffs_truncate }, /* truncate */
{ &vop_update_desc, ffs_update }, /* update */
{ &vop_bwrite_desc, vn_bwrite },
{ (struct vnodeop_desc*)NULL, (int(*)())NULL }
};
struct vnodeopv_desc ffs_vnodeop_opv_desc =
{ &ffs_vnodeop_p, ffs_vnodeop_entries };
vfs_op_init()首先将系统所有文件系统的那个opv_desc_vector_p设为null,然后设置每个fs的struct vnodeop_desc 结构中的vdesc_offset,这个结构是一个辅助的数据结构,用来保存一些文件系统相关的参数。定义如下:
struct vnodeop_desc {
int vdesc_offset; /* offset in vector--first for speed */
char    *vdesc_name; /* a readable name for debugging */
int vdesc_flags; /* VDESC_* flags */
/*
 * These ops are used by bypass routines to map and locate arguments.
 * Creds and procs are not needed in bypass routines, but sometimes
 * they are useful to (for example) transport layers.
 * Nameidata is useful because it has a cred in it.
 */
int *vdesc_vp_offsets; /* list ended by VDESC_NO_OFFSET */
int vdesc_vpp_offset; /* return vpp location */
int vdesc_cred_offset; /* cred location, if any */
int vdesc_proc_offset; /* proc location, if any */
int vdesc_componentname_offset; /* if any */
/*
 * Finally, we've got a list of private data (about each operation)
 * for each transport layer.  (Support to manage this list is not
 * yet part of BSD.)
 */
caddr_t *vdesc_transports;
};我对于这个结构每个成员的作用还不清楚,先放在这里。
vfs_op_init()做的事情很少,主要的初始化工作在vfs_opv_init()中完成,它初始化每个文件系统的操作函数(op vector)。
void vfs_opv_init()
{
int i, j, k;
int (***opv_desc_vector_p)();
int (**opv_desc_vector)();
struct vnodeopv_entry_desc *opve_descp;
//Allocate the dynamic vectors and fill them in.
for (i=0; vfs_opv_descs[i]; i++) {
opv_desc_vector_p = vfs_opv_descs[i]->opv_desc_vector_p;
/*
 * Allocate and init the vector, if it needs it.
 * Also handle backwards compatibility.
 */
A. if (*opv_desc_vector_p == NULL) {
/* XXX - shouldn't be M_VNODE */
MALLOC(*opv_desc_vector_p, PFI*,
       vfs_opv_numops*sizeof(PFI), M_VNODE, M_WAITOK);
bzero (*opv_desc_vector_p, vfs_opv_numops*sizeof(PFI));
DODEBUG(printf("vector at %x allocated\n",
    opv_desc_vector_p));
}
B. opv_desc_vector = *opv_desc_vector_p;
for (j=0; vfs_opv_descs[i]->opv_desc_ops[j].opve_op; j++) {
opve_descp = &(vfs_opv_descs[i]->opv_desc_ops[j]);
/*
 * Sanity check:  is this operation listed
 * in the list of operations?  We check this
 * by seeing if its offest is zero.  Since
 * the default routine should always be listed
 * first, it should be the only one with a zero
 * offset.  Any other operation with a zero
 * offset is probably not listed in
 * vfs_op_descs, and so is probably an error.
 *
 * A panic here means the layer programmer
 * has committed the all-too common bug
 * of adding a new operation to the layer's
 * list of vnode operations but
 * not adding the operation to the system-wide
 * list of supported operations.
 */
if (opve_descp->opve_op->vdesc_offset == 0 &&
    opve_descp->opve_op->vdesc_offset !=
     VOFFSET(vop_default)) {
printf("operation %s not listed in %s.\n",
    opve_descp->opve_op->vdesc_name,
    "vfs_op_descs");
panic ("vfs_opv_init: bad operation");
}
 // Fill in this entry.
C. opv_desc_vector[opve_descp->opve_op->vdesc_offset] =
opve_descp->opve_impl;
}
}
/*
 * Finally, go back and replace unfilled routines
 * with their default.  (Sigh, an O(n^3) algorithm.  I
 * could make it better, but that'd be work, and n is small.)
 */
D. for (i = 0; vfs_opv_descs[i]; i++) {
opv_desc_vector = *(vfs_opv_descs[i]->opv_desc_vector_p);
/*
 * Force every operations vector to have a default routine.
 */
if (opv_desc_vector[VOFFSET(vop_default)]==NULL) {
panic("vfs_opv_init: operation vector without default routine.");
}
for (k = 0; k<vfs_opv_numops; k++)
if (opv_desc_vector[k] == NULL)
opv_desc_vector[k] =
opv_desc_vector[VOFFSET(vop_default)];
E. }
}
vfs_opv_descs是系统定义的全局变量,是个数组,每个文件系统占一个元素,我们刚刚在vfs_op_init()中把每个opv_desc的opv_desc_vector_p设成NULL,这里我们为每个opv_desc分配内存(A~B段代码)。B~C段做一些检查,C那里把对应的操作函数地址填到op vector中,而对于那些没有提供文件系统特定(filesystem specfic)的操作则在D~E段设定default的函数。
这段涉及到两种op vector,一个是所谓的vnode的操作vector,即vfs_opv_descs描述的,它是对vnode来说的,还有上面的struct vfsops 定义的,这是与文件系统相关的操作,两个是不同的。