vfscore核心数据结构

vfscore核心数据结构

数据结构介绍

四个非常重要的数据结构:vfscore_file,dentry,mount,vnode

1、文件结构体

文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开时创建的。

1
2
3
4
5
6
7
8
9
10
11
12
struct vfscore_file {
int fd; /* 文件描述符,用于让进程快速索引到文件位置 */
int f_flags; /* open flags 打开标志 */
int f_count; /* reference count 引用计数 */
off_t f_offset; /* current position in file 文件指针,用于定位当前指针在文件中的哪个位置 */
void *f_data; /* file descriptor specific data 与文件描述符绑定的特殊数据?(应该和底层实现有关,之后的调研方向) */
int f_vfs_flags; /* internal implementation flags 实现标志(可能是区分不同类型文件的标识符) */
struct dentry *f_dentry; /* 该文件所在的目录项 */
struct uk_mutex f_lock; /* 文件锁,用于同步操作 */

struct uk_list_head f_ep; /* List of eventpoll_fd's eventpoll的文件描述符的链表 */
};

1.1 int fd

unikraft定义了这么一个结构体,叫做fdtable,其具体定义如下:

1
2
3
4
5
struct fdtable {
unsigned long bitmap[UK_BITS_TO_LONGS(FDTABLE_MAX_FILES)];
uint32_t fd_start;
struct vfscore_file *files[FDTABLE_MAX_FILES];
};

当文件被进程打开时,会创建一个fd作为该文件的文件标识符,用于快速检索文件所在的位置,而文件则会被file[fd]所指向,在bitmap中也会占用一个bit用来标识fd已被分配。

2、目录项结构体

VFS把每个目录看作一个文件,由若干子目录和文件组成。对于进程查找路径名中的每个分量,内核都为其创建一个目录项对象;目录项对象将每个分量与其对应的索引节点相联系。例如/tmp/test,内核会分别为“/”,“tmp”,“test”创建目录项。目录项也可以用于标识普通文件。

1
2
3
4
5
6
7
8
9
10
11
12
struct dentry {
struct uk_hlist_node d_link; /* link for hash list 详见2.1 */
int d_refcnt; /* reference count 引用计数 */
char *d_path; /* pointer to path in fs dentry所代表的相对mount的相对路径 */
struct vnode *d_vnode; /* 每个目录项所绑定的vnode项 */
struct mount *d_mount; /* 该目录项所指向的挂载点 */
struct dentry *d_parent; /* pointer to parent 指向父目录的指针*/
struct uk_list_head d_names_link; /* link fo vnode::d_names 详见2.4*/
struct uk_mutex d_lock; /* 目录项锁,用于同步操作 */
struct uk_list_head d_child_list;
struct uk_list_head d_child_link; /* 详见2.5 */
};

dentry节点会被挂载在一个hashtable中,其定义在lib/vfscore/dentry.c中,其定义为:

static struct uk_hlist_head dentry_hash_table[DENTRY_BUCKETS];

dentry中存储的路径会与其指向的mount节点的信息计算一个hash值,然后根据假设计算出的hash值为k,那么该节点就会插入dentry_hash_table[k]所代表的链表中,d_link则是用于哈希链表连接的一个指针项。哈希表可以用来根据路径值和挂载点快速检索某个dentry。

以下为struct uk_hlist_head的定义:

1
2
3
4
5
6
7
struct uk_hlist_head {
struct uk_hlist_node *first;
};

struct uk_hlist_node {
struct uk_hlist_node *next, **pprev;
};

可以看到该d_link项仅仅就是用来链接不同的dentry项,哈希表示意图如图所示:

dentry哈希表

2.2 struct vnode *d_vnode;

该dentry目录项所对应的vnode项,而vnode项是描述该目录项所代表文件的各种属性,如文件类型,文件大小等,详见vnode具体分析。

2.3 struct dentry *d_parent;

目录项其实是一种类似于树形结构的组织结构(其实每个目录项下面的子目录项的链接是个环形结构),每个目录项节点会有一个指向父目录的指针。

每个vnode可能同时有多个不同的dentry同时指向,所以指向同一个vnode的所有dentry便自己组成一个环形链接结构。由vnode中的v_names则是这个链接结构的一个节点(头部),所有的uk_list_head结构其实都是环形双向链表结构,在遍历的时候的停止条件则可以设成遍历到本身就停止,方便遍历。

d_child_list与d_child_link的关系就像vnames与d_names_link的关系一样,d_child_list是子目录项所构成的环形链接结构的一个节点(头部),而d_child_link则是该层级目录结构下所有的目录项相互链接的链接项,父级目录与子级目录之间的关系如图所示:

dentry 父子目录项关系

3、挂载结构体

mount挂载的作用,就是将一个设备(存储设备)挂接到一个已知目录下,访问该目录就是访问该存储设备

linux操作系统将所有的设备都看作文件,它将整个计算机的资源都整合成一个大的文件目录。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上,然后通过访问这个目录来访问存储设备。挂载就是把设备放在一个目录下,让系统知道怎么管理这个设备里的文件,了解这个存储设备的可读写特性之类的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct mount {
struct vfsops *m_op; /* pointer to vfs operation 详见3.1 */
int m_flags; /* mount flag 挂载标志 */
int m_count; /* reference count 引用计数 */
char m_path[PATH_MAX]; /* mounted path 详见3.2 */
char m_special[PATH_MAX]; /* resource 该mount所挂载的设备在文件系统中的绝对路径。*/
struct device *m_dev; /* mounted device 指向mount所挂载的设备 */
struct dentry *m_root; /* root vnode */
struct dentry *m_covered; /* vnode covered on parent fs 详见3.3 */
void *m_data; /* private data for fs 文件系统的私有数据 */
struct uk_list_head mnt_list;
fsid_t m_fsid; /* id that uniquely identifies the fs 该文件系统的id */
};

3.1 struct vfsops *m_op;

通常来讲,一个mount挂载设备对应的是一个文件系统(如ext2,9p等),而m_op指向的是该类型文件系统的基本操作,vfsops结构体的定义如下:

1
2
3
4
5
6
7
8
struct vfsops {
int (*vfs_mount) (struct mount *, const char *, int, const void *);
int (*vfs_unmount) (struct mount *, int flags);
int (*vfs_sync) (struct mount *);
int (*vfs_vget) (struct mount *, struct vnode *);
int (*vfs_statfs) (struct mount *, struct statfs *);
struct vnops *vfs_vnops;
};

之所以该vfscore可以兼容多种不同的文件系统在同一个大的文件系统中,是因为vfs定义了统一的抽象接口,而下层不同的文件系统只要实现这些接口,便可以被注册到vfs中统一管理并使用。

3.2 char m_path[PATH_MAX];

该mount点所指向的绝对路径(相对于整个文件系统根目录的路径),而前文dentry中的d_path指向的是相对路径,即以mount挂载点为root(路径为/)的相对路径。

3.3 struct dentry *m_root; 与 struct dentry *m_covered;

该mount点所指向的dentry目录项为m_covered。假设我们需要将device挂载在目录/home/user/fs0下面,那么该m_covered所指向的为/home/user/fs0路径所对应的dentry,挂载之后,在该mount之下的所有dentry项的d_path都会变成相对于该mount点的相对路径,而m_root则是指向挂载mount点后所创建的root项。mount示意图如图所示:

mount示意图

3.4 struct uk_list_head mnt_list;

关联所有mount实例的链接,所有的mount实例都会通过该mnt_list链接成一个双向链接的环

4、vnode文件信息管理结构体

vnode用于管理文件(目录也是一种文件)的具体信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct vnode {
uint64_t v_ino; /* inode number 该vnode所关联的底层inode的编号 */
struct uk_list_head v_link; /* link for hash list 详见4.1 */
struct mount *v_mount; /* mounted vfs pointer 该vnode所指向的挂载点*/
struct vnops *v_op; /* vnode operations 详见4.2 */
int v_refcnt; /* reference count vnode引用计数 */
int v_type; /* vnode type 详见4.3 */
int v_flags; /* vnode flag 文件访问的模式 */
mode_t v_mode; /* file mode 文件的权限控制 */
off_t v_size; /* file size 文件大小 */
struct uk_mutex v_lock; /* lock for this vnode vnode锁,用于同步操作 */
struct uk_list_head v_names; /* directory entries pointing at this */
void *v_data; /* private data for fs */
};

所有的已经激活的vnode(也就是已经被opened)都会被挂载进vnode hashtable中,其定义为static struct uk_list_head vnode_table[VNODE_BUCKETS];,具体作用与dentry hashtable如出一辙。哈希计算参数为挂载点mount与inode编号ino。

4.2 struct vnops *v_op;

每个vnode都会有很多的操作,比如open,close,write等,vnops的定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct vnops {
vnop_open_t vop_open;
vnop_close_t vop_close;
vnop_read_t vop_read;
vnop_write_t vop_write;
vnop_seek_t vop_seek;
vnop_ioctl_t vop_ioctl;
vnop_fsync_t vop_fsync;
vnop_readdir_t vop_readdir;
vnop_lookup_t vop_lookup;
vnop_create_t vop_create;
vnop_remove_t vop_remove;
vnop_rename_t vop_rename;
vnop_mkdir_t vop_mkdir;
vnop_rmdir_t vop_rmdir;
vnop_getattr_t vop_getattr;
vnop_setattr_t vop_setattr;
vnop_inactive_t vop_inactive;
vnop_truncate_t vop_truncate;
vnop_link_t vop_link;
vnop_cache_t vop_cache;
vnop_fallocate_t vop_fallocate;
vnop_readlink_t vop_readlink;
vnop_symlink_t vop_symlink;
vnop_poll_t vop_poll;
};

而该vnops也有对外封装调用的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define VOP_OPEN(VP, FP)	   ((VP)->v_op->vop_open)(FP)
#define VOP_CLOSE(VP, FP) ((VP)->v_op->vop_close)(VP, FP)
#define VOP_READ(VP, FP, U, F) ((VP)->v_op->vop_read)(VP, FP, U, F)
#define VOP_CACHE(VP, FP, U) ((VP)->v_op->vop_cache)(VP, FP, U)
#define VOP_WRITE(VP, U, F) ((VP)->v_op->vop_write)(VP, U, F)
#define VOP_SEEK(VP, FP, OLD, NEW) ((VP)->v_op->vop_seek)(VP, FP, OLD, NEW)
#define VOP_IOCTL(VP, FP, C, A) ((VP)->v_op->vop_ioctl)(VP, FP, C, A)
#define VOP_FSYNC(VP, FP) ((VP)->v_op->vop_fsync)(VP, FP)
#define VOP_READDIR(VP, FP, DIR) ((VP)->v_op->vop_readdir)(VP, FP, DIR)
#define VOP_LOOKUP(DVP, N, VP) ((DVP)->v_op->vop_lookup)(DVP, N, VP)
#define VOP_CREATE(DVP, N, M) ((DVP)->v_op->vop_create)(DVP, N, M)
#define VOP_REMOVE(DVP, VP, N) ((DVP)->v_op->vop_remove)(DVP, VP, N)
#define VOP_RENAME(DVP1, VP1, N1, DVP2, VP2, N2) \
((DVP1)->v_op->vop_rename)(DVP1, VP1, N1, DVP2, VP2, N2)
#define VOP_MKDIR(DVP, N, M) ((DVP)->v_op->vop_mkdir)(DVP, N, M)
#define VOP_RMDIR(DVP, VP, N) ((DVP)->v_op->vop_rmdir)(DVP, VP, N)
#define VOP_GETATTR(VP, VAP) ((VP)->v_op->vop_getattr)(VP, VAP)
#define VOP_SETATTR(VP, VAP) ((VP)->v_op->vop_setattr)(VP, VAP)
#define VOP_INACTIVE(VP) ((VP)->v_op->vop_inactive)(VP)
#define VOP_TRUNCATE(VP, N) ((VP)->v_op->vop_truncate)(VP, N)
#define VOP_LINK(DVP, SVP, N) ((DVP)->v_op->vop_link)(DVP, SVP, N)
#define VOP_FALLOCATE(VP, M, OFF, LEN) ((VP)->v_op->vop_fallocate)(VP, M, OFF, LEN)
#define VOP_READLINK(VP, U) ((VP)->v_op->vop_readlink)(VP, U)
#define VOP_SYMLINK(DVP, OP, NP) ((DVP)->v_op->vop_symlink)(DVP, OP, NP)
#define VOP_POLL(VP, EP, ECP) ((VP)->v_op->vop_poll)(VP, EP, ECP)

同vfs能够纳入多种不同的文件系统,vfs能够允许不同文件存在同一个文件系统的原因也是统一定义了文件操作的抽象,不同文件的区别就在于其vnode所对应的操作项的实施方式不同,而在vfs层的接口可以屏蔽这些实现细节的不同,而调用统一的抽象方法,故而一个大的文件系统可以容纳不同种类的文件。

4.3 int v_type;

标识该文件的具体类别,而在unikraft文件系统中,文件类型被封装为一个枚举变量,其中枚举了所有可能的文件类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum vtype {
VNON, /* no type */
VREG, /* regular file */
VDIR, /* directory */
VBLK, /* block device */
VCHR, /* character device */
VLNK, /* symbolic link */
VSOCK, /* socks */
VFIFO, /* FIFO */
#ifdef CONFIG_LIBPOSIX_EVENT
VEPOLL, /* epoll */
VEVENT, /* eventfd */
#endif /* CONFIG_LIBPOSIX_EVENT */
VBAD
};

4.4 struct uk_list_head v_names;

用于关联每一个指向vnode的dentry项。d_child_list与d_child_link的关系就像vnames与d_names_link的关系一样,具体图示见2.5