0%

从ucore来总结操作系统(8)----文件系统

文件系统

关于ucore的文件系统,可以参考ucore的risc-v的实验指导书,写的很有逻辑且详细

0X00 环境准备

本lab基于前所有lab,但是需要对其进行一些扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

static struct proc_struct *alloc_proc(void)
{
// 初始化 PCB 下的 fs(进程相关的文件信息)
proc->filesp = NULL;
}

int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf)
{
// 使用 copy_files()函数复制父进程的fs到子进程中
if (copy_files(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}
}

因为proc_struct新增了一个文件相关的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

struct proc_struct
{
...
struct files_struct *filesp;
...
}

struct files_struct
{
// 当前工作目录
struct inode *pwd; // inode of present working directory
// 文件描述符(数组)
struct file *fd_array; // opened files array
// 打开的文件数目
int files_count; // the number of opened files
// 用于文件加锁的信号量
semaphore_t files_sem; // lock protect sem
};

由于文件系统比较复杂,我们还是先来通过实验指导书来宏观对ucore的文件系统做初步了解。

总体介绍

ucore的文件系统架构主要由四部分组成:

  • 通用文件系统访问接口层:该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务。
  • 文件系统抽象层:向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问。向下提供一个同样的抽象函数指针列表和数据结构屏蔽不同文件系统的实现细节。
  • Simple FS文件系统层:一个基于索引方式的简单文件系统实例。向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数。向下访问外设接口
  • 外设接口层:向上提供device访问接口屏蔽不同硬件细节。向下实现访问各种具体设备驱动的接口,比如disk设备接口/串口设备接口/键盘设备接口等。

对照上面的层次我们再大致介绍一下文件系统的访问处理过程,加深对文件系统的总体理解。假如应用程序操作文件(打开/创建/删除/读写),首先需要通过文件系统的通用文件系统访问接口层给用户空间提供的访问接口进入文件系统内部,接着由文件系统抽象层把访问请求转发给某一具体文件系统(比如SFS文件系统),具体文件系统(Simple FS文件系统层)把应用程序的访问请求转化为对磁盘上的block的处理请求,并通过外设接口层交给磁盘驱动例程来完成具体的磁盘操作。结合用户态写文件函数write的整个执行过程,我们可以比较清楚地看出ucore文件系统架构的层次和依赖关系。

从ucore操作系统不同的角度来看,ucore中的文件系统架构包含四类主要的数据结构, 它们分别是:

  • 超级块(SuperBlock):它主要从文件系统的全局角度描述特定文件系统的全局信息。它的作用范围是整个OS空间。
  • 索引节点(inode)它主要从文件系统的单个文件的角度它描述了文件的各种属性和数据所在位置。它的作用范围是整个OS空间。
  • 目录项(dentry):它主要从文件系统的文件路径的角度描述了文件路径中的一个特定的目录项(注:一系列目录项形成目录/文件路径)。它的作用范围是整个OS空间。对于SFS而言,inode(具体为struct sfs_disk_inode)对应于物理磁盘上的具体对象,dentry(具体为struct sfs_disk_entry)是一个内存实体,其中的ino成员指向对应的inode number,另外一个成员是file name(文件名).
  • 文件(file),它主要从进程的角度描述了一个进程在访问文件时需要了解的文件标识,文件读写的位置,文件引用情况等信息。它的作用范围是某一具体进程。

如果一个用户进程打开了一个文件,那么在ucore中涉及的相关数据结构和关系如下图所示:

层次结构

通用文件系统访问接口层

我们把上面刚出现的proc_struct和files_struct重新看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

struct proc_struct
{
...
struct files_struct *filesp;
...
}

struct files_struct
{
// 当前工作目录
struct inode *pwd; // inode of present working directory
// 文件描述符(数组)
struct file *fd_array; // opened files array
// 打开的文件数目
int files_count; // the number of opened files
// 用于文件加锁的信号量
semaphore_t files_sem; // lock protect sem
};

prco结构体中的files_struct结构体指针指向当前程序打开的所有文件,因此files_struct结构体用于描述多个打开的文件。而在files_struct结构体内部,存在一个file结构体数组用于存放打开的所有文件,也存在一个inode结构体指针指向当前工作目录。

如果将proc结构体看作是一个一个线程、那么files_struct结构体就可以看成是通用文件系统访问接口层的抽象,基于此,用户(线程)可以使用open、close等通用系统调用接口来管理文件。

通用文件系统访问接口层的抽象(大致看一下就行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

int sysfile_open(const char *path, uint32_t open_flags); // Open or create a file. FLAGS/MODE per the syscall.
int sysfile_close(int fd); // Close a vnode opened
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); // change DIR
int sysfile_mkdir(const char *path); // create DIR
int sysfile_link(const char *path1, const char *path2); // set a path1's link as path2
int sysfile_rename(const char *path1, const char *path2); // rename file
int sysfile_unlink(const char *path); // unlink a path
int sysfile_getcwd(char *buf, size_t len); // get current working directory
int sysfile_getdirentry(int fd, struct dirent *direntp); // get the file entry in DIR
int sysfile_dup(int fd1, int fd2); // duplicate file
int sysfile_pipe(int *fd_store); // build PIPE
int sysfile_mkfifo(const char *name, uint32_t open_flags); // build named PIPE

通用文件系统访问接口层的抽象(更底层API)(大致看一下就行)

在这些sysfile_xx函数中,调用的下一层函数分别是封装好的各个file_xx函数

1
2
3
4
5
6
7
8
9
10
11
12
13

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

文件系统抽象层

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

系统接口再下一层就到了VFS虚拟文件系统。VFS函数涉及到了文件结构体file和inode结构体。

然后我们看一下file结构体和inode结构体:

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

struct file
{
// 状态值,描述文件打开、关闭、初始化等
enum
{
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status;
// 读写权限
bool readable;
bool writable;
// 文件描述符
int fd;
// 下一次写入的起始位置
off_t pos;
// 文件的i结点数组
struct inode *node;
// 打开此文件的次数
int open_count;
};


struct inode
{
// 包含不同文件系统特定inode信息的union成员变量
union
{
//设备文件系统内存inode信息
struct device __device_info;
//SFS文件系统内存inode信息
struct sfs_inode __sfs_inode_info;
} in_info;

// 此inode所属文件系统类型
enum
{
inode_type_device_info = 0x1234,
inode_type_sfs_inode_info,
} in_type;
// 此inode的引用计数
atomic_t ref_count;
// 打开此inode对应文件的个数
atomic_t open_count;
// 抽象的文件系统,包含访问文件系统的函数指针
struct fs *in_fs;
// 抽象的inode操作,包含访问inode的函数指针
const struct inode_ops *in_ops;
};

file结构体指定了文件的相关类型,包括读写权限,文件描述符fd,当前读取到的位置pos,文件系统中与硬盘特定区域所对应的结点node,以及打开的引用次数open_count。index node是位于内存的索引节点,它是VFS结构中的重要数据结构,因为它实际负责把不同文件系统的特定索引节点信息(甚至不能算是一个索引节点)统一封装起来,避免了进程直接访问具体文件系统。

inode结构体中存放了info、类型type、引用次数ref_count、打开次数open_count、相关联的文件系统in_fs以及当前结构所对应的操作集合in_ops。该结构与硬盘上对应区域相关联,从而便于对硬盘进行操作。

inode_ops成员是对常规文件、目录、设备文件所有操作的一个抽象函数表示。对于某一具体的文件系统中的文件或目录,只需实现相关的函数,就可以被用户进程访问具体的文件了,且用户进程无需了解具体文件系统的实现细节。

file结构体对应的接口(大致看一下就行)
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

/*
* Virtual File System layer functions.
*
* The VFS layer translates operations on abstract on-disk files or
* pathnames to operations on specific files on specific filesystems.
*/
void vfs_init(void);
void vfs_cleanup(void);
void vfs_devlist_init(void);

/*
* VFS layer low-level operations.
* See inode.h for direct operations on inodes.
* See fs.h for direct operations on filesystems/devices.
*
* vfs_set_curdir - change current directory of current thread by inode
* vfs_get_curdir - retrieve inode of current directory of current thread
* vfs_get_root - get root inode for the filesystem named DEVNAME
* vfs_get_devname - get mounted device name for the filesystem passed in
*/
int vfs_set_curdir(struct inode *dir);
int vfs_get_curdir(struct inode **dir_store);
int vfs_get_root(const char *devname, struct inode **root_store);
const char *vfs_get_devname(struct fs *fs);

/*
* VFS layer high-level operations on pathnames
* Because namei may destroy pathnames, these all may too.
*
* vfs_open - Open or create a file. FLAGS/MODE per the syscall.
* vfs_close - Close a inode opened with vfs_open. Does not fail.
* (See vfspath.c for a discussion of why.)
* vfs_link - Create a hard link to a file.
* vfs_symlink - Create a symlink PATH containing contents CONTENTS.
* vfs_readlink - Read contents of a symlink into a uio.
* vfs_mkdir - Create a directory. MODE per the syscall.
* vfs_unlink - Delete a file/directory.
* vfs_rename - rename a file.
* vfs_chdir - Change current directory of current thread by name.
* vfs_getcwd - Retrieve name of current directory of current thread.
*
*/
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);
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);

/*
* VFS layer mid-level operations.
*
* vfs_lookup - Like VOP_LOOKUP, but takes a full device:path name,
* or a name relative to the current directory, and
* goes to the correct filesystem.
* vfs_lookparent - Likewise, for VOP_LOOKPARENT.
*
* Both of these may destroy the path passed in.
*/
int vfs_lookup(char *path, struct inode **node_store);
int vfs_lookup_parent(char *path, struct inode **node_store, char **endp);

/*
* Misc
*
* vfs_set_bootfs - Set the filesystem that paths beginning with a
* slash are sent to. If not set, these paths fail
* with ENOENT. The argument should be the device
* name or volume name for the filesystem (such as
* "lhd0:") but need not have the trailing colon.
*
* vfs_get_bootfs - return the inode of the bootfs filesystem.
*
* vfs_add_fs - Add a hardwired filesystem to the VFS named device
* list. It will be accessible as "devname:". This is
* intended for filesystem-devices like emufs, and
* gizmos like Linux procfs or BSD kernfs, not for
* mounting filesystems on disk devices.
*
* vfs_add_dev - Add a device to the VFS named device list. If
* MOUNTABLE is zero, the device will be accessible
* as "DEVNAME:". If the mountable flag is set, the
* device will be accessible as "DEVNAMEraw:" and
* mountable under the name "DEVNAME". Thus, the
* console, added with MOUNTABLE not set, would be
* accessed by pathname as "con:", and lhd0, added
* with mountable set, would be accessed by
* pathname as "lhd0raw:" and mounted by passing
* "lhd0" to vfs_mount.
*
* vfs_mount - Attempt to mount a filesystem on a device. The
* device named by DEVNAME will be looked up and
* passed, along with DATA, to the supplied function
* MOUNTFUNC, which should create a struct fs and
* return it in RESULT.
*
* vfs_unmount - Unmount the filesystem presently mounted on the
* specified device.
*
* vfs_unmountall - Unmount all mounted filesystems.
*/
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);

// vfs中更为底层的函数
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);
};

文件系统抽象层VFS提供了file接口、dir接口、inode接口、fs接口以及外设接口。而这些接口在sfs中被具体实现。

Simple FS 文件系统层

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

/*
* Abstract filesystem. (Or device accessible as a file.)
*
* Information:
* fs_info : filesystem-specific data (sfs_fs)
* fs_type : filesystem type
* Operations:
*
* fs_sync - Flush all dirty buffers to disk.
* fs_get_root - Return root inode of filesystem.
* fs_unmount - Attempt unmount of filesystem.
* fs_cleanup - Cleanup of filesystem.???
*
*
* fs_get_root should increment the refcount of the inode returned.
* It should not ever return NULL.
*
* If fs_unmount returns an error, the filesystem stays mounted, and
* consequently the struct fs instance should remain valid. On success,
* however, the filesystem object and all storage associated with the
* filesystem should have been discarded/released.
*
*/
struct fs
{
union
{
struct sfs_fs __sfs_info;
} fs_info; // filesystem-specific data
enum
{
fs_type_sfs_info,
} fs_type; // filesystem type
int (*fs_sync)(struct fs *fs); // Flush all dirty buffers to disk
struct inode *(*fs_get_root)(struct fs *fs); // Return root inode of filesystem.
int (*fs_unmount)(struct fs *fs); // Attempt unmount of filesystem.
void (*fs_cleanup)(struct fs *fs); // Cleanup of filesystem.???
};

/* filesystem for sfs */
struct sfs_fs
{
struct sfs_super super; /* on-disk superblock */
struct device *dev; /* device mounted on */
struct bitmap *freemap; /* blocks in use are mared 0 */
bool super_dirty; /* true if super/freemap modified */
void *sfs_buffer; /* buffer for non-block aligned io */
semaphore_t fs_sem; /* semaphore for fs */
semaphore_t io_sem; /* semaphore for io */
semaphore_t mutex_sem; /* semaphore for link/unlink and rename */
list_entry_t inode_list; /* inode linked-list */
list_entry_t *hash_list; /* inode hash linked-list */
};

sfs_fs结构中包含了底层设备的超级块superblock、所挂载的设备dev、以及底层设备中用于表示空间分配情况的freemap等。fs结构是我们在上层函数调用中所直接操作的抽象文件系统,而sfs_fs则是在下层函数中所使用的。在原先sfs_fs上抽象出一层fs结构有助于忽略不同文件系统的差异。

对应的接口为(大致看一下就行)
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

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);
// ......

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

1
2
3
4
5

+------------+----------+---------+-------------------------------------+
| superblock | root-dir | freemap | Inode / File Data / Dir Data blocks |
+------------+----------+---------+-------------------------------------+

  • 第0个块(4K)是超级块(superblock),它包含了关于文件系统的所有关键参数,当计算机被启动或文件系统被首次接触时,超级块的内容就会被装入内存。其定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13

/*
* On-disk superblock
*/
struct sfs_super
{
// 超级块结构中包含成员变量魔数magic,内核通过它来检查磁盘镜像是否是合法的 SFS img
uint32_t magic; /* magic number, should be SFS_MAGIC */
uint32_t blocks; /* # of blocks in fs */
uint32_t unused_blocks; /* # of unused blocks in fs */
char info[SFS_MAX_INFO_LEN + 1]; /* infomation for sfs */
};

  • 第1个块放了一个root-dir的inode,用来记录根目录的相关信息。root-dir是SFS文件系统的根结点,通过这个root-dir的inode信息就可以定位并查找到根目录下的所有文件信息。
  • 从第2个块开始,根据SFS中所有块的数量,用1个bit来表示一个块的占用和未被占用的情况。这个区域称为SFS的freemap区域,这将占用若干个块空间。为了更好地记录和管理freemap区域
  • 最后在剩余的磁盘空间中,存放了所有其他目录和文件的inode信息和内容数据信息。需要注意的是虽然inode的大小小于一个块的大小(4096B),但为了实现简单,每个 inode 都占用一个完整的 block。

索引结点

在sfs层面上,inode结构既可表示文件file、目录dir,也可表示设备device。而区分inode结构的操作有两种,一种是其in_info成员变量,另一种是该结构的成员指针in_ops。当uCore创建一个用于存储文件/目录的inode结构(即该inode结构的in_info成员变量为sfs_inode类型)时,程序会执行函数sfs_create_inode。该函数会将inode结构中的sfs_inode成员与磁盘对应结点sfs_disk_inode相关联,从而使得只凭inode即可操作该结点。

而sfs_disk_inode结构记录了文件或目录的内容存储的索引信息,该数据结构在硬盘里储存,需要时读入内存。type成员表明该结构是目录类型还是文件类型,又或者是链接link类型。如果inode表示的是文件,则成员变量direct[]直接指向了保存文件内容数据的数据块索引值。indirect指向的是间接数据块,此数据块实际存放的全部是数据块索引,这些数据块索引指向的数据块才被用来存放文件内容数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

/* file types */
#define SFS_TYPE_INVAL 0 /* Should not appear on disk */
#define SFS_TYPE_FILE 1
#define SFS_TYPE_DIR 2
#define SFS_TYPE_LINK 3
/* inode (on disk) */
struct sfs_disk_inode
{
// 如果inode表示常规文件,则size是文件大小
uint32_t size;
// inode的文件类型
uint16_t type;
// 此inode的硬链接数
uint16_t nlinks;
// 此inode的数据块数的个数
uint32_t blocks;
// 此inode的直接数据块索引值(有SFS_NDIRECT个)
uint32_t direct[SFS_NDIRECT];
// 此inode的一级间接数据块索引值
uint32_t indirect;
};

对于普通文件,索引值指向的 block 中保存的是文件中的数据。而对于目录,索引值指向的数据保存的是目录下所有的文件名以及对应的索引节点所在的索引块(磁盘块)所形成的数组。即

1
2
3
4
5
6
7
8
9
10

/* file entry (on disk) */
struct sfs_disk_entry
{
// 索引节点所占数据块索引值
uint32_t ino;
// 文件名
char name[SFS_MAX_FNAME_LEN + 1];
};

还有内存索引点,保存在内存中的索引结点

1
2
3
4
5
6
7
8
9
10
11
12
13

/* inode for sfs */
struct sfs_inode
{
struct sfs_disk_inode *din; /* on-disk inode */
uint32_t ino; /* inode number */
bool dirty; /* true if inode modified */
int reclaim_count; /* kill inode if it hits zero */
semaphore_t sem; /* semaphore for din */
list_entry_t inode_link; /* entry for linked-list in sfs_fs */
list_entry_t hash_link; /* entry for hash linked-list in sfs_fs */
};

SFS中的内存sfs_inode除了包含SFS的硬盘sfs_disk_inode信息,而且还增加了其他一些信息。这些信息用于判断相关硬盘位置是否改写、互斥操作、回收和快速地定位等作用。

需要注意的是,一个内存sfs_inode是在打开一个文件后才创建的,如果关机则相关信息都会消失。而硬盘sfs_disk_inode的内容是保存在硬盘中的,只是在进程需要时才被读入到内存中,用于访问文件或目录的具体内容数据

还有文件结点, 用于指向磁盘索引结点的结点,其结构如下

1
2
3
4
5
6
7
8

/* file entry (on disk) */
struct sfs_disk_entry
{
uint32_t ino; /* inode number */
char name[SFS_MAX_FNAME_LEN + 1]; /* file name */
};

文件结点中的name表示当前文件的文件名,而其ino成员则指向了sfs_disk_inode磁盘索引结点。上一层的目录索引结点则会指向各个下层的文件结点。

同时,为了方便实现上面提到的多级数据的访问以及目录中 entry 的操作,对于inode,SFS实现了一些辅助的函数,它们分别是

  • sfs_bmap_load_nolock : 将对应sfs_inode的第 index 个索引指向的 block 的索引值取出,并存到相应的指针指向的单元(ino_store)。如果index == din->blocks, 则将会为inode增长一个 block。并标记 inode 为 dirty
  • sfs_bmap_truncate_nolock : 将多级数据索引表的最后一个 entry 释放掉。该函数可以认为是sfs_bmap_load_nolock中,index == inode->blocks的逆操作。
  • sfs_dirent_read_nolock : 将目录的第 slot 个 entry 读取到指定的内存空间。
  • sfs_dirent_search_nolock : 该函数是常用的查找函数,函数会在目录下查找 name,并且返回相应的搜索结果(文件或文件夹)的 inode 的编号(也是磁盘编号),和相应的 entry 在该目录的 index 编号以及目录下的数据页是否有空闲的 entry。

需要注意的是,这些后缀为nolock的函数,只能在已经获得相应inode的semaphore才能调用。

外设接口层(I/O设备)

在底层一点就是I/O设备的相关实现,这些部分直接基于驱动来实现。

文件系统挂载流程

一个文件系统在使用前,需要将其挂载至内核中。在uCore里,硬盘disk0的挂载流程如下:

  • 首先,在fs_init函数中执行init_device(disk0),初始化对应device结构并将其连接至vdev_list链表中:
  • 之后,在fs_init函数中执行sfs_init() -> sfs_mount("disk0")
  • 紧接着,sfs_mount会调用vfs_mount,在vfs的挂载接口中调用sfs自己的sfs_do_mount挂载函数。sfs_do_mount挂载函数会执行以下几个操作
    • 从待挂载设备中读取超级块,并验证超级块中,魔数与总块数是否存在错误
    • 初始化哈希链表
    • 从待挂载设备中读入freemap并测试其正确性
    • 设置fs结构的相关信息,并在函数最后将该信息设置为传入的device结构体中的fs成员变量

0X01 完成读文件操作的实现

我们还是先来看一下这张图:

打开文件流程

首先假定用户进程需要打开的文件已经存在在硬盘上。当调用open函数打开一个文件时,首先进入通用文件访问接口层的处理流程,即进一步调用如下用户态函数: open->sys_open->syscall,从而引起系统调用进入到内核态。到了内核态后,通过中断处理例程,会调用到sys_open内核函数,并进一步调用sysfile_open内核函数。到了这里,需要把位于用户空间的字符串"sfs_filetest1"拷贝到内核空间中的字符串path中,并进入到文件系统抽象层的处理流程完成进一步的打开文件操作中。

在文件系统抽象层中,需要调用的是file_open函数,它要给这个即将打开的文件分配一个file数据结构的变量,这个变量其实是当前进程的打开文件数组current->fs_struct->filemap[]中的一个空闲元素(即还没用于一个打开的文件),而这个元素的索引值就是最终要返回到用户进程并赋值给变量fd1。到了这一步还仅仅是给当前用户进程分配了一个file数据结构的变量,还没有找到对应的文件索引节点。为此需要进一步调用vfs_open函数来找到path指出的文件所对应的基于inode数据结构的VFS索引节点node。vfs_open函数需要完成两件事情:通过vfs_lookup找到path对应文件的inode;调用vop_open函数打开文件:

  • 找到文件设备的根目录“/”的索引节点需要注意,这里的vfs_lookup函数是一个针对目录的操作函数,它会调用vop_lookup函数来找到SFS文件系统中的“/”目录下的“sfs_filetest1”文件。为此,vfs_lookup函数首先调用get_device函数,并进一步调用vfs_get_bootfs函数(其实调用了)来找到根目录“/”对应的inode。这个inode就是位于vfs.c中的inode变量bootfs_node。这个变量在init_main函数(位于kern/process/proc.c)执行时获得了赋值。
  • 通过调用vop_lookup函数来查找到根目录“/”下对应文件sfs_filetest1的索引节点,,如果找到就返回此索引节点。
  • 把file和node建立联系。完成第3步后,将返回到file_open函数中,通过执行语句“file->node=node;”,就把当前进程的current->fs_struct->filemap[fd](即file所指变量)的成员变量node指针指向了代表sfs_filetest1文件的索引节点inode。这时返回fd。经过重重回退,通过系统调用返回,用户态的syscall->sys_open->open->safe_open等用户函数的层层函数返回,最终把把fd赋值给fd1。自此完成了打开文件操作。但这里我们还没有分析第2和第3步是如何进一步调用SFS文件系统提供的函数找位于SFS文件系统上的sfs_filetest1文件所对应的sfs磁盘inode的过程。

当线程打开文件后尝试读取文件,则先进入通用文件访问接口层的处理流程,即进一步调用如下用户态函数:read->sys_read->syscall,从而引起系统调用进入到内核态。到了内核态以后,通过中断处理例程,会调用到sys_read内核函数,并进一步调用sysfile_read内核函数,进入到文件系统抽象层处理流程完成进一步读文件的操作。

在文件系统抽象层中,会检查错误并调用kmalloc分配4096字节的buffer空间。当读取时,先检查剩余部分大小,保证除最后一次外其余都是按照4096字节读,这部分调用file_read来实现。由于在内核态读取,所以需要将其拷贝到用户内存空间中。

我们顺着file_read往下看,主要执行read的函数是vop_read(vop_read函数实际上是对sfs_read的包装),sfs_read函数直接调用sfs_io函数,而该函数将进一步调用sfs_io_nolock。sfs_io_nolock正是我们需要实现的函数。

注释中已经提醒我们,sys_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
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

/*
* sfs_io_nolock - Rd/Wr a file contentfrom offset position to offset+ length disk blocks<-->buffer (in memroy)
* @sfs: sfs file system
* @sin: sfs inode in memory
* @buf: the buffer Rd/Wr
* @offset: the offset of file
* @alenp: the length need to read (is a pointer). and will RETURN the really Rd/Wr lenght
* @write: BOOL, 0 read, 1 write
*/
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;
// calculate the Rd/Wr end position
// 计算缓冲区读取/写入的终止位置
if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos)
{
return -E_INVAL;
}
// 如果偏移与终止位置相同,即欲读取/写入0字节的数据则直接返回
if (offset == endpos)
{
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; // The NO. of Rd/Wr begin block
uint32_t nblks = endpos / SFS_BLKSIZE - blkno; // The size of Rd/Wr blocks

// LAB8:EXERCISE1 YOUR CODE HINT: call sfs_bmap_load_nolock, sfs_rbuf, sfs_rblock,etc. read different kind of blocks in file
/*
* (1) If offset isn't aligned with the first block, Rd/Wr some content from offset to the end of the first block
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
* Rd/Wr size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset)
* (2) Rd/Wr aligned blocks
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_block_op
* (3) If end position isn't aligned with the last block, Rd/Wr some content from begin to the (endpos % SFS_BLKSIZE) of the last block
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
*/
// 对齐偏移。如果偏移没有对齐第一个基础块,则多读取/写入第一个基础块的末尾数据
if ((blkoff = offset % SFS_BLKSIZE) != 0)
{
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
// 获取第一个基础块所对应的block的编号ino
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0)
{
goto out;
}

// 通过上一步取出的ino,读取/写入一部分第一个基础块的末尾数据
if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0)
{
goto out;
}
alen += size;
if (nblks == 0)
{
goto out;
}
buf += size, blkno++, nblks--;
}

// 循环读取/写入对齐好的数据
size = SFS_BLKSIZE;
while (nblks != 0)
{
// 获取inode对应的基础块编号
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0)
{
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;
}
out:
*alenp = alen;
if (offset + alen > sin->din->size)
{
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}

0X02 基于文件系统的执行程序机制的实现

我们先回忆一下之前是怎么加载程序的:

  • 首先把用户程序随内核编译,放在img的最后
  • 内核启动后,会把用户程序加载到内存中
  • 内核线程调用execve函数来加载位于内存中的用户线程,切换上下文然后执行。

之前没有文件系统,故不能从磁盘上加载程序;而有了文件系统以后,可以使用文件系统来把程序加载到内存里,然后像之前一样执行即可。

但是由于多了文件系统,proc结构体啥的都发生了变化,我们需要做一些修正:

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

static struct proc_struct * alloc_proc(void)
{
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL)
{
// Lab7内容
// ...

// LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ...
// LAB8 添加一个filesp指针的初始化
proc->filesp = NULL;
}
return proc;
}

// 多了file_struct结构的复制操作与执行失败时的重置操作
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)
{
goto fork_out;
}
proc->parent = current;
assert(current->wait_state == 0);

if (setup_kstack(proc) != 0)
{
goto bad_fork_cleanup_proc;
}
// LAB8:EXERCISE2 YOUR CODE HINT:how to copy the fs in parent's proc_struct?
// LAB8 将当前进程的fs复制到fork出的进程中
if (copy_files(clone_flags, proc) != 0)
{
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();
hash_proc(proc);
set_links(proc);
}
local_intr_restore(intr_flag);

wakeup_proc(proc);

ret = proc->pid;
fork_out:
return ret;
// LAB8 如果复制失败,则需要重置原先的操作
bad_fork_cleanup_fs: // for LAB8
put_files(proc);
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}

然后我们来正式实现execve和相关函数。通过前面lab我们知道load_icode函数完成了整个execve过程复杂的工作,所以我们在此也要继续修正该函数:

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

// load_icode - called by sys_exec-->do_execve
static int load_icode(int fd, int argc, char **kargv)
{
/* LAB8:EXERCISE2 YOUR CODE HINT:how to load the file with handler fd in to process's memory? how to setup argc/argv?
* MACROs or Functions:
* mm_create - create a mm
* setup_pgdir - setup pgdir in mm
* load_icode_read - read raw data content of program file
* mm_map - build new vma
* pgdir_alloc_page - allocate new memory for TEXT/DATA/BSS/stack parts
* lcr3 - update Page Directory Addr Register -- CR3
*/
/* (1) create a new mm for current process
* (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
* (3) copy TEXT/DATA/BSS parts in binary to memory space of process
* (3.1) read raw data content in file and resolve elfhdr
* (3.2) read raw data content in file and resolve proghdr based on info in elfhdr
* (3.3) call mm_map to build vma related to TEXT/DATA
* (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file
* and copy them into the new allocated pages
* (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
* (4) call mm_map to setup user stack, and put parameters into user stack
* (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO)
* (6) setup uargc and uargv in user stacks
* (7) setup trapframe for user environment
* (8) if up steps failed, you should cleanup the env.
*/
assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);

if (current->mm != NULL)
{
panic("load_icode: current->mm must be empty.\n");
}

int ret = -E_NO_MEM;
// 创建proc的内存管理结构
struct mm_struct *mm;
if ((mm = mm_create()) == NULL)
{
goto bad_mm;
}
if (setup_pgdir(mm) != 0)
{
goto bad_pgdir_cleanup_mm;
}

struct Page *page;
// LAB8 这里要从文件中读取ELF header,而不是Lab7中的内存了
struct elfhdr __elf, *elf = &__elf;
if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0)
{
goto bad_elf_cleanup_pgdir;
}
// 判断读取入的elf header是否正确
if (elf->e_magic != ELF_MAGIC)
{
ret = -E_INVAL_ELF;
goto bad_elf_cleanup_pgdir;
}
// 根据每一段的大小和基地址来分配不同的内存空间
struct proghdr __ph, *ph = &__ph;
uint32_t vm_flags, perm, phnum;
for (phnum = 0; phnum < elf->e_phnum; phnum++)
{
// LAB8 从文件特定偏移处读取每个段的详细信息(包括大小、基地址等等)
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)
{
continue;
}
if (ph->p_filesz > ph->p_memsz)
{
ret = -E_INVAL_ELF;
goto bad_cleanup_mmap;
}
if (ph->p_filesz == 0)
{
continue;
}
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)
{
goto bad_cleanup_mmap;
}
off_t offset = ph->p_offset;
size_t off, size;
uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);

ret = -E_NO_MEM;

end = ph->p_va + ph->p_filesz;
while (start < end)
{
// 设置该内存所对应的页表项
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL)
{
ret = -E_NO_MEM;
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la)
{
size -= la - end;
}
// LAB8 读取elf对应段内的数据并写入至该内存中
if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0)
{
goto bad_cleanup_mmap;
}
start += size, offset += size;
}
end = ph->p_va + ph->p_memsz;
// 对于段中当前页中剩余的空间(复制elf数据后剩下的空间),将其置为0
if (start < la)
{
/* ph->p_memsz == ph->p_filesz */
if (start == end)
{
continue;
}
off = start + PGSIZE - la, size = PGSIZE - off;
if (end < la)
{
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
start += size;
assert((end < la && start == end) || (end >= la && start == la));
}
// 对于段中剩余页中的空间(复制elf数据后的多余页面),将其置为0
while (start < end)
{
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL)
{
ret = -E_NO_MEM;
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la)
{
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
start += size;
}
}
// 关闭读取的ELF
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;
}
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_count_inc(mm);
// 设置CR3页表相关寄存器
current->mm = mm;
current->cr3 = PADDR(mm->pgdir);
lcr3(PADDR(mm->pgdir));

// setup argc, argv
// 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;

struct trapframe *tf = current->tf;
memset(tf, 0, sizeof(struct 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;
}

0X03 总结

说实话,文件系统本身就很复杂,这里还掺杂了虚拟文件系统的概念,难度挺大,主要就是认识一下。我自己是基于现有的驱动(其实是伪驱动,不基于ucore),额外实现了一个不带虚拟文件系统的简易文件系统,这样的话理解似乎会更深一点。