lab8 — 文件系统

前置知识

设备驱动程序、文件系统、虚拟文件系统。

改动点

相比于 lab7 源代码,lab8 主要做了如下改动:

  • proc.h 扩展 struct 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
    struct proc_struct {
    ... // 同以往结构的属性
    struct files_struct *filesp; // 当前进程的文件集信息
    };

    struct files_struct {
    struct inode *pwd; // 当前进程所在工作目录的 inode
    struct file *fd_array; // 打开文件表
    int files_count; // 共享此 files_struct 的进程个数
    semaphore_t files_sem; // 用于互斥访问 files_struct
    };

    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; // 此文件打开的次数 (此实验中,该字段似乎没什么用。然而,对于 Linux 系统而言,该字段是具有意义的:父子进程共享文件描述符,它们会对应至相同的文件表项,该表项的 open_count 取值会增加)
    };

    在 Linux 系统中,每个打开的文件对应三种数据结构:文件描述符表、打开文件表、inode 表,其中前一者为进程级数据结构,后两者为系统级数据结构。

    ucore 中,每个打开的文件仅对应两种数据结构:打开文件表 (包含文件描述符表的信息)、inode 表,其中前者为进程级数据结构,后者为系统级数据结构。

  • iobuf.[ch] 提供数据读写的内核缓冲区

    1
    2
    3
    4
    5
    6
    struct iobuf {
    void *io_base;
    off_t io_offset;
    size_t io_len;
    size_t io_resid;
    };
  • dev.h 规范设备抽象 (只要设备实现此结构所需内容,该系统便可应用此设备,用于屏蔽底层设备的不同)

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

    值得一说的是,借助于上述内容,我们可以实现如下函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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,
    };
  • inode.h 规范 VFS 层级的 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
    struct inode {
    // inode 对应的实际类型所存放的信息 (若是设备,则存放设备信息;若是特定 FS,则存放其详细的 inode 信息)
    union {
    struct device __device_info;
    struct sfs_inode __sfs_inode_info;
    } in_info;
    // inode 对应的实际类型 (特定设备、特定文件系统)
    enum {
    inode_type_device_info = 0x1234,
    inode_type_sfs_inode_info,
    } in_type;
    // 此 inode 的引用计数
    int ref_count;
    // 打开此 inode 的文件个数
    int open_count;
    // inode 对应的抽象文件系统
    struct fs *in_fs;
    // 抽象 inode 的操作集
    const struct inode_ops *in_ops;
    };

    // VFS 层级,针对 inode 操作的众多定义
    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.h 规范 VFS 层级的 fs 结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    struct fs {
    // 具体文件系统的信息 (此实验仅涉及 sfs)。
    union {
    struct sfs_fs __sfs_info;
    } fs_info;
    // fs 对应的实际文件系统类型
    enum {
    fs_type_sfs_info,
    } fs_type;
    // 针对 fs 的四大操作
    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);
    };

    vfs.h 提供 VFS 层级的、针对文件,路径等内容的众多操作,它们进一步会调用 inode_op->xxx 完成具体功能。

  • sysfile.[ch] 提供关于文件系统调用的内核级封装

    这部分提供的系统调用会进一步调用 VFS 层级的函数,从而实现相关功能。

  • vfsdev.c 提供 vfs_dev_t 结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // ucore 将设备也视为一种文件,因此也将其集成至 VFS。
    // VFS 将设备表示为 vfs_dev_t,并将其串接为一个链表,以方便后续操作。
    typedef struct {
    // 设备名称
    const char *devname;
    // 设备对应的 inode 信息 (十分重要,借助于它,ucore 才能统一设备与文件系统的操作)
    struct inode *devnode;
    // 设备所挂载的文件系统
    struct fs *fs;
    // 设备是否可挂载
    bool mountable;
    // 链接只用
    list_entry_t vdev_link;
    } vfs_dev_t;
  • sfs.h 提供简易文件系统 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
    // sfs superblock 内容的具体结构
    struct sfs_super {
    uint32_t magic; // 唯一标记 sfs
    uint32_t blocks; // sfs 的总块数
    uint32_t unused_blocks; // sfs 尚未使用的块数
    char info[SFS_MAX_INFO_LEN + 1]; // sfs 简介信息
    };

    // sfs inode 内容的具体结构
    struct sfs_disk_inode {
    uint32_t size; // 文件大小 (字节单位)
    uint16_t type; // 文件类型 (文件、目录、链接)
    uint16_t nlinks; // 硬链接数目
    uint32_t blocks; // 文件内容所占块数
    uint32_t direct[SFS_NDIRECT]; // 块索引的直接索引
    uint32_t indirect; // 块索引的一级索引
    };

    // sfs 目录文件内部一项的具体结构
    struct sfs_disk_entry {
    uint32_t ino; // 文件/目录的 inode 号
    char name[SFS_MAX_FNAME_LEN + 1]; // 文件/目录的名称
    };

    // 上述均为数据在硬盘中的组织形式,下述则是数据在内存中的组织形式

    // sfs inode 内容的具体结构 (之所以区分硬盘和内存,一则额外信息需要保存,二则方便某些操作)
    struct sfs_inode {
    struct sfs_disk_inode *din; // 硬盘 inode 的具体信息
    uint32_t ino; // inode 号
    bool dirty; // 此 inode 是否被修改
    int reclaim_count; // 此 inode 待回收数,若其值为 0,需将其写回硬盘
    semaphore_t sem; // 用于互斥访问 sfs_disk_inode
    list_entry_t inode_link; // 链接之用
    list_entry_t hash_link;
    };

    // sfs 文件系统的具体结构
    struct sfs_fs {
    struct sfs_super super; // superblock 信息
    struct device *dev; // 所挂载的设备
    struct bitmap *freemap; // freemap 表示的空闲块信息
    bool super_dirty; // superblock/freemap 是否被修改
    void *sfs_buffer; // 用于从硬盘获取非对齐块信息,以此作为缓冲,并复制给其他缓冲区
    semaphore_t fs_sem; // 用于互斥访问 fs
    semaphore_t io_sem; // 用于互斥访问 io
    semaphore_t mutex_sem; /* semaphore for link/unlink and rename */
    list_entry_t inode_list; // sfs 所管部分 inode 的链表组织形式
    list_entry_t *hash_list; // sfs 所管部分 inode 的 hash 表组织形式
    };
  • sfs_inode.c 提供 SFS 关于 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
    // 注意:这些操作最终要依托于挂载设备的 dev_node_ops 操作而实现。 
    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,
    };
    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,
    };

练习零

该练习用于了解 ucore 文件系统的实现机制与运行流程。

文件系统的实现机制

ucore 文件系统的实现机制详见 fs_init() ,我们对其进行简要分析:

  • vfs_init() 初始化 VFS

    VFS 主要记录两大数据:

    1
    2
    3
    4
    5
    6
    7
    // 设备信息
    static list_entry_t vdev_list;
    static semaphore_t vdev_list_sem;

    // 根目录的 inode 信息
    static struct inode *bootfs_node = NULL;
    static semaphore_t bootfs_sem;

    因此,初始化上述变量即是 vfs_init() 的工作。

  • dev_init() 初始化相关设备

    此处设备指代 stdinstdoutdisk0,在此仅以 stdin 的初始化进行说明。

    stdin 初始化工作主要在于构建 vfs_dev_t,完成相关初始化,并将其加入至 vdev_list

    vfs_dev_t 的详细信息具体如下 (可以看到:各字段均已填充完毕):

    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
    typedef struct {
    const char *devname; --> "stdin"
    struct inode *devnode; --> 下述的 struct inode
    struct fs *fs; --> NULL
    bool mountable; --> false (设备不同,选项不同。对于 disk0,其选择即为 true)
    list_entry_t vdev_link;
    } vfs_dev_t;

    struct inode {
    union {
    struct device __device_info;
    struct sfs_inode __sfs_inode_info;
    } in_info; --> 下述的 struct device
    enum {
    inode_type_device_info = 0x1234,
    inode_type_sfs_inode_info,
    } in_type; --> inode_type_device_info
    int ref_count; --> 1
    int open_count; --> 0
    struct fs *in_fs; --> NULL
    const struct inode_ops *in_ops; --> 上述的 dev_node_ops (重申一次,这些操作具体由 struct device 所定义的四个函数实现)
    };

    struct device {
    size_t d_blocks; --> 0
    size_t d_blocksize; --> 1
    int (*d_open)(struct device *dev, uint32_t open_flags); --> stdin_open
    int (*d_close)(struct device *dev); --> stdin_close
    int (*d_io)(struct device *dev, struct iobuf *iob, bool write); --> stdin_io
    int (*d_ioctl)(struct device *dev, int op, void *data); --> stdin_ioctl
    };
  • sfs_init() 初始化 sfs

    sfs_init() 的工作在于:挂载 sfs 至 disk0,并使用 disk0 硬盘信息初始化 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
    28
    29
    30
    // disk0 所指代的 vfs_dev_t->fs = 下述 struct fs

    struct fs {
    // 具体文件系统的信息 (此实验仅涉及 sfs)。
    union {
    struct sfs_fs __sfs_info;
    } fs_info; --> 下述的 struct sfs_fs
    // fs 对应的实际文件系统类型
    enum {
    fs_type_sfs_info,
    } fs_type; --> fs_type_sfs_info
    // 针对 fs 的四大操作
    int (*fs_sync)(struct fs *fs); --> sfs_sync (此四者为 sfs 的具体实现)
    struct inode *(*fs_get_root)(struct fs *fs); --> sfs_get_root
    int (*fs_unmount)(struct fs *fs); --> sfs_unmount
    void (*fs_cleanup)(struct fs *fs); --> sfs_cleanup
    };

    struct sfs_fs {
    struct sfs_super super; --> disk0 硬盘所存的 superblock
    struct device *dev; --> disk0 所指代的 struct device
    struct bitmap *freemap; --> 新建 freemap,并使用 disk0 硬盘所存信息进行初始化
    bool super_dirty; --> false
    void *sfs_buffer; --> 新建的缓冲区
    semaphore_t fs_sem; --> 初始化若干信号量
    semaphore_t io_sem;
    semaphore_t mutex_sem;
    list_entry_t inode_list; --> 初始化链表
    list_entry_t *hash_list; --> 初始化 hash 表
    };

    注:第二个内核线程 init 的主体实现 init_main() 会设置 disk0 的根目录 inodebootfs_node

至此,ucore 文件系统已然实现。

文件系统的运行流程

使用若干文件操作说明 ucore 文件系统的运行流程:

  • SYS_open

    1
    2
    3
    4
    5
    6
    7
    8
    1. 系统调用 SYS_open 陷入中断,经获取 path/open_flags 参数后,调用 sysfile_open() 进行处理。
    2. sysfile_open() 进一步调用 file_open(),它首先从当前进程的文件集中分配 struct file,并调用 vfs_open() 进行处理。
    3. vfs_open() 进一步调用 vfs_lookup() 去寻找 path 对应文件的 inode。
    1. vfs_lookup() 借助于 get_device() 获取 path 最初目录的 inode (对于路径 "device:xxx" 而言,即是 device 对应的 inode,它可通过遍历 vdev_list 找到;对于路径 "/xxx" 而言,即是 / 对应的 inode,它可通过访问 bootfs_node 得到;对于路径 "xxx",即是工作目录对应的 inode`,它可通过访问 pwd 得到)。
    2. 随后,借助于 vop_lookup() 寻找该目录下对应文件的 inode (该目录的 inode 已知,则可直接调用其具体的 inode_ops)。
    4. 如果没有找到对应文件的 inode,而 open_flags 允许新建,则新建一个 inode (该目录是已知的,则其通过 inode_ops 调用的新建流程会自动设置,新建 inode 的 inode_ops 为该文件系统所允许的 inode_ops),并完成相应的初始化工作。
    5. 使用上述得到的 inode 以及 open_flags 填充 struct file。
    6. 返回 file->fd 给用户进程。
  • SYS_close

    1
    2
    3
    4
    1. 系统调用 SYS_close 陷入中断,经获取 fd 参数后,调用 sysfile_close() 进行处理。
    2. sysfile_close() 进一步调用 file_close(),它首先从当前进程的文件集中获取此 fd 对应的 struct file,并调用 fd_array_close() 进行处理。
    3. fd_array_close() 将 file->open_count 减一,如果此时为 0,则调用 fd_array_free() 进行清理。
    4. fd_array_free() 进一步调用 vfs_close(),它会依据 node->ref_count/open_count 是否为 0,进一步调用 vop_close()/vop_reclaim() 完成善后工作 (由于 inode 已知,同样可直接调用特定文件系统的 inode_ops )。
  • SYS_read

    1
    2
    3
    4
    5
    1. 系统调用 SYS_read 陷入中断,经获取 fd/base/len 参数后,调用 sysfile_read() 进行处理。
    2. sysfile_read() 执行若干预处理操作 (判断是否存在该 fd、分配内核缓冲区),使用 file_read() 进行读取。
    3. file_read() 找到该 fd 对应的 struct file,经过权限是否允许的判断后,使用 vop_read() 读取相关内容至内核缓冲区。
    4. vop_read() 会基于 file-> pos 找到相应的物理块,并进一步调用设备的 dev_node_ops 完成读取操作。
    5. 层层返回,将内核缓冲区的数据拷贝至用户空间 (可能由于待读取的数据很多,它会多次重复执行 3/4/5)。
  • SYS_write

    1
    2
    3
    4
    5
    6
    1. 系统调用 SYS_write 陷入中断,经获取 fd/base/len 参数后,调用 sysfile_write() 进行处理。
    2. sysfile_write() 执行若干预处理操作 (判断是否存在该 fd、分配内核缓冲区、)。
    3. 随后,sysfile_write() 拷贝用户空间数据至内核缓冲区,并使用 file_write() 进行写入。
    4. file_write() 找到该 fd 对应的 struct file,经过权限是否允许的判断后,使用 vop_write() 将内核缓冲区内容写入至相关设备。
    5. vop_write() 会基于 file-> pos 找到相应的物理块,并进一步调用设备的 dev_node_ops 完成写入操作。
    // 可能由于待写入的数据很多,它会多次重复执行 3/4/5。

    练习一

该练习用于实现文件读写的核心函数 sfs_io_nolock()

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
static int sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
... // 未修改处

if ((blkoff = offset % SFS_BLKSIZE) != 0) {
// 判断 endpos 和 offset 是否在同一块中?
// 若为同一块 则 size 为 endpos - offset。
// 若不为同一块 则 size 为 SFS_BLKSIZE - blkoff(偏移) 为 第一块要读的大小。
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
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) {
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;
}

如何实现 UNIX 的 PIPE 机制?

简单来说,PIPE 用于两个进程通信,前者输出放至管道,后者输入取自管道,输入输出并不同步。那么,两个进程应当对应不同的 struct file,但是对应相同的 struct inode (其对应的实际数据应当直接存放于内核之中)。

另外,对于每个进程而言,其 fd_array[0,1,2] 分别指代输入、输出、错误输出。因此,应当修改前者的输出 fd_array[1] 和后者的输入 fd_array[0] 为上述的 struct file

练习二

该练习用于实现程序加载的核心函数 load_icode()

load_icode() 实现具体见源代码 (可类比 lab7 实现此函数):

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
static int load_icode(int fd, int argc, char **kargv) {
if (current->mm != NULL) {
panic("load_icode: current->mm must be empty.\n");
}
int ret = -E_NO_MEM;

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;
struct elfhdr __elf, *elf = &__elf;
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;
}
struct proghdr __ph, *ph = &__ph;

uint32_t i;
uint32_t vm_flags, perm;

for (i = 0; i < elf->e_phnum; ++i) {
if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), elf->e_phoff + sizeof(struct proghdr) * i)) != 0) {
goto bad_elf_cleanup_pgdir;
}
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) {
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la) {
size -= la - end;
}

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;
if (start < la) {
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));
}
while (start < end) {
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
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;
}

}
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);
current->mm = mm;
current->cr3 = PADDR(mm->pgdir);
lcr3(PADDR(mm->pgdir));

// 相比于 lab7 实现,该实现需要妥善放置 argc 和 kargv 至用户栈,因此需要一番计算,使得其尽量减少不必要存储消耗,且能保证 kargv[i] 能顺利索引到相应的字符串。
// 先算出所有参数加起来的长度
uint32_t total_len = 0;
for (i = 0; i < argc; ++i) {
total_len += strnlen(kargv[i], EXEC_MAX_ARG_LEN) + 1;
}

// 用户栈顶 减去所有参数加起来的长度 再 4字节对齐 找到 真正存放字符串参数的栈的位置
char *arg_str = (USTACKTOP - total_len) & 0xfffffffc;
// 放字符串参数的栈的位置的下面 是存放指向字符串参数的指针
int32_t *arg_ptr = (int32_t *)arg_str - argc;
// 指向字符串参数的指针下面 是参数的个数
int32_t *stacktop = arg_ptr - 1;
*stacktop = argc;
for (i = 0; i < argc; ++i) {
uint32_t arg_len = strnlen(kargv[i], EXEC_MAX_ARG_LEN);
strncpy(arg_str, kargv[i], arg_len);
*arg_ptr = arg_str;
arg_str += arg_len + 1;
++arg_ptr;
}

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