0%

Ucore-Lab8

文件系统和文件

文件系统简述:

文件系统是操作系统用于明确 “存储设备或分区上的文件” 的 “一组方法和数据结构”,是操作系统中负责管理和存储文件信息的软件机构(被称为文件管理系统,简称文件系统)

  • 注意:文件系统是对应硬盘的分区的,而不是整个硬盘,不管是硬盘只有一个分区,还是几个分区,不同的分区可以有着不同的文件系统

文件系统由三部分组成:

  • 文件系统的接口层(用于对文件系统进行操作的一系列函数)
  • 文件系统抽象层(“对象操纵和管理” 软件的集合)
  • 对象及属性

从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统,具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等,文件系统是软件系统的一部分,它的存在使得应用可以方便的使用抽象命名的数据对象和大小可变的空间

  • 比如桌面上的各种文件:操作系统提供了一种抽象来控制管理这些文件,可以是图形界面,也可以是命令行,这些抽象可以让我们忽略一些底层的原理,免去了大量复杂的操作

文件系统的功能:

  • 管理和调度文件的存储空间,提供文件的逻辑结构、物理结构和存储方法
  • 实现文件从标识到实际地址的映射,实现文件的控制操作和存取操作,实现文件信息的共享并提供可靠的文件保密和保护措施,提供文件的安全措施

文件系统-访问接口层

下面就是在内核中通用的文件相关函数(上层的文件系统需要的系统调用),同时也是我们在uCore中最常使用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int sysfile_open(const char *path, uint32_t open_flags);    // 打开或创建一个文件
int sysfile_close(int fd); // 关闭一个打开的vnode
int sysfile_read(int fd, void *base, size_t len); // 读-Read file
int sysfile_write(int fd, void *base, size_t len); // 写-Write file
int sysfile_seek(int fd, off_t pos, int whence); // 查找-Seek file
int sysfile_fstat(int fd, struct stat *stat); // 统计-Stat file
int sysfile_fsync(int fd); // 同步-Sync file
int sysfile_chdir(const char *path); // 改变DIR(页目录表)
int sysfile_mkdir(const char *path); // 创建DIR(页目录表)
int sysfile_link(const char *path1, const char *path2); // 设置path1的链接设置为path2
int sysfile_rename(const char *path1, const char *path2); // 重命名文件
int sysfile_unlink(const char *path); // 取消path的链接
int sysfile_getcwd(char *buf, size_t len); // 获取当前工作目录
int sysfile_getdirentry(int fd, struct dirent *direntp); // 在DIR中获取文件条目
int sysfile_dup(int fd1, int fd2); // 复制文件
int sysfile_pipe(int *fd_store); // 建造管道
int sysfile_mkfifo(const char *name, uint32_t open_flags); // 生成命名管道
  • sysfile_open:打开或创建一个文件
1
2
3
4
5
6
7
8
9
10
11
int
sysfile_open(const char *__path, uint32_t open_flags) {
int ret;
char *path;
if ((ret = copy_path(&path, __path)) != 0) {
return ret;
}
ret = file_open(path, open_flags);
kfree(path);
return ret;
}
  • sysfile_close:关闭一个打开的vnode
1
2
3
4
int
sysfile_close(int fd) {
return file_close(fd);
}
  • sysfile_read:读文件
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
int
sysfile_read(int fd, void *base, size_t len) {
struct mm_struct *mm = current->mm;
if (len == 0) {
return 0;
}
if (!file_testfd(fd, 1, 0)) {
return -E_INVAL;
}
void *buffer;
if ((buffer = kmalloc(IOBUF_SIZE)) == NULL) {
return -E_NO_MEM;
}
int ret = 0;
size_t copied = 0, alen;
while (len != 0) {
if ((alen = IOBUF_SIZE) > len) {
alen = len;
}
ret = file_read(fd, buffer, alen, &alen);
if (alen != 0) {
lock_mm(mm);
{
if (copy_to_user(mm, base, buffer, alen)) {
assert(len >= alen);
base += alen, len -= alen, copied += alen;
}
else if (ret == 0) {
ret = -E_INVAL;
}
}
unlock_mm(mm);
}
if (ret != 0 || alen == 0) {
goto out;
}
}
out:
kfree(buffer);
if (copied != 0) {
return copied;
}
return ret;
}
  • sysfile_write:写文件
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
int
sysfile_write(int fd, void *base, size_t len) {
struct mm_struct *mm = current->mm;
if (len == 0) {
return 0;
}
if (!file_testfd(fd, 0, 1)) {
return -E_INVAL;
}
void *buffer;
if ((buffer = kmalloc(IOBUF_SIZE)) == NULL) {
return -E_NO_MEM;
}

int ret = 0;
size_t copied = 0, alen;
while (len != 0) {
if ((alen = IOBUF_SIZE) > len) {
alen = len;
}
lock_mm(mm);
{
if (!copy_from_user(mm, buffer, base, alen, 0)) {
ret = -E_INVAL;
}
}
unlock_mm(mm);
if (ret == 0) {
ret = file_write(fd, buffer, alen, &alen);
if (alen != 0) {
assert(len >= alen);
base += alen, len -= alen, copied += alen;
}
}
if (ret != 0 || alen == 0) {
goto out;
}
}

out:
kfree(buffer);
if (copied != 0) {
return copied;
}
return ret;
}
  • sysfile_seek:寻找文件
1
2
3
4
int
sysfile_seek(int fd, off_t pos, int whence) {
return file_seek(fd, pos, whence);
}
  • sysfile_fstat:统计文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int
sysfile_fstat(int fd, struct stat *__stat) {
struct mm_struct *mm = current->mm;
int ret;
struct stat __local_stat, *stat = &__local_stat;
if ((ret = file_fstat(fd, stat)) != 0) {
return ret;
}

lock_mm(mm);
{
if (!copy_to_user(mm, __stat, stat, sizeof(struct stat))) {
ret = -E_INVAL;
}
}
unlock_mm(mm);
return ret;
}
  • sysfile_fsync:同步文件
1
2
3
4
int
sysfile_fsync(int fd) {
return file_fsync(fd);
}
  • sysfile_chdir:改变DIR(页目录表)
1
2
3
4
5
6
7
8
9
10
11
int
sysfile_chdir(const char *__path) {
int ret;
char *path;
if ((ret = copy_path(&path, __path)) != 0) {
return ret;
}
ret = vfs_chdir(path);
kfree(path);
return ret;
}
  • sysfile_mkdir:创建DIR(页目录表),ucore没有该接口,它采用另一种方式实现
  • sysfile_link:设置path1的链接设置为path2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
sysfile_link(const char *__path1, const char *__path2) {
int ret;
char *old_path, *new_path;
if ((ret = copy_path(&old_path, __path1)) != 0) {
return ret;
}
if ((ret = copy_path(&new_path, __path2)) != 0) {
kfree(old_path);
return ret;
}
ret = vfs_link(old_path, new_path);
kfree(old_path), kfree(new_path);
return ret;
}
  • sysfile_rename:重命名文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
sysfile_rename(const char *__path1, const char *__path2) {
int ret;
char *old_path, *new_path;
if ((ret = copy_path(&old_path, __path1)) != 0) {
return ret;
}
if ((ret = copy_path(&new_path, __path2)) != 0) {
kfree(old_path);
return ret;
}
ret = vfs_rename(old_path, new_path);
kfree(old_path), kfree(new_path);
return ret;
}
  • sysfile_unlink:取消path的链接
1
2
3
4
5
6
7
8
9
10
11
int
sysfile_unlink(const char *__path) {
int ret;
char *path;
if ((ret = copy_path(&path, __path)) != 0) {
return ret;
}
ret = vfs_unlink(path);
kfree(path);
return ret;
}
  • sysfile_getcwd:获取当前工作目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int
sysfile_getcwd(char *buf, size_t len) {
struct mm_struct *mm = current->mm;
if (len == 0) {
return -E_INVAL;
}

int ret = -E_INVAL;
lock_mm(mm);
{
if (user_mem_check(mm, (uintptr_t)buf, len, 1)) {
struct iobuf __iob, *iob = iobuf_init(&__iob, buf, len, 0);
ret = vfs_getcwd(iob);
}
}
unlock_mm(mm);
return ret;
}
  • sysfile_getdirentry:在DIR中获取文件条目
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
int
sysfile_getdirentry(int fd, struct dirent *__direntp) {
struct mm_struct *mm = current->mm;
struct dirent *direntp;
if ((direntp = kmalloc(sizeof(struct dirent))) == NULL) {
return -E_NO_MEM;
}

int ret = 0;
lock_mm(mm);
{
if (!copy_from_user(mm, &(direntp->offset), &(__direntp->offset), sizeof(direntp->offset), 1)) {
ret = -E_INVAL;
}
}
unlock_mm(mm);

if (ret != 0 || (ret = file_getdirentry(fd, direntp)) != 0) {
goto out;
}

lock_mm(mm);
{
if (!copy_to_user(mm, __direntp, direntp, sizeof(struct dirent))) {
ret = -E_INVAL;
}
}
unlock_mm(mm);

out:
kfree(direntp);
return ret;
}
  • sysfile_dup:复制文件
1
2
3
4
int
sysfile_dup(int fd1, int fd2) {
return file_dup(fd1, fd2);
}
  • sysfile_pipe:创建管道(未完成)
1
2
3
4
int
sysfile_pipe(int *fd_store) {
return -E_UNIMP;
}
  • sysfile_mkfifo:生成命名管道(未完成)
1
2
3
4
int
sysfile_mkfifo(const char *__name, uint32_t open_flags) {
return -E_UNIMP;
}

这些 sysfile_xx 函数本质上就是更底层的 file_xx 函数(直接控制文件的函数)的外包装,可以说 file_xx 函数再外加一些对文件系统的操作就是 sysfile_xx 了:

1
2
3
4
5
6
7
8
9
10
11
int file_open(char *path, uint32_t open_flags);
int file_close(int fd);
int file_read(int fd, void *base, size_t len, size_t *copied_store);
int file_write(int fd, void *base, size_t len, size_t *copied_store);
int file_seek(int fd, off_t pos, int whence);
int file_fstat(int fd, struct stat *stat);
int file_fsync(int fd);
int file_getdirentry(int fd, struct dirent *dirent);
int file_dup(int fd1, int fd2);
int file_pipe(int fd[]);
int file_mkfifo(const char *name, uint32_t open_flags);

通常来讲,这些函数都会操作当前进程访问文件的数据接口,即 current->filesp(这也是进程描述结构体 proc_struct 新增的条目-filesp)

文件系统-抽象层

文件系统抽象层是把 不同文件系统对外共性接口 提取出来,形成一个函数指针数组,这样,通用文件系统访问接口层只需访问文件系统抽象层,而不需关心具体文件系统的实现细节和接口(有点面向对象的味道)

VFS虚拟文件系统-实现抽象层的技术

系统接口(通用文件系统访问接口层)再下一层就到了 VFS 虚拟文件系统

虚拟文件系统(VFS)是 物理文件系统与服务之间的一个接口层(用于在文件系统与服务之间进行最初的解析),它对 Linux 的每个文件系统的所有细节进行抽象,使得不同的文件系统在 Linux 核心以及系统中运行的其他进程看来都是相同的

虚拟文件系统中,所使用的相关函数接口分别是 :

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
void vfs_init(void); // 虚拟文件系统vfs初始化
void vfs_cleanup(void); // 虚拟文件系统vfs清除
void vfs_devlist_init(void);

int vfs_set_curdir(struct inode *dir); // 通过inode更改当前线程的当前目录
int vfs_get_curdir(struct inode **dir_store); // 检索当前线程的当前目录的inode
int vfs_get_root(const char *devname, struct inode **root_store); // 获取名为DEVNAME的文件系统的根inode
const char *vfs_get_devname(struct fs *fs); // 获取传入的文件系统的挂载设备名称

int vfs_open(char *path, uint32_t open_flags, struct inode **inode_store); // 打开或创建文件
int vfs_close(struct inode *node); // 关闭文件
int vfs_link(char *old_path, char *new_path); // 创建指向文件的硬链接
int vfs_symlink(char *old_path, char *new_path); // 创建包含内容的符号链接路径
int vfs_readlink(char *path, struct iobuf *iob); // 将符号链接的内容读入uio(内核驱动)
int vfs_mkdir(char *path); // 创建一个目录
int vfs_unlink(char *path); // 删除文件/目录
int vfs_rename(char *old_path, char *new_path); // 重命名文件
int vfs_chdir(char *path); // 按名称更改当前线程的当前目录
int vfs_getcwd(struct iobuf *iob); // 检索当前线程当前目录的名称

int vfs_lookup(char *path, struct inode **node_store); // 查找文件
int vfs_lookup_parent(char *path, struct inode **node_store, char **endp); // 查找父目录

int vfs_set_bootfs(char *fsname);
int vfs_get_bootfs(struct inode **node_store);

int vfs_add_fs(const char *devname, struct fs *fs);
int vfs_add_dev(const char *devname, struct inode *devnode, bool mountable);

int vfs_mount(const char *devname, int (*mountfunc)(struct device *dev, struct fs **fs_store));
int vfs_unmount(const char *devname);
int vfs_unmount_all(void);

struct inode_ops {
unsigned long vop_magic;
int (*vop_open)(struct inode *node, uint32_t open_flags);
int (*vop_close)(struct inode *node);
int (*vop_read)(struct inode *node, struct iobuf *iob);
int (*vop_write)(struct inode *node, struct iobuf *iob);
int (*vop_fstat)(struct inode *node, struct stat *stat);
int (*vop_fsync)(struct inode *node);
int (*vop_namefile)(struct inode *node, struct iobuf *iob);
int (*vop_getdirentry)(struct inode *node, struct iobuf *iob);
int (*vop_reclaim)(struct inode *node);
int (*vop_gettype)(struct inode *node, uint32_t *type_store);
int (*vop_tryseek)(struct inode *node, off_t pos);
int (*vop_truncate)(struct inode *node, off_t len);
int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **node_store);
int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store);
int (*vop_ioctl)(struct inode *node, int op, void *data);
};

ucore 虚拟文件系统中有四大对象:SuperBlock、inode、dentry、file

  • 超级块(SuperBlock)

超级块主要从文件系统的全局角度描述特定文件系统的全局信息,它的作用范围是整个OS空间

ucore 中有如下结构体来描述超级块:

1
2
3
4
5
6
struct sfs_super {
uint32_t magic; /* 魔数,必须是SFS_MAGIC */
uint32_t blocks; /* fs中的块数 */
uint32_t unused_blocks; /* fs中未使用的区块 */
char info[SFS_MAX_INFO_LEN + 1]; /* sfs的info(信息) */
};

PS:内核通过 magic 来检查磁盘镜像是否是合法的 SFS 镜像

  • 索引节点(inode)

UNIX 将文件的相关元数据信息(如访问控制权限、大小、拥有者、创建时间、数据内容等等信息)存储在一个单独的数据结构中,该结构被称为索引节点(每个文件都有一个 inode)

Linux 系统为每一个文件都分配了一个 inode 编号,这个编号中记录了文件相关的一些元信息,通过这些元信息可以用来唯一标识一个文件(操作系统上的 inode 并非无穷无尽,通常在你安装操作系统后,系统上的 inode 数量就已经确定了下来)

ucore 中的 inode 由如下结构体确定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct inode {
union {
struct device __device_info; /* 设备结点 */
struct sfs_inode __sfs_inode_info; /* 文件/目录节点 */
} in_info;
enum {
inode_type_device_info = 0x1234,
inode_type_sfs_inode_info,
} in_type; /* 类型 */
int ref_count; /* 引用次数 */
int open_count; /* 打开次数 */
struct fs *in_fs; /* 相关联的文件系统 */
const struct inode_ops *in_ops; /* 当前结构所对应的操作集合(inode接口) */
};

接下来看看几种类型的索引结点:

一,磁盘索引结点——保存在硬盘中的索引结点

  • sfs_disk_inode 结构记录了文件或目录的内容存储的索引信息,该数据结构在硬盘里储存,需要时读入内存:
1
2
3
4
5
6
7
8
struct sfs_disk_inode {
uint32_t size; /* 文件大小 */
uint16_t type; /* 目标类型(文件,目录,链接) */
uint16_t nlinks; /* 此文件的硬链接 */
uint32_t blocks; /* 此文件的块 */
uint32_t direct[SFS_NDIRECT]; /* 直接指向了保存文件内容数据的数据块索引值 */
uint32_t indirect; /* 指向的是间接数据块 */
};
  • 对于普通文件:索引值 direct 指向的 block 中保存的是文件中的数据
  • 对于目录:索引值 direct 指向的数据保存的是目录下所有的文件名,以及对应的索引节点所在的索引块(磁盘块)所形成的数组,数据结构如下:(其实就是它就是目录项)
1
2
3
4
struct sfs_disk_entry {
uint32_t ino; /* inode编号 */
char name[SFS_MAX_FNAME_LEN + 1]; /* 文件名称 */
};
  • 不管是文件还是目录,磁盘索引结点都需要与内存索引结点进行“绑定”,这样才可以操控磁盘上的数据
  • 当 uCore 创建一个“用于存储文件/目录”的 inode 结构时(即该 inode -> in_info 成员变量为 sfs_inode 类型),程序会执行函数 sfs_create_inode,该函数会将 inode -> sfs_inode 成员与磁盘对应结点 sfs_disk_inode 相关联,从而使得只凭 inode 即可操作该结点
  • PS:用于描述设备 device 的 inode 会在其他函数中被初始化,不会执行函数 sfs_create_inode

二,内存索引结点——保存在内存中的索引结点(inode 结构体的条目之一)

1
2
3
4
5
6
7
8
9
struct sfs_inode {
struct sfs_disk_inode *din; /* 磁盘索引节点 */
uint32_t ino; /* inode编号 */
bool dirty; /* 如果inode被修改,则为true */
int reclaim_count; /* 如果它为'0'就杀死inode */
semaphore_t sem; /* din(磁盘索引节点)的信号量 */
list_entry_t inode_link; /* inode链表的入口 */
list_entry_t hash_link; /* inode哈希链表的入口 */
};
  • SFS 中的内存 sfs_inode 包含SFS的硬盘 sfs_disk_inode 信息,而且还增加了其他一些信息,这些信息用于:判断相关硬盘位置是否改写、互斥操作、回收和快速地定位
  • PS:一个内存 sfs_inode 是在打开一个文件后才创建的,如果关机则相关信息都会消失,而硬盘 sfs_disk_inode 的内容是保存在硬盘中的,只是在进程需要时才被读入到内存中,用于访问文件或目录的具体内容数据

三,文件结点——用于指向磁盘索引结点的结点(有助于硬链接的实现)

1
2
3
4
struct sfs_disk_entry {
uint32_t ino; /* inode编号(指向了sfs_disk_inode磁盘索引结点) */
char name[SFS_MAX_FNAME_LEN + 1]; /* 文件名称 */
};

inode->in_ops 指向 inode 接口,是对常规文件、目录、设备文件所有操作的一个抽象函数表示

对于某一具体的文件系统中的文件或目录,只需实现相关的函数,就可以被用户进程访问具体的文件了(用户进程无需了解具体文件系统的实现细节)

inode_ops 采用如下结构体进行组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct inode_ops {
unsigned long vop_magic;
int (*vop_open)(struct inode *node, uint32_t open_flags);
int (*vop_close)(struct inode *node);
int (*vop_read)(struct inode *node, struct iobuf *iob);
int (*vop_write)(struct inode *node, struct iobuf *iob);
int (*vop_fstat)(struct inode *node, struct stat *stat);
int (*vop_fsync)(struct inode *node);
int (*vop_namefile)(struct inode *node, struct iobuf *iob);
int (*vop_getdirentry)(struct inode *node, struct iobuf *iob);
int (*vop_reclaim)(struct inode *node);
int (*vop_gettype)(struct inode *node, uint32_t *type_store);
int (*vop_tryseek)(struct inode *node, off_t pos);
int (*vop_truncate)(struct inode *node, off_t len);
int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **node_store);
int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store);
int (*vop_ioctl)(struct inode *node, int op, void *data);
};

inode 结构是与文件系统相关的,不同文件系统所实现的 inode 结构是不同的(主要体现在 inode_ops 条目),它的存在可以让 VFS 忽略更下一级的文件系统差异,使之注重于提供一个统一的文件系统接口

inode_ops 根据其 in_info 的不同而实现其不同的功能:(目录,文件,外设)

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
// sfs特定的dir操作对应于inode上的抽象操作
static const struct inode_ops sfs_node_dirops = {
.vop_magic = VOP_MAGIC,
.vop_open = sfs_opendir,
.vop_close = sfs_close,
.vop_fstat = sfs_fstat,
.vop_fsync = sfs_fsync,
.vop_namefile = sfs_namefile,
.vop_getdirentry = sfs_getdirentry,
.vop_reclaim = sfs_reclaim,
.vop_gettype = sfs_gettype,
.vop_lookup = sfs_lookup,
};
// sfs特定的file操作对应于inode上的抽象操作
static const struct inode_ops sfs_node_fileops = {
.vop_magic = VOP_MAGIC,
.vop_open = sfs_openfile,
.vop_close = sfs_close,
.vop_read = sfs_read,
.vop_write = sfs_write,
.vop_fstat = sfs_fstat,
.vop_fsync = sfs_fsync,
.vop_reclaim = sfs_reclaim,
.vop_gettype = sfs_gettype,
.vop_tryseek = sfs_tryseek,
.vop_truncate = sfs_truncfile,
};
// sfs特定的dev操作对应于inode上的抽象操作
static const struct inode_ops dev_node_ops = {
.vop_magic = VOP_MAGIC,
.vop_open = dev_open,
.vop_close = dev_close,
.vop_read = dev_read,
.vop_write = dev_write,
.vop_fstat = dev_fstat,
.vop_ioctl = dev_ioctl,
.vop_gettype = dev_gettype,
.vop_tryseek = dev_tryseek,
.vop_lookup = dev_lookup,
};
  • 目录项(dentry)

目录项不是目录,而是目录的组成部分

目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计

  • UNIX 中目录被看作一种特定的文件,而目录项是文件路径中的一部分
  • 如一个文件路径名是 “/test/testfile” ,则包含的目录项为:
    • 根目录 “/” ,目录 “test” 和文件 “testfile” ,这三个都是目录项
  • 一般而言,目录项包含目录项的名字(文件名或目录名)和目录项的索引节点位置

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

1
2
3
4
struct sfs_disk_entry {
uint32_t ino; /* inode编号 */
char name[SFS_MAX_FNAME_LEN + 1]; /* 文件名称 */
};

dentry(具体为struct sfs_disk_entry)就是一个内存实体:其中的 ino 成员指向对应的 inode number,另外一个成员是 file name

  • 文件(file)

它主要从进程的角度描述了一个进程在访问文件时需要了解的文件标识,文件读写的位置,文件引用情况等信息,它的作用范围是某一具体进程

下面是用于描述文件的结构体:file(又称打开的文件描述)

1
2
3
4
5
6
7
8
9
10
11
struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status;
bool readable; /* 读权限标记位 */
bool writable; /* 写权限标记位 */
int fd; /* 文件描述符 */
off_t pos; /* 当前读取位置(下一次写入的起始位置) */
struct inode *node; /* 文件系统中与硬盘特定区域所对应的结点(索引节点inode) */
int open_count; /* 打开的引用次数 */
};

上面的 file 只是对一个文件而言,对于一个进程(用户)来说,可以同时处理多个文件,所以需要另一个结构来管理所有的 files:

1
2
3
4
5
6
struct files_struct {
struct inode *pwd; // 当前工作目录的inode
struct file *fd_array; // 打开的文件数组(存放file结构体)
int files_count; // 打开的文件数
semaphore_t files_sem; // 对应的信号量
};

文件系统-文件系统层

从 VFS 向下一层,就是 SFS(Simple FS,文件系统层,简称 SFS)

ucore 内核把所有文件都看作是字节流,任何内部逻辑结构都是专用的,由应用程序负责解释,但是 ucore 区分文件的物理结构,ucore 目前支持如下几种类型的文件:

  • 常规文件:文件中包括的内容信息是由应用程序输入,SFS 文件系统在普通文件上不强加任何内部结构,把其文件内容信息看作为字节
  • 目录:包含一系列的 entry,每个 entry 包含文件名和指向与之相关联的索引节点(index node)的指针,目录是按层次结构组织的
  • 链接文件:实际上一个链接文件是一个已经存在的文件的另一个可选择的文件名
  • 设备文件:不包含数据,但是提供了一个映射物理设备(如串口、键盘等)到一个文件名的机制,可通过设备文件访问外围设备
  • 管道:管道是进程间通讯的一个基础设施,管道缓存了其输入端所接受的数据,以便在管道输出端读的进程能一个先进先出的方式来接受数据

SFS 文件系统中目录和常规文件具有共同的属性,而这些属性保存在索引节点中,SFS 通过索引节点来管理目录和常规文件,索引节点包含操作系统所需要的关于某个文件的关键信息,比如文件的属性、访问许可权以及其它控制信息都保存在索引节点中

函数接口与数据结构

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
void sfs_init(void);
int sfs_mount(const char *devname);

void lock_sfs_fs(struct sfs_fs *sfs);
void lock_sfs_io(struct sfs_fs *sfs);
void unlock_sfs_fs(struct sfs_fs *sfs);
void unlock_sfs_io(struct sfs_fs *sfs);

int sfs_rblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
int sfs_wblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
int sfs_rbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
int sfs_wbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
int sfs_sync_super(struct sfs_fs *sfs);
int sfs_sync_freemap(struct sfs_fs *sfs);
int sfs_clear_block(struct sfs_fs *sfs, uint32_t blkno, uint32_t nblks);

int sfs_load_inode(struct sfs_fs *sfs, struct inode **node_store, uint32_t ino);

static int sfs_sync(struct fs *fs);
static struct inode* sfs_get_root(struct fs *fs) ;
static int sfs_unmount(struct fs *fs);
static void sfs_cleanup(struct fs *fs);
static int fs_init_read(struct device *dev, uint32_t blkno, void *blk_buffer);
static int fs_do_mount(struct device *dev, struct fs **fs_store);

/* PS:函数定义多的一批,这里就不挂了 */

SFS 中涉及到了两种文件系统结构,分别是 fssfs_fs

  • fs 结构是我们 在上层函数调用中所直接操作 的抽象文件系统结构
  • sfs_fs 结构则是 在下层函数中所使用的

在原先 sfs_fs 上抽象出一层 fs 结构有助于忽略不同文件系统的差异,其实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
struct fs { /* 上层函数直接操作 */
union {
struct sfs_fs __sfs_info;
} fs_info; // 特定于文件系统的数据
enum {
fs_type_sfs_info,
} fs_type; // 文件系统的类型
int (*fs_sync)(struct fs *fs); // 将所有脏缓冲区刷新到磁盘
struct inode *(*fs_get_root)(struct fs *fs); // 返回文件系统的根索引节点
int (*fs_unmount)(struct fs *fs); // 尝试卸载文件系统
void (*fs_cleanup)(struct fs *fs); // 清理文件系统
};
1
2
3
4
5
6
7
8
9
10
11
12
struct sfs_fs { /* 对接下层函数 */
struct sfs_super super; /* 磁盘上超级块 */
struct device *dev; /* 指向设备安装位置 */
struct bitmap *freemap; /* 正在使用的块被标记为'0' */
bool super_dirty; /* 是否修改了super/freemap */
void *sfs_buffer; /* 用于non-block对齐的缓冲区 */
semaphore_t fs_sem; /* fs的信号量 */
semaphore_t io_sem; /* io的信号量 */
semaphore_t mutex_sem; /* 用于链接/取消链接和重命名的信号量 */
list_entry_t inode_list; /* 索引节点链表(inode链表) */
list_entry_t *hash_list; /* 索引节点哈希链表 */
};
  • sfs_fs 结构中包含了底层设备的超级块 superblock 、所挂载的设备 dev 、以及底层设备中用于表示空间分配情况的 freemap

文件系统布局

文件系统通常保存在磁盘上

在本实验中,第三个磁盘(即 disk0,前两个磁盘分别是 ucore.img 和 swap.img)用于存放一个SFS文件系统(Simple Filesystem),通常文件系统中,磁盘的使用是以扇区(Sector)为单位的,但是为了实现简便,SFS 中以 block (4K,与内存 page 大小相等)为基本单位

SFS文件系统的布局如下:

1
2
3
+------------+----------+---------+-------------------------------------+
| superblock | root-dir | freemap | Inode / File Data / Dir Data blocks |
+------------+----------+---------+-------------------------------------+
  • 第0个块是超级块(superblock)
    • 它包含了关于文件系统的所有关键参数,当计算机被启动或文件系统被首次接触时,超级块的内容就会被装入内存
  • 第1个块放了一个 root-dir 的 inode,用来记录根目录的相关信息
    • root-dir 是 SFS 文件系统的根结点
    • 通过这个 root-dir 的 inode 信息就可以定位并查找到根目录下的所有文件信息
  • 从第2个块开始,根据 SFS 中所有块的数量,用1个bit来表示一个块的占用和未被占用的情况
    • 这个区域称为 SFS 的 freemap 区域,这将占用若干个块空间(为了更好地记录和管理 freemap 区域)
  • 最后在剩余的磁盘空间中,存放了所有其他目录和文件的inode信息和内容数据信息
    • 需要注意的是:虽然 inode 的大小小于一个块的大小(4096B),但为了实现简单,每个 inode 都占用一个完整的 block

文件系统-外设接口层

再底层一点就是 I/O 设备的相关实现,例如结构体 device

1
2
3
4
5
6
7
8
struct device {
size_t d_blocks;
size_t d_blocksize;
int (*d_open)(struct device *dev, uint32_t open_flags);
int (*d_close)(struct device *dev);
int (*d_io)(struct device *dev, struct iobuf *iob, bool write);
int (*d_ioctl)(struct device *dev, int op, void *data);
};

该结构体支持对块设备、字符设备的表示,完成对设备的基本操作

结构体 device 只表示了一个设备所能使用的功能,我们需要一个数据结构用于将 device 和 fs 关联,同时,为了将连接的所有设备连接在一起,uCore定义了一个链表,通过该链表即可访问到所有设备,而这就是定义 vfs_dev_t 结构体的目的:

1
2
3
4
5
6
7
typedef struct {
const char *devname; /* 结构体device的名称 */
struct inode *devnode; /* 结构体device的索引节点(inode) */
struct fs *fs; /* 被关联的fs结构(上层函数直接操作的接口) */
bool mountable;
list_entry_t vdev_link; /* vdev链表 */
} vfs_dev_t;

文件系统挂载流程

一个文件系统在使用前,需要将其挂载至内核中(使一个存储设备上的计算机文件和目录,可供用户通过计算机的文件系统访问的一个过程),在 uCore 里,硬盘 disk0 的挂载流程如下:

程序会先执行 fs_init 函数:

1
2
3
4
5
6
void
fs_init(void) {
vfs_init(); /* 初始化vfs虚拟文件系统 */
dev_init(); /* 初始化dev外设接口层 */
sfs_init(); /* 初始化sfs文件系统 */
}
  • vfs_init:初始化 vfs 虚拟文件系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static semaphore_t bootfs_sem; /* fs文件系统的信号量 */
static semaphore_t vdev_list_sem; /* vdev链表的信号量 */
static list_entry_t vdev_list; /* vdev链表头 */

void
vfs_init(void) {
sem_init(&bootfs_sem, 1); /* 初始化fs信号量 */
vfs_devlist_init(); /* 初始化vdev结构体 */
}

void
vfs_devlist_init(void) {
list_init(&vdev_list); /* 初始化vdev链表 */
sem_init(&vdev_list_sem, 1); /* 初始化vdev链表信号量 */
}

void
sem_init(semaphore_t *sem, int value) {
sem->value = value; /* 初始化value(信号量关键整数) */
wait_queue_init(&(sem->wait_queue)); /* 初始化信号量等待队列 */
}
  • dev_init:初始化 dev 外设接口层
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
void
dev_init(void) {
init_device(stdin);
init_device(stdout);
init_device(disk0); /* 我们只关注这一个 */
}

#define init_device(x) \
do { \
extern void dev_init_##x(void); \
dev_init_##x(); \
} while (0) /* 这个循环八成是为了效率 */

void
dev_init_disk0(void) { /* 初始化disk0(超级块) */
struct inode *node;
if ((node = dev_create_inode()) == NULL) { /* 为dev创建inode */
panic("disk0: dev_create_node.\n");
}
disk0_device_init(vop_info(node, device)); /* 根据inode初始化dev */

int ret;
if ((ret = vfs_add_dev("disk0", node, 1)) != 0) { /* 把该dev添加到vdev链表 */
panic("disk0: vfs_add_dev: %e.\n", ret);
}
}
  • sfs_init:初始化 sfs 文件系统
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
void
sfs_init(void) {
int ret;
if ((ret = sfs_mount("disk0")) != 0) { /* 挂载sfs文件系统 */
panic("failed: sfs: sfs_mount: %e.\n", ret);
}
}

int
sfs_mount(const char *devname) {
return vfs_mount(devname, sfs_do_mount); /* 挂载vfs虚拟文件系统 */
}

int
vfs_mount(const char *devname, int (*mountfunc)(struct device *dev, struct fs **fs_store)) { /* 执行完毕后,文件系统就成功挂载了 */
int ret;
lock_vdev_list();
vfs_dev_t *vdev;
if ((ret = find_mount(devname, &vdev)) != 0) {
goto out;
}
if (vdev->fs != NULL) {
ret = -E_BUSY;
goto out;
}
assert(vdev->devname != NULL && vdev->mountable);

struct device *dev = vop_info(vdev->devnode, device);
if ((ret = mountfunc(dev, &(vdev->fs))) == 0) { /* 执行sfs_do_mount(挂载函数) */
assert(vdev->fs != NULL);
cprintf("vfs: mount %s.\n", vdev->devname);
}

out:
unlock_vdev_list();
return ret;
}

调用流程为:

1
sfs_init -> sfs_mount -> vfs_mount -> sfs_do_mount

sfs_do_mount 挂载函数会执行以下几个操作:

  • 从待挂载设备中读取超级块,并验证超级块中,魔数与总块数是否存在错误
  • 初始化哈希链表
  • 从待挂载设备中读入 freemap 并测试其正确性
  • 设置 fs 结构的相关信息,并在函数最后将该信息设置为传入的 device 结构体中的 fs 成员变量

文件打开流程

用户进程调用 open 函数时,调用链如下:

1
open -> sysfile_open -> file_open(包含vfs_open)

file_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
int
file_open(char *path, uint32_t open_flags) {
bool readable = 0, writable = 0;
switch (open_flags & O_ACCMODE) {
case O_RDONLY: readable = 1; break;
case O_WRONLY: writable = 1; break;
case O_RDWR:
readable = writable = 1;
break;
default:
return -E_INVAL;
}

int ret;
struct file *file;
if ((ret = fd_array_alloc(NO_FD, &file)) != 0) {
/* 在当前进程的文件管理结构filesp中,获取一个空闲的file对象 */
return ret;
}

struct inode *node;
if ((ret = vfs_open(path, open_flags, &node)) != 0) {
/* 调用vfs_open函数,并存储该函数返回的inode结构 */
fd_array_free(file);
return ret;
}

file->pos = 0;
if (open_flags & O_APPEND) {
/* 如果打开方式是append,则还会设置file的pos成员为当前文件的大小 */
struct stat __stat, *stat = &__stat;
if ((ret = vop_fstat(node, stat)) != 0) {
vfs_close(node);
fd_array_free(file);
return ret;
}
file->pos = stat->st_size;
}
/* 根据上一步返回的inode,设置file对象的属性 */
file->node = node;
file->readable = readable;
file->writable = writable;
fd_array_open(file);
return file->fd; /* 返回file->fd */
}

vfs_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
int
vfs_open(char *path, uint32_t open_flags, struct inode **node_store) {
bool can_write = 0;
switch (open_flags & O_ACCMODE) {
case O_RDONLY:
break;
case O_WRONLY:
case O_RDWR:
can_write = 1;
break;
default:
return -E_INVAL;
}

if (open_flags & O_TRUNC) {
if (!can_write) {
return -E_INVAL;
}
}

int ret;
struct inode *node;
bool excl = (open_flags & O_EXCL) != 0;
bool create = (open_flags & O_CREAT) != 0;
ret = vfs_lookup(path, &node); /* 调用vfs_lookup搜索给出的路径,判断是否存在该文件,如果存在,则vfs_lookup函数(sfs_lookup)返回该文件所对应的inode节点 */

if (ret != 0) {
/* 如果给出的路径不存在,即文件不存在,则根据传入的flag,选择调用vop_create创建新文件或直接返回错误信息 */
if (ret == -16 && (create)) {
char *name;
struct inode *dir;
if ((ret = vfs_lookup_parent(path, &dir, &name)) != 0) {
return ret;
}
ret = vop_create(dir, name, excl, &node);
} else return ret;
} else if (excl && create) {
return -E_EXISTS;
}
assert(node != NULL);

if ((ret = vop_open(node, open_flags)) != 0) {
/* 调用vop_open函数(sfs_openfile)尝试打开文件(打开文件的主体) */
vop_ref_dec(node);
return ret;
}

vop_open_inc(node);
if (open_flags & O_TRUNC || create) {
/* 如果文件打开正常,则根据当前函数传入的open_flags参数来判断是否需要将当前文件截断至'0'(即清空) */
if ((ret = vop_truncate(node, 0)) != 0) {
/* 如果需要截断,则执行vop_truncate函数(sfs_truncfile) */
vop_open_dec(node);
vop_ref_dec(node);
return ret;
}
}
*node_store = node;
return 0;
}

文件读取流程

用户进程调用 read 函数时,调用链如下:

1
read -> sysfile_read -> file_read -> sfs_read -> sfs_io -> sfs_io_nolock

file_read 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int
file_read(int fd, void *base, size_t len, size_t *copied_store) {
int ret;
struct file *file;
*copied_store = 0;
if ((ret = fd2file(fd, &file)) != 0) {
return ret;
}
if (!file->readable) { /* 测试当前待读取的文件是否存在读权限 */
return -E_INVAL;
}
fd_array_acquire(file);

struct iobuf __iob, *iob = iobuf_init(&__iob, base, len, file->pos); /* 在内核中创建一块缓冲区 */
ret = vop_read(file->node, iob); /* 实际上执行sfs_read,将数据读取至缓冲区iob */

size_t copied = iobuf_used(iob);
if (file->status == FD_OPENED) {
file->pos += copied;
}
*copied_store = copied;
fd_array_release(file);
return ret;
}

file_read 中涉及到 IO 缓冲区,在 ucore 中,IO 缓冲区由如下结构体进行管理:

1
2
3
4
5
6
struct iobuf {
void *io_base; // IO缓冲区的内存地址
off_t io_offset; // 当前读取/写入的地址
size_t io_len; // 缓冲区的大小
size_t io_resid; // 剩余尚未读取/写入的内存空间
};

file_read 会进一步调用 vop_read,将数据读取至缓冲区 iob,最终调用 sfs_io_nolock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int
sfs_read(struct inode *node, struct iobuf *iob) {
return sfs_io(node, iob, 0);
}

static inline int
sfs_io(struct inode *node, struct iobuf *iob, bool write) {
struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs);
struct sfs_inode *sin = vop_info(node, sfs_inode);
int ret;
lock_sin(sin);
{
size_t alen = iob->io_resid;
ret = sfs_io_nolock(sfs, sin, iob->io_base, iob->io_offset, &alen, write);
if (alen != 0) {
/* 果当前缓冲区中存在尚未读取/写入的数据,则跳过该部分数据 */
iobuf_skip(iob, alen); /* 写入/读取至该块数据的下一个地址处 */
}
}
unlock_sin(sin);
return ret;
}
  • sfs_io_nolock 函数将在练习1中详细讲解

练习0-把 lab7 的内容复制粘贴到 lab8

练习1-完成读文件操作的实现

用户进程调用 read 函数时,调用链如下:

1
read -> sysfile_read -> file_read -> sfs_read -> sfs_io -> sfs_io_nolock

前面几个函数都可以跳过了,我们的任务就是补全最后一个函数:

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
static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
struct sfs_disk_inode *din = sin->din;
assert(din->type != SFS_TYPE_DIR);
/* 计算缓冲区读取/写入的终止位置 */
off_t endpos = offset + *alenp, blkoff;
*alenp = 0;
if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos) {
return -E_INVAL;
}
if (offset == endpos) {
/* 如果偏移与终止位置相同,及欲读取/写入0字节的数据 */
return 0;
}
if (endpos > SFS_MAX_FILE_SIZE) {
endpos = SFS_MAX_FILE_SIZE;
}
if (!write) {
if (offset >= din->size) {
/* 如果是读取数据,并冲区中剩余的数据超出一个硬盘节点的数据大小 */
return 0;
}
if (endpos > din->size) {
endpos = din->size;
}
}

/* 根据不同的执行函数,设置对应的函数指针 */
int (*sfs_buf_op)(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
int (*sfs_block_op)(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
if (write) {
sfs_buf_op = sfs_wbuf, sfs_block_op = sfs_wblock;
}
else {
sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock;
}

int ret = 0;
size_t size, alen = 0;
uint32_t ino;
uint32_t blkno = offset / SFS_BLKSIZE; /* Rd/Wr起始块的编号 */
uint32_t nblks = endpos / SFS_BLKSIZE - blkno; /* Rd/Wr块的大小 */

/* <---- start ----> */

/* <---- end ----> */

out:
*alenp = alen;
if (offset + alen > sin->din->size) {
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}

实现过程:

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
static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
struct sfs_disk_inode *din = sin->din;
assert(din->type != SFS_TYPE_DIR);
/* 计算缓冲区读取/写入的终止位置 */
off_t endpos = offset + *alenp, blkoff;
*alenp = 0;
if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos) {
return -E_INVAL;
}
if (offset == endpos) {
/* 如果偏移与终止位置相同,及欲读取/写入0字节的数据 */
return 0;
}
if (endpos > SFS_MAX_FILE_SIZE) {
endpos = SFS_MAX_FILE_SIZE;
}
if (!write) {
if (offset >= din->size) {
/* 如果是读取数据,并冲区中剩余的数据超出一个硬盘节点的数据大小 */
return 0;
}
if (endpos > din->size) {
endpos = din->size;
}
}

/* 根据不同的执行函数,设置对应的函数指针 */
int (*sfs_buf_op)(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
int (*sfs_block_op)(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
if (write) {
sfs_buf_op = sfs_wbuf, sfs_block_op = sfs_wblock;
}
else {
sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock;
}

int ret = 0;
size_t size, alen = 0;
uint32_t ino;
uint32_t blkno = offset / SFS_BLKSIZE; /* Rd/Wr起始块的编号 */
uint32_t nblks = endpos / SFS_BLKSIZE - blkno; /* Rd/Wr块的大小 */

/* <---- start ----> */

if ((blkoff = offset % SFS_BLKSIZE) != 0) {
/* 对齐偏移,如果偏移没有对齐第一个基础块,则多读取/写入第一个基础块的末尾数据 */
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
/* 获取第一个基础块所对应的block的编号ino */
goto out;
}
if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {
/* 通过上一步取出的ino,读取/写入一部分第一个基础块的末尾数据 */
goto out;
}
alen += size;
if (nblks == 0) {
goto out;
}
buf += size, blkno ++, nblks --;
}

size = SFS_BLKSIZE;
while (nblks != 0) { /* 循环读取/写入对齐好的数据 */
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
/* 获取inode对应的基础块编号 */
goto out;
}
if ((ret = sfs_block_op(sfs, buf, ino, 1)) != 0) {
/* 单次读取/写入一基础块的数据 */
goto out;
}
alen += size, buf += size, blkno ++, nblks --;
}

if ((size = endpos % SFS_BLKSIZE) != 0) {
/* 如果末尾位置没有与最后一个基础块对齐,则多读取/写入一点末尾基础块的数据 */
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0) {
goto out;
}
alen += size;
}

/* <---- end ----> */

out:
*alenp = alen;
if (offset + alen > sin->din->size) {
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}

练习2-完成基于文件系统的执行程序机制的实现

基于文件系统的执行程序机制,有几部分地方需要添加代码,分别是 alloc_procdo_forkload_icode 三个函数

  • alloc_proc:分配一个 proc_struct,用于描述进程的信息(在之前实验已经实现过了)
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
static struct proc_struct *
alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->runs = 0;
proc->kstack = 0;
proc->need_resched = 0;
proc->parent = NULL;
proc->mm = NULL;
memset(&(proc->context), 0, sizeof(struct context));
proc->tf = NULL;
proc->cr3 = boot_cr3;
proc->flags = 0;
memset(proc->name, 0, PROC_NAME_LEN);
proc->wait_state = 0;
proc->cptr = proc->optr = proc->yptr = NULL;
proc->rq = NULL;
list_init(&(proc->run_link));
proc->time_slice = 0;
proc->lab6_run_pool.left = proc->lab6_run_pool.right = proc->lab6_run_pool.parent = NULL;
proc->lab6_stride = 0;
proc->lab6_priority = 0;
proc->filesp = NULL; /* lab8新添:proc->filesp,用于描述进程的文件相关信息 */
}
return proc;
}

新增的条目为 files_struct 结构体:(用于在进程中管理多个 file 结构体)

1
2
3
4
5
6
struct files_struct {
struct inode *pwd; // 当前工作目录的inode
struct file *fd_array; // 打开的文件数组(存放file结构体)
int files_count; // 打开的文件数
semaphore_t files_sem; // 对应的信号量
};
  • do_fork:创建当前内核线程的一个副本,它们的执行上下文、代码、数据都一样,但是存储位置不同,在这个过程中,需要给新内核线程分配资源,并且复制原进程的状态(在之前实验已经实现过了)
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
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
int ret = -E_NO_FREE_PROC;
struct proc_struct *proc;
if (nr_process >= MAX_PROCESS) {
goto fork_out;
}
ret = -E_NO_MEM;
if ((proc = alloc_proc()) == NULL) { /* 分配一个物理页,作为proc_struct */
goto fork_out;
}

proc->parent = current; /* 设置父进程为当前进程 */
assert(current->wait_state == 0); /* lab5新增:断言进程为等待态 */

if (setup_kstack(proc) != 0) { /* 分配内核栈 */
goto bad_fork_cleanup_proc;
}
if (copy_fs(clone_flags, proc) != 0) { /* lab8新添:将当前进程的fs复制到,fork出的进程中 */
goto bad_fork_cleanup_kstack;
}
if (copy_mm(clone_flags, proc) != 0) { /* 将所有虚拟页数据复制过去 */
goto bad_fork_cleanup_fs;
}
copy_thread(proc, stack, tf); /* 复制线程的状态,包括寄存器上下文等等 */

bool intr_flag;
local_intr_save(intr_flag); /* 阻塞中断 */
{
proc->pid = get_pid(); /* 为进程分配唯一的PID */
hash_proc(proc); /* 将proc添加到进程哈希链表中 */
set_links(proc); /* lab5改动:取消list_add,采用set_links */
}
local_intr_restore(intr_flag); /* 解除中断的阻塞 */
wakeup_proc(proc); /* 设置新的子进程可执行(唤醒该进程) */
ret = proc->pid; /* 设置返回值为pid */

fork_out:
return ret;

bad_fork_cleanup_fs: /* lab8新添:goto标志位 */
put_fs(proc);
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
  • load_icode:加载并解析一个处于内存中的ELF执行文件格式的应用程序(函数改动较大)
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
static int
load_icode(int fd, int argc, char **kargv) {
// fd:对应的文件描述符
// argc:传入load_icode函数的参数个数
// kargv:传入的各个参数
assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);

if (current->mm != NULL) { /* 检查当前进程是否为NULL */
panic("load_icode: current->mm must be empty.\n");
}

int ret = -E_NO_MEM;
struct mm_struct *mm; /* mm_struct结构体:用于描述虚拟内存区域(vma)的各种信息 */
if ((mm = mm_create()) == NULL) { /* 创建一片虚拟内存 */
goto bad_mm; /* 创建失败,直接返回 */
}
if (setup_pgdir(mm) != 0) { /* 新建一个页目录表,每个进程都需要一个页目录表 */
goto bad_pgdir_cleanup_mm; /* 创建失败,执行mm_destroy */
}

struct Page *page;
struct elfhdr __elf, *elf = &__elf; /* lab8改动:获取的二进制文件的基础信息 */

if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {
/* 获取的二进制文件的文件头 */
goto bad_elf_cleanup_pgdir;
}

if (elf->e_magic != ELF_MAGIC) { /* 检查该程序的魔数是否正确 */
ret = -E_INVAL_ELF;
goto bad_elf_cleanup_pgdir;
}

/* <---- 遍历程序头表,并构建vma ----> */

struct proghdr __ph, *ph = &__ph;
uint32_t vm_flags, perm, phnum;
for (phnum = 0; phnum < elf->e_phnum; phnum ++) { /* 遍历整个程序头表(ph就是各个段头表) */
off_t phoff = elf->e_phoff + sizeof(struct proghdr) * phnum;
if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), phoff)) != 0) {
goto bad_cleanup_mmap;
}
if (ph->p_type != ELF_PT_LOAD) {
/* 遍历寻找到ELF_PT_LOAD为止,在ucore中,该段是TEXT/DATA */
continue ;
}
if (ph->p_filesz > ph->p_memsz) {
/* 文件中段的大小 > 内存中段的大小 */
/* 内存中p_memsz大于p_filesz的原因是,可加载段可能包含一个.bss部分,没有此部分则是等于状态,绝对不可能是小于状态 */
ret = -E_INVAL_ELF;
goto bad_cleanup_mmap; /* 调用exit_mmap */
}
if (ph->p_filesz == 0) {
continue ;
}

/* <---- 根据标志位进行初始化,准备构建vma ----> */

vm_flags = 0, perm = PTE_U;
if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
if (vm_flags & VM_WRITE) perm |= PTE_W;
if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
/* 调用mm_map,为目标段构建新的vma */
goto bad_cleanup_mmap; /* 调用exit_mmap */
}

/* <---- 建立并分配页目录表,复制TEXT/DATA段到进程的内存(建立映射) ----> */

off_t offset = ph->p_offset; /* 获取TEXT/DATA的段地址 */
size_t off, size;
uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);
/* start:初始化为段起始地址(映射段的虚拟地址) */
/* la(可变参数):start进行内存页对齐后(只舍不进)的地址 */

ret = -E_NO_MEM;
end = ph->p_va + ph->p_filesz;
/* end:初始化为段结束地址(映射段的虚拟地址+文件中段的大小) */

while (start < end) { /* 持续为pgdir分配页表,直到整个段都完成映射 */
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
/* 分配一块物理页(作为页表),设置页表项(对应la),插入页表目录(pgdir) */
ret = -E_NO_MEM;
goto bad_cleanup_mmap;
}
off = start - la; /* 更新偏移 */
/* 第一次: off='start为了页对齐而舍弃的数值'(正) */
/* 后续: off='0' */
size = PGSIZE - off; /* 更新已分配的段大小(每次增加PGSIZE) */
la += PGSIZE; /* 更新当前的物理地址(每次增加PGSIZE) */

if (end < la) {
size -= la - end; /* 获取准确的段大小 */
}
if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0) {
/* 获取页目录表的虚拟地址,通过off计算出对应页目录表项,用memcpy在其中填入from(TEXT/DATA段的起始地址) */
goto bad_cleanup_mmap;
}
start += size, offset += size;
/* 第一次: start增加的值比la小一些 */
/* 后续: start和la都增加相同的值(PGSIZE),并且地址也相同 */
}

/* <---- 分配内存,建立并分配页目录表,建立BSS段(建立映射) ----> */

end = ph->p_va + ph->p_memsz; /* end:初始化为段结束地址(映射段的虚拟地址+内存中段的大小) */

if (start < la) {
if (start == end) { /* start最后会小于等于la,以下代码就是为了当"start<la"时,实现"start=la",并且置空原start距新start多出的部分 */
continue ;
}
off = start + PGSIZE - la, size = PGSIZE - off;
if (end < la) {
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
/* 获取页目录表的虚拟地址,通过off计算出对应页目录表项,并使用memset置空 */
start += size;
assert((end < la && start == end) || (end >= la && start == la));
}

while (start < end) { /* 持续为pgdir分配页表,直到整个段都完成映射 */
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
ret = -E_NO_MEM;
goto bad_cleanup_mmap; /* 调用exit_mmap */
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la) {
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
/* 获取页目录表的虚拟地址,通过off计算出对应页目录表项,并使用memset置空 */
start += size;
}
}

/* <---- 构建用户堆栈内存 ----> */

sysfile_close(fd);
vm_flags = VM_READ | VM_WRITE | VM_STACK;
if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
goto bad_cleanup_mmap; /* 调用exit_mmap */
}
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);

/* <---- 设置当前进程的mm,sr3,设置CR3寄存器 ----> */

mm_count_inc(mm); /* 设置并返回"共享该虚拟内存空间mva的进程数" */
current->mm = mm; /* 设置当前进程的"proc_struct->mm"为该虚拟内存空间mva */
current->cr3 = PADDR(mm->pgdir); /* 设置当前进程的"proc_struct->cr3"为该页目录表的地址 */
lcr3(PADDR(mm->pgdir)); /* 设置CR3寄存器为当前页目录表的物理地址 */

/* <---- lab8新增:设置execve所启动的程序参数 ----> */

uint32_t argv_size=0, i;
for (i = 0; i < argc; i ++) {
argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
}

uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long)+1)*sizeof(long);
char** uargv=(char **)(stacktop - argc * sizeof(char *));

argv_size = 0;
for (i = 0; i < argc; i ++) { /* 直接将传入的参数压入至新栈的底部 */
uargv[i] = strcpy((char *)(stacktop + argv_size ), kargv[i]);
argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
}

stacktop = (uintptr_t)uargv - sizeof(int);
*(int *)stacktop = argc;

/* <---- 为用户环境设置trapframe ----> */

struct trapframe *tf = current->tf; /* 构建中断帧 */
memset(tf, 0, sizeof(struct trapframe)); /* 把trapframe清零 */
tf->tf_cs = USER_CS; /* 初始化中断帧的各个条目 */
tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
tf->tf_esp = stacktop;
tf->tf_eip = elf->e_entry;
tf->tf_eflags = FL_IF;
ret = 0;

out:
return ret;
bad_cleanup_mmap:
exit_mmap(mm);
bad_elf_cleanup_pgdir:
put_pgdir(mm);
bad_pgdir_cleanup_mm:
mm_destroy(mm);
bad_mm:
goto out;
}