vfscore源码以及相关函数

vfscore

1. linux——vfs虚拟文件系统

  1. 虚拟文件系统VFS
    (1) 是具体文件系统和上层应用的接口层
    (2) 只存在于内存中,不存在任何外存空间
    (3) 在系统启动时建立,系统关闭时消亡
    (4) 由超级块、inode, dentry, vfsmont等信息组成

  2. 具体文件系统
    (1) 如 minix ext1/2/3/4 sysfs等
    (2) 实现代码被组织成模块形式,向vfs注册回调
    (3) 处理和具体文件系统相关的细节

1.1 vfs各个对象之间的关系

在这里插入图片描述

  1. file_system_type
    用于描述某种文件系统类型,一个file_system_type相当于一个类,用于定义一个具体的文件系统实例,某个file_system_type对象(具体的某个文件系统)与特定的 super_block关联,所有的文件系统都通过next组成单链表

  2. super_block
    用于描述整个文件系统的元数据,它与根inode,根dentry关联,同时也与挂载实例vfsmount相关联,它通过s_list链入全局链表super_blocks;通过s_instances连入file_system_type的fs_supers哈希表中

  3. dentry
    用于描述文件和目录的层级关系,dentry与父dentry和子dentry形成dentry树;近期没有使用的dentry通过d_lru链接入super block的s_dentry_lru链表;dentry通过d_u.d_alias连入inode的i_dentry哈希链表,通过d_inode指向所属的inode,通过dentry可以查找到对应的inode。为了查找某一个文件,它与vfsmount构成<vfsmount, dentry>二元组。图中的dentry如果它的d_parent为它自身则为root dentry

  4. inode
    所有的inode通过链表链接起来,通过i_sb_list指针连入super_block的s_inodes链表,它的i_sb指向所属文件系统的super block;
    近期没有使用的inode通过i_lru链接入super block的s_inode_lru链表

    ​ 每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。

    ​ 一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,后期会根据实际的需要发生变化。

    ​ 注意inode号:inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。

    ​ 当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,现在可以处理文件数据了。

  5. vfsmount
    每一个装载实例都对应一个vfsmount,vfsmount与待装载文件系统(非被装载)的root dentry,super_block关联.

  6. mount
    mount是vfsmount的封装,所有的mount链接成一张链表,mount代表待装载文件系统, 它的装载点指明了挂载到哪个dentry。可以为一个文件系统创建多个装载实例vfsmount,挂载到不同的挂载点上。图中的mount如果它的mnt_parent为它自身则为root mount,代表的是rootfs

1.2 VFS与具体文件系统的关联

1.2.1 VFS与具体文件系统的超级块

在这里插入图片描述

​ 从图中可以看出VFS中在内存构建了一个通用的超级块实例,它通过s_fs_info来指向具体文件系统的内存超级块,而具体文件系统的超级块又来源于具体文件系统在磁盘上的超级块。

1.2.2 VFS和具体文件系统的inode

在这里插入图片描述

可以看出inode具有三种形态:
磁盘上的inode;具体文件系统的inode;VFS的inode
vfs的inode往往作为具体文件系统的inode的一个成员变量
vfs的inode的i_private一般指向了具体文件系统的inode

1.2.3 vfs与具体文件系统的dentry

在这里插入图片描述

一般dentry只存在于VFS中

1.3 具体文件系统磁盘布局

在这里插入图片描述

以minix文件系统为例,文件系统在磁盘布局包含如下部分:

  • 引导块

    在文件系统开头,通常是一个扇区,存放引导程序,用于读入并启动操作系统

  • 超级块

    存放文件系统结构信息,并说明各部分大小

  • i节点位图

    说明i节点使用情况,如果inode已经使用,则对应的bit置1

  • 逻辑块位图

    描述磁盘上每个逻辑块的使用情况,如果逻辑块被使用则置1

  • i节点

    反应文件的元数据,一般是描述了文件由哪些逻辑块组成

  • 逻辑块

    保存了文件的数据

1.4 路径查找总体过程

在这里插入图片描述

  1. 根据文件名查找目录文件,目录文件是特殊文件,保存了文件名和i节点的对应关系,找到对应的目录项,目录项包含了inode号,找到对应的inode
  2. 进入inode节点找到对应的逻辑块
  3. 读取逻辑块读取相关文件内容

2. 源码分析

​ 文件目录结构如下图:

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
27
28
29
30
31
32
33
vfscore
├─ Config.uk
├─ dentry.c
├─ eventpoll.c
├─ exportsyms.uk
├─ extra.ld
├─ fd.c
├─ file.c
├─ fops.c
├─ include
│ ├─ sys
│ │ └─ vfs.h
│ └─ vfscore
│ ├─ dentry.h
│ ├─ eventpoll.h
│ ├─ file.h
│ ├─ fs.h
│ ├─ mount.h
│ ├─ prex.h
│ ├─ uio.h
│ └─ vnode.h
├─ lookup.c
├─ main.c
├─ Makefile.uk
├─ mount.c
├─ pipe.c
├─ rootfs.c
├─ stdio.c
├─ subr_uio.c
├─ syscalls.c
├─ task.c
├─ vfs.h
└─ vnode.c

2.1 vnode.h

[The Sun VFS/Vnode Architecture - UNIX Filesystems: Evolution, Design, and Implementation Book] (oreilly.com)

编号 Inode Vnode
1. Inodes有与文件内容无关的文件元数据 vnode包含在文件生命周期内不会更改的属性
2. Inode是一种磁盘上的结构,它从磁盘的角度解释文件的存储 Vnode是inode的内存结构抽象
3. Inode不是内核的数据结构 Vnode是inode的内核表示。
4. 它可以快速访问 它比Inode需要更多的访问时间
5. inode总是有效的 Vnode不一定总是有效的
6. 它包含了总是需要的信息(例如保护、管理权力) Vnode只在打开文件时才存在
7. Inode与分区内的唯一编号相关联 Vnode在分区中没有唯一的编号
8. 然而,它是UNIX操作系统中的一种数据结构 Vnode是内核内存中的一个对象,它代表UNIX文件接口

vnode负责记录文件存储的位置指针以及文件的属性等等,它具体包含以下几个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct vnode {
uint64_t v_ino; /* inode number */
struct uk_list_head v_link; /* link for hash list */
struct mount *v_mount; /* mounted vfs pointer */
struct vnops *v_op; /* vnode operations */
int v_refcnt; /* reference count */
int v_type; /* vnode type */
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 */
struct uk_list_head v_names; /* directory entries pointing at this */
void *v_data; /* private data for fs */
};
  • v_ino: 对应inode的数字号,即索引节点号
  • v_link: 一个双向链表的节点,表示所有hash值相同的vnode的链表,用于快速查找
  • *v_mount: vfs挂载的指针
  • *v_op: vnode的操作指针
  • v_refcnt: 引用计数,当为0时即可以删除
  • v_type: vnode的类型,大致分为无类型、常规文件、目录、块设备(例如硬盘)、字符设备(例如键盘、打印机,一次处理一个字符)、软链接(删除的时候计数-1)、socket、FIFO管道(进程通信)
  • v_flags: vnode的标志,有三类-> VROOT 指文件系统的root节点;VISTTY 指设备是tty(Teletype)指终端设备;VPROTDEV指被保护的设备。
  • v_mode: 文件的访问权限
  • v_size: 文件的大小
  • v_lock: 互斥锁,保证在一个时间里只有一个线程能访问
  • v_name: 这个节点所在目录项的链表
  • *v_data: 存储的文件的指针

除了以上几个属性,vnode自身还包括以下几个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct vattr {
unsigned int va_mask;
enum vtype va_type; /* vnode type */
mode_t va_mode; /* file access mode */
nlink_t va_nlink;
uid_t va_uid;
gid_t va_gid;
dev_t va_fsid; /* id of the underlying filesystem */
ino_t va_nodeid;
struct timespec va_atime;
struct timespec va_mtime;
struct timespec va_ctime;
dev_t va_rdev;
uint64_t va_nblocks;
off_t va_size;
};
  • va_mask: 表示vnode属性的掩码,一共17位,每一位代表一个属性
  • va-type: 表示vnode的类型,同上v-type
  • va-mode: 表示文件的访问模式
  • va-nlink: 硬链接数。当该vnode描述一个目录时,这个值至少为2,代表.和..的数目
  • va-uid: vnode所属文件的拥有者的id
  • va-gid: vnode所属文件所在组的id
  • va-fsid: 底层文件系统的id
  • va-nodeid: 索引节点号
  • va-atime: 文件最近一次被访问的时间
  • va-mtime: 文件最近一次被修改的时间
  • va-ctime: 文件最近一次被修改的时间,比起内容更强调文件属性
  • va-rdev: 如果该节点描述的是一个设备文件,则该值为设备号
  • va-nblocks: 文件使用的块的个数
  • va-size: 文件的大小

vattr结构体主要用于合并多种类型的文件属性,以便将它们作为函数参数进行传递。

vnode的操作函数有以下几个:

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; // 控制设备I/O
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; // 获取vnode属性
vnop_setattr_t vop_setattr; // 设置vnode属性
vnop_inactive_t vop_inactive; // 设置vnode为不活跃的vnode
vnop_truncate_t vop_truncate; // 截断文件
vnop_link_t vop_link; // 创建硬链接
vnop_cache_t vop_cache; // 创建vnode缓存
vnop_fallocate_t vop_fallocate; // 为文件分配物理空间
vnop_readlink_t vop_readlink; // 查找软链接文件的源文件
vnop_symlink_t vop_symlink; // 创建软链接
vnop_poll_t vop_poll; // 将vnode放到等待队列
};

2.2 dentry.h

​ dentry即directory entry目录项,用于描述文件和目录的层级关系。目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。

​ 例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。

​ 注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。

1
2
3
4
5
6
7
8
9
10
11
12
struct dentry {
struct uk_hlist_node d_link; /* link for hash list */
int d_refcnt; /* reference count */
char *d_path; /* pointer to path in fs */
struct vnode *d_vnode;
struct mount *d_mount;
struct dentry *d_parent; /* pointer to parent */
struct uk_list_head d_names_link; /* link fo vnode::d_names */
struct uk_mutex d_lock;
struct uk_list_head d_child_list;
struct uk_list_head d_child_link;
};
  • d_link: 同v_link,用于快速查找dentry
  • d_refcnt: 同v_refcnt, 引用计数
  • *d_path: 在文件系统里的路径的指针
  • *d_vnode: 目录项指向的节点,一个有效的dentry结构必定有一个vnode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_vnode必定指向一个vnode结构。但是一个vnode却可以对应多个目录项(例如在别的路径下创建的软链接)
  • *d_mount: 目录项指向的挂载
  • *d_parent: 目录项的父节点
  • d_names_link:
  • d_lock: 同v_lock,是一个互斥锁
  • d_child_list: 目录项的子
  • d_child_link:

dentry的函数:

1
2
3
4
5
6
struct dentry *dentry_alloc(struct dentry *parent_dp, struct vnode *vp, const char *path);
struct dentry *dentry_lookup(struct mount *mp, char *path);
int dentry_move(struct dentry *dp, struct dentry *parent_dp, char *path);
void dentry_remove(struct dentry *dp);
void dref(struct dentry *dp);
void drele(struct dentry *dp);
  • alloc: 通过父目录项节点、对应的文件节点vnode、路径path创建一个目录项
  • lookup:通过挂载和路径来找到目录项
  • move: 移动目录项到指定路径
  • remove: 删除目录项
  • ref:
  • rele: 释放目录项

2.3 mount.h

mount主要用于挂载设备,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct mount {
struct vfsops *m_op; /* pointer to vfs operation */
int m_flags; /* mount flag */
int m_count; /* reference count */
char m_path[PATH_MAX]; /* mounted path */
char m_special[PATH_MAX]; /* resource */
struct device *m_dev; /* mounted device */
struct dentry *m_root; /* root vnode */
struct dentry *m_covered; /* vnode covered on parent fs */
void *m_data; /* private data for fs */
struct uk_list_head mnt_list;
fsid_t m_fsid; /* id that uniquely identifies the fs */
};
  • m_op: mount操作的指针
  • m_flags: 挂载的类型,分为只读文件系统、同步写文件系统、异步写文件系统、不能执行的文件系统、不能设置权限的文件系统 、不能解释特别的文件、与底层文件系统的联合。
  • m_path: 挂载的路径
  • m_special: 资源的路径
  • *m_dev: 挂载的设备
  • *m_root: 根目录项
  • *m_covered: 挂载的文件系统的父目录项
  • *m_data: 挂载的文件系统数据
  • mnt_list: 挂载的文件系统的链表
  • m_fsid: 挂载的文件系统的独特的id

挂载操作有以下几个操作:

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;
};

2.4 file.h

vfscore_file

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 */
};
  • fd: 文件描述符 file descriptor
  • f_flags: 文件是否是打开的标志
  • f_count: 文件的引用计数
  • f_offset: 在文件中的当前位置
  • *f_data: 文件描述符描述的特定数据
  • f_vfs_flags: 内部实现的标志
  • *f_dentry: 文件的目录项
  • f_lock: 文件的互斥锁
  • f_ep: 事件队列的列表

2.5 main.c

首先main.c定义了三个内联函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static inline int libc_error(int err)
{
errno = err;
return -1;
}

/* 在BSD系统对read和write的内部实现中,一个部分读或写操作会同时返回EWOULDBLOCK error 和一个非零bytes,我们需要避免这种读写出错,因此当error是EWOULDBLOCK或者EINTR并且bytes不为0时不会报错。
*/
static inline int has_error(int error, int bytes)
{
/* TODO: OSv checks also for ERESTART */
return error && (
(bytes == 0) ||
(error != EWOULDBLOCK && error != EINTR));
}

// 将模式mode与全局umask进行与操作来实现mask
static inline mode_t apply_umask(mode_t mode)
{
return mode & ~ukarch_load_n(&global_umask);
}

2.5.1 open

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 定义open函数,返回值为int
UK_LLSYSCALL_R_DEFINE(int, open, const char*, pathname, int, flags,
mode_t, mode)
{
trace_vfs_open(pathname, flags, mode);

struct task *t = main_task; // 定义了一个任务
char path[PATH_MAX]; // 定义了一个路径数组
struct vfscore_file *fp; // 定义了一个vfs file的指针*fp
int fd, error; // 申请fd空间,错误信息error
int acc; // 访问模式access mode

acc = 0;
switch (flags & O_ACCMODE) { // 只读、只写、可读写三种情况的acc
case O_RDONLY:
acc = VREAD;
break;
case O_WRONLY:
acc = VWRITE;
break;
case O_RDWR:
acc = VREAD | VWRITE;
break;
}

// task convert 把相对路径pathname和当前路径t->cwd转换为绝对路径并且保存在path数组中
error = task_conv(t, pathname, acc, path);
if (error)
goto out_error;

// sys open最核心的代码,作用就是把vfs_file与路径path绑定
error = sys_open(path, flags, mode, &fp);
if (error)
goto out_error;

// fdalloc 从空闲描述符表分配文件的文件描述符,并且把对应的vfs_file放入打开文件列表中,通过数组索引可快速找到该文件
error = fdalloc(fp, &fd);
if (error)
goto out_fput;
fdrop(fp); // 释放vfs_file的占用
trace_vfs_open_ret(fd);
return fd; //最后返回file的文件描述符fd

out_fput:
fdrop(fp);
out_error:
trace_vfs_open_err(error);
return -error;
}

sys_open的代码如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 返回int,传入绝对路径path数组的地址,标志位,文件形式,vfs_file的地址的地址
int
sys_open(char *path, int flags, mode_t mode, struct vfscore_file **fpp)
{
struct vfscore_file *fp; // 创建一个vfscore_file的指针
struct dentry *dp, *ddp; // 创建两个目录项的指针
struct vnode *vp; // 创建一个vnode的指针
char *filename; // 文件名的指针
int error; // 错误信息

DPRINTF(VFSDB_SYSCALL, ("sys_open: path=%s flags=%x mode=%x\n",
path, flags, mode));

// 读取open的模式,即判断是创建、读还是写
flags = vfscore_fflags(flags);
if (flags & O_CREAT) { // 是创建
// namei函数:将该目录项与路径path绑定
error = namei(path, &dp);
// 没有这个目录的时候创建目录
if (error == ENOENT) {
// lookup: 将文件名与filename绑定,ddp与路径path绑定
if ((error = lookup(path, &ddp, &filename)) != 0)
return error;
// 给目录项ddp所指向的vnode上锁
vn_lock(ddp->d_vnode);
// 判断该vnode能不能写
if ((error = vn_access(ddp->d_vnode, VWRITE)) != 0) {
vn_unlock(ddp->d_vnode);
drele(ddp);
return error;
}
mode &= ~S_IFMT; // 形式转为文件形式
mode |= S_IFREG; // 形式转为常规文件形式
error = VOP_CREATE(ddp->d_vnode, filename, mode); // 创建文件,将文件与该vnode绑定
vn_unlock(ddp->d_vnode); // 将该vnode解锁
drele(ddp); // 释放目录项ddp

if (error)
return error;
if ((error = namei(path, &dp)) != 0) // 再将路径path与目录项dp绑定
return error;

vp = dp->d_vnode; // vp即为目录项dp的vnode
flags &= ~O_TRUNC; // 将文件模式转为非截取模式
} else if (error) {
return error;
} else {
// 文件已经存在的情况就不需要创建了
if (flags & O_EXCL) {
error = EEXIST;
goto out_drele;
}
}

vp = dp->d_vnode;
flags &= ~O_CREAT; // 将文件模式转为非创建模式
} else {
// 打开文件
if (flags & O_NOFOLLOW) { // 如果是nofollow模式,与软链接有关
error = open_no_follow_chk(path);
if (error != 0)
return error;
}
error = namei(path, &dp); // 将路径与目录项dp绑定
if (error)
return error;

vp = dp->d_vnode; // vp即dp的vnode

// 如果是写模式或者是截断模式
if (flags & UK_FWRITE || flags & O_TRUNC) {
error = vn_access(vp, VWRITE); // 查看vnode的访问权限
if (error)
goto out_drele;

error = EISDIR; // 目录无法写
if (vp->v_type == VDIR) // 如果vnode指向的文件是目录,则无法写
goto out_drele;
}
if (flags & O_DIRECTORY) { // 如果打开的是个目录
if (vp->v_type != VDIR) { // 但是vnode指向的文件又不是目录
error = ENOTDIR;
goto out_drele;
}
}
}

fp = calloc(sizeof(struct vfscore_file), 1); // 给vfs_file分配空间
if (!fp) {
error = ENOMEM; // 内存不足了
goto out_drele;
}

fhold(fp); // 将vfs_file的引用计数加一
fp->f_flags = flags; // 将vfs_file的标志位赋值

/*
* Don't need to increase refcount here, we already hold a reference
* to dp from namei().
*/
fp->f_dentry = dp; // 将vfs_file的目录项赋值

uk_mutex_init(&fp->f_lock);
UK_INIT_LIST_HEAD(&fp->f_ep);

vn_lock(vp); // 给vnode上锁

if (flags & O_TRUNC) { // 截断模式
error = EINVAL;
if (!(flags & UK_FWRITE) || vp->v_type == VDIR) // 如果模式不是写或者vnode指向的是个目录就去释放
goto out_fp_free_unlock;

error = VOP_TRUNCATE(vp, 0); // vnode的截断
if (error)
goto out_fp_free_unlock;
}

error = VOP_OPEN(vp, fp); // 核心代码 vnode的打开
if (error)
goto out_fp_free_unlock;

vn_unlock(vp); // 解锁vnode

*fpp = fp; // 将vfs_file与fp绑定,即与目录项dp、节点vp、文件名绑定了
return 0;

out_fp_free_unlock:
free(fp);
vn_unlock(vp);
out_drele:
if (dp)
drele(dp);
return error;
}

VOP_OPEN的实现代码如下:9pfs是qemu所使用的文件系统Plan 9 Filesystem Protocol

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
27
28
29
30
31
32
33
34
35
36
37
38
39
static int uk_9pfs_open(struct vfscore_file *file)
{
struct uk_9pdev *dev = UK_9PFS_MD(file->f_dentry->d_mount)->dev;
struct uk_9pfid *openedfid;
struct uk_9pfs_file_data *fd;
int rc;

/* Allocate memory for file data. */
fd = calloc(1, sizeof(*fd));
if (!fd)
return ENOMEM;

/* Clone fid. */
openedfid = uk_9p_walk(dev, UK_9PFS_VFID(file->f_dentry->d_vnode),
NULL);
if (PTRISERR(openedfid)) {
rc = PTR2ERR(openedfid);
goto out;
}

/* Open cloned fid. */
rc = uk_9p_open(dev, openedfid,
uk_9pfs_open_mode_from_posix_flags(file->f_flags));

if (rc)
goto out_err;

fd->fid = openedfid;
file->f_data = fd;
UK_9PFS_ND(file->f_dentry->d_vnode)->nb_open_files++;

return 0;

out_err:
uk_9pfid_put(openedfid);
out:
free(fd);
return -rc;
}

2.6 一些函数

2.6.1 task.c : task_conv、path_conv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将当前工作目录和相对路径进行连接,合成为绝对路径
int
task_conv(struct task *t, const char *cpath, int acc __unused, char *full)
{
int rc;

rc = path_conv(t->t_cwd, cpath, full); // t->t_cwd 即当前工作目录
if (rc != 0) {
return (rc);
}

/* Check if the client task has required permission */
return (0); //sec_file_permission(t->t_taskid, full, acc);
}

path_conv:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* 上述转换的具体实现
* Convert to full path from the cwd of task and path.
* @wd: working directory
* @path: target path
* @full: full path to be returned
*/
int
path_conv(char *wd, const char *cpath, char *full)
{
char path[PATH_MAX];
char *src, *tgt, *p, *end;
size_t len = 0;

strlcpy(path, cpath, PATH_MAX); // 定义一个暂时的变量path来储存相对路径
path[PATH_MAX - 1] = '\0'; // 路径最后一个字符是空字符

len = strlen(path);
if (len >= PATH_MAX)
return ENAMETOOLONG;
if (strlen(wd) + len >= PATH_MAX)
return ENAMETOOLONG;
src = path;
tgt = full;
end = src + len;
if (path[0] == '/') { // 如果相对路径的第一个字符是/说明是绝对路径
*tgt++ = *src++;
len = 1;
} else { // 否则就加上工作目录
strlcpy(full, wd, PATH_MAX);
len = strlen(wd);
tgt += len;
if (len > 1 && path[0] != '.') {
*tgt = '/';
tgt++;
len++;
}
}
while (*src) { // 根据相对路径中的路径来赋值
p = src;
while (*p != '/' && *p != '\0')
p++;
*p = '\0';
if (!strcmp(src, "..")) { // 如果遇到..就推到上一级
if (len >= 2) {
len -= 2;
tgt -= 2; /* skip previous '/' */
while (*tgt != '/') {
tgt--;
len--;
}
if (len == 0) {
tgt++;
len++;
}
}
} else if (!strcmp(src, ".")) { //如果遇到.就忽略
/* Ignore "." */
} else {
while (*src != '\0') {
*tgt++ = *src++;
len++;
}
}
if (p == end)
break;
if (len > 0 && *(tgt - 1) != '/') { // 如果遇到/就加上/
*tgt++ = '/';
len++;
}
src = p + 1;
}
*tgt = '\0';

return (0);
}

2.6.2 fd.c: fdalloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 分配一个空闲的文件描述符fd file descriptor,并且将文件管理指针fp装载到fdtable对应的位置中,这样上层可以方便地通过文件
// 描述符fd来快速获取到文件管理指针fp,进而快速管理文件。
int fdalloc(struct vfscore_file *fp, int *newfd)
{
int fd, ret = 0;

fhold(fp);

fd = vfscore_alloc_fd(); // 找到一个空闲的文件描述符fd
if (fd < 0) {
ret = fd;
goto exit;
}

ret = vfscore_install_fd(fd, fp); // 将文件管理指针fp装载到fdtable[fd]中
if (ret)
fdrop(fp);
else
*newfd = fd;

exit:
return ret;
}

vfscore_alloc_fd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int vfscore_alloc_fd(void)
{
unsigned long flags;
int ret;

flags = ukplat_lcpu_save_irqf();

// 寻找fdtable中位图中第一个是0的位置,即空闲的位置fd
ret = uk_find_next_zero_bit(fdtable.bitmap, FDTABLE_MAX_FILES, 0);

if (ret == FDTABLE_MAX_FILES) {
ret = -ENFILE;
goto exit;
}

// 将空闲位置的位图设为1,代表不空闲
uk_bitmap_set(fdtable.bitmap, ret, 1);

exit:
ukplat_lcpu_restore_irqf(flags);
return ret;
}

2.6.3 lookup.c: namei、lookup

lookup:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* 通过一个全路径来查找与该路径对应的目录项 例如 "/home/usr/1.txt"
* Search a pathname.
* This is a very central but not so complicated routine. ;-P
*
* @path: full path.
* @dpp: pointer to dentry for directory.
* @name: if non-null, pointer to file name in path.
*/
int
lookup(char *path, struct dentry **dpp, char **name)
{
char buf[PATH_MAX];
char root[] = "/";
char *file, *dir;
struct dentry *dp;
int error;

DPRINTF(VFSDB_VNODE, ("lookup: path=%s\n", path));

/*
* Get the path for directory.
*/
strlcpy(buf, path, sizeof(buf)); // 把path赋值到buf中 "/home/usr/1.txt"
file = strrchr(buf, '/'); // 找到file的名字 :"/1.txt"
if (!buf[0]) {
return ENOTDIR;
}
if (file == buf) { // 如果file和path一样说明文件在根目录下 dir = "/"
dir = root;
} else { // 否则将file第一个/去掉: file = "1.txt" dir = "/home/usr/1.txt"
*file = '\0';
dir = buf;
}
/*
* Get the vnode for directory
*/
if ((error = namei(dir, &dp)) != 0) { //通过"/home/usr/1.txt"找到对应的目录项
return error;
}
if (dp->d_vnode->v_type != VDIR) {
drele(dp);
return ENOTDIR;
}

*dpp = dp;

if (name) {
/*
* 获取文件名 "1.txt"
*/
*name = strrchr(path, '/') + 1;
}
return 0;
}

namei:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* 通过一个完整的路径获取到对应的目录项 "/home/usr/fs0/file/1.txt"
* Convert a pathname into a pointer to a dentry
*
* @path: full path name.
* @dpp: dentry to be returned.
*/
int
namei(const char *path, struct dentry **dpp)
{
char *p;
char node[PATH_MAX];
char name[PATH_MAX];
char fp[PATH_MAX];
struct mount *mp;
struct dentry *dp, *ddp;
struct vnode *dvp, *vp;
int error, i;
int links_followed;
int need_continue;

DPRINTF(VFSDB_VNODE, ("namei: path=%s\n", path));

links_followed = 0;
strlcpy(fp, path, PATH_MAX); // fp[] = "/home/usr/fs0/file/1.txt"

do { // 大循环,循环条件是有软链接导致跳转
need_continue = 0;
/*
* 找到离1.txt最近的挂载点, 比如fs0是9pfs的挂载点 则挂载mp中对应的目录项为fs0
* p指向该文件目录的根目录,即"...fs0/"后面的地址
*/
if (vfs_findroot(fp, &mp, &p)) {
return ENOTDIR;
}
int mountpoint_len = p - fp - 1; // p-fp-1即"/home/usr/fs0"所占用的地址长度
strlcpy(node, "/", sizeof(node)); // *node = "/\0"
strlcat(node, p, sizeof(node)); // node[] = "/file/1.txt\0"
dp = dentry_lookup(mp, node); // 通过挂载项mp和"/file/1.txt\0"来查找对应的目录项
if (dp) { // 如果已经存在的目录项就直接返回这个目录项
/* vnode is already active. */
*dpp = dp;
return 0;
}
/* 如果不存在目录项,就从根目录的目录项开始找
* Find target vnode, started from root directory.
* This is done to attach the fs specific data to
* the target vnode.
*/
ddp = mp->m_root; // ddp就是挂载对应的根目录的目录项"/"
if (!ddp) {
UK_CRASH("VFS: no root");
}
dref(ddp);

node[0] = '\0'; // node[] = "\0"

while (*p != '\0') { // for(*p = "f"; *p != '\0'; p++)
/*
* Get lower directory/file name.
*/
while (*p == '/') { // 如果*p = / 就继续
p++;
}

if (*p == '\0') { // 如果*p 到空了就break
break;
}

for (i = 0; i < PATH_MAX; i++) {
if (*p == '\0' || *p == '/') {
break;
}
name[i] = *p++; // name[] = "file"
}
name[i] = '\0'; // name[] = "file\0"

/*
* Get a vnode for the target.
*/
strlcat(node, "/", sizeof(node)); // node[] = "/"
strlcat(node, name, sizeof(node)); // node[] = "/file\0"
dvp = ddp->d_vnode; // 挂载的根目录对应的vnode
vn_lock(dvp);
dp = dentry_lookup(mp, node); // 寻找"/file"和挂载mp对应的目录项
if (dp == NULL) { // 如果没找到就创建目录项
/* 通过挂载根目录的vnode来查找"file\0"目录对应的vnode*/
error = VOP_LOOKUP(dvp, name, &vp);
if (error) {
vn_unlock(dvp);
drele(ddp);
return error;
}

dp = dentry_alloc(ddp, vp, node); // 通过这个vnode和目录名"/file\0"分配一个目录项
vput(vp); // vnode引用计数加一

if (!dp) { // 没有内存了
vn_unlock(dvp);
drele(ddp);
return ENOMEM;
}
}
vn_unlock(dvp);
drele(ddp);
ddp = dp;

if (dp->d_vnode->v_type == VLNK) { // 如果找到的这个目录项对应的是个软链接
error = namei_follow_link(dp, node, name, fp, mountpoint_len); // 则通过这个软链接转到真正的目录项上
if (error) {
drele(dp);
return (error);
}

drele(dp);

p = fp; // 将p指针指到真正的位置上
dp = NULL;
ddp = NULL;
vp = NULL;
dvp = NULL;
name[0] = 0;
node[0] = 0;

if (++links_followed >= MAXSYMLINKS) { // 如果不停地跳转,超过了设定的最大链接数量就报错
return (ELOOP);
}
need_continue = 1;
break;
}

if (*p == '/' && ddp->d_vnode->v_type != VDIR) { // 如果p走到了"/"但是前面的"file"不是个目录就报错
drele(ddp);
return ENOTDIR;
}
}
} while (need_continue);

*dpp = dp;
return 0;
}

2.6.4 mount.c: vfs_findroot

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
27
28
29
30
31
32
33
34
35
36
/* 找到该目录最近的挂载点,代表这个目录是哪个文件系统的挂载 例如寻找"/home/usr/fs0/file/1.txt"的挂载点
* Get the root directory and mount point for specified path.
* @path: full path.
* @mp: mount point to return.
* @root: pointer to root directory in path.
*/
int
vfs_findroot(const char *path, struct mount **mp, char **root)
{
struct mount *m = NULL, *tmp;
size_t len, max_len = 0;

if (!path)
return -1;

/* 寻找最近的挂载点
* "/home/usr/fs0/file/1.txt" 对应的挂载点有 "/"和"/home/usr/fs0/"
* count_match 返回的是最长匹配字符个数,即离路径最近的挂载点
*/
uk_mutex_lock(&mount_lock);
uk_list_for_each_entry(tmp, &mount_list, mnt_list) {// 遍历挂载点,如果一个挂载点离path最近,就返回这个挂载点
len = count_match(path, tmp->m_path);
if (len > max_len) {
max_len = len;
m = tmp;
}
}
uk_mutex_unlock(&mount_lock);
if (m == NULL)
return -1;
*root = (char *)(path + max_len); // root指向挂载点的根目录
if (**root == '/')
(*root)++;
*mp = m;
return 0;
}

2.6.5 dentry.c: dentry_lookup、dentry_alloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 通过目录"file"和该目录对应的挂载来查找对应的目录项
struct dentry *
dentry_lookup(struct mount *mp, char *path)
{
struct dentry *dp;

uk_mutex_lock(&dentry_hash_lock);
// 通过挂载和目录"file"计算哈希值,查找目录项的哈希链表,进一步查找需要的目录项。查找条件是挂载和目录
uk_hlist_for_each_entry(dp, &dentry_hash_table[dentry_hash(mp, path)], d_link) {
if (dp->d_mount == mp && !strncmp(dp->d_path, path, PATH_MAX)) {
dp->d_refcnt++;
uk_mutex_unlock(&dentry_hash_lock);
return dp;
}
}
uk_mutex_unlock(&dentry_hash_lock);
return NULL; /* not found */
}

dentry_alloc:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 通过vnode、父目录项parent_dp、 目录"file"来分配一个目录项
struct dentry *
dentry_alloc(struct dentry *parent_dp, struct vnode *vp, const char *path)
{
struct mount *mp = vp->v_mount;
struct dentry *dp = (struct dentry*)calloc(sizeof(*dp), 1);

if (!dp) {
return NULL;
}

dp->d_path = strdup(path); // 拷贝目录使得该目录项的目录是"file"
if (!dp->d_path) {
free(dp);
return NULL;
}

vref(vp);

dp->d_refcnt = 1; // 新创建的目录项的引用计数是1
dp->d_vnode = vp; // 将目录项与vnode绑定
dp->d_mount = mp; // 将目录项与对应的挂载绑定
UK_INIT_LIST_HEAD(&dp->d_child_list); // 代表child_list是个链表头

if (parent_dp) {
dref(parent_dp);

uk_mutex_lock(&parent_dp->d_lock);

// 将child link插入到父目录项的child_list链表中,这样遍历的时候就能从上到下遍历了
uk_list_add(&dp->d_child_link, &parent_dp->d_child_list);
uk_mutex_unlock(&parent_dp->d_lock);
}
dp->d_parent = parent_dp; //设置父目录项

// 将目录名添加到vnode的名字链表中,因为一个vnode可能对应多个目录项,所以通过链表将目录名串起来
vn_add_name(vp, dp);

uk_mutex_lock(&dentry_hash_lock);

// 将目录项计算哈希值,插入到对应的哈希链表中。
uk_hlist_add_head(&dp->d_link,
&dentry_hash_table[dentry_hash(mp, path)]);
uk_mutex_unlock(&dentry_hash_lock);
return dp;
};

3. unikraft——vfscore

3.1 dentry目录项的结构

3.2 vnode的结构和dentry的关系

3.3 mount挂载的结构

3.4 open函数的流程