ucore Lab8 文件系统

练习0:填写已有实验

本实验依赖实验1/2/3/4/5/6/7。请把你做的实验1/2/3/4/5/6/7的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”/“LAB5”/“LAB6” /“LAB7”的注释相应部分。并确保编译通过。注意:为了能够正确执行lab8的测试应用程序,可能需对已完成的实验1/2/3/4/5/6/7的代码进行进一步改进。

不太需要改写什么

练习1: 完成读文件操作的实现(需要编码)

文件打开大致流程

  1. 用户态应用程序:
    • 用户态的应用程序通过系统调用(例如 open 系统调用)来请求内核打开文件。
  2. 系统调用接口层:
    • 在系统调用接口层,例如 sys_open 函数,内核获取用户态应用程序传递的参数,例如文件路径名(path)和打开标志(open_flags)。
  3. 虚拟文件系统层(VFS):
    • 在 VFS 层,内核根据传递的文件路径名找到对应的文件系统(例如 SFS 文件系统)。
    • VFS 层会调用具体文件系统的打开函数(例如 sfs_open)来进行文件的打开操作。
  4. 文件系统层(例如 SFS 文件系统):
    • 在文件系统层,打开函数(例如 sfs_open)会获取文件路径名对应的 inode,并创建 struct file 结构体,用于表示打开的文件。该结构体包含了与文件相关的信息,如文件的 inode、文件读写位置等。
    • 打开函数还可能进行权限检查,确保应用程序有权访问该文件。
  5. 返回文件描述符:
    • 在 VFS 层,打开文件后会为该文件创建一个文件描述符,并返回给用户态应用程序。文件描述符是一个整数,用于标识文件的打开状态。
  6. 应用程序的文件操作:
    • 用户态应用程序在获取到文件描述符后,可以使用该文件描述符进行文件读写等操作,通过系统调用(例如 readwrite)来请求内核进行相应的操作。

下面给出读操作有关sfs_io_nolock函数的调用关系

  1. 用户程序调用 sys_read 系统调用:
    • 用户程序通过 sys_read 系统调用来请求读取文件内容。
  2. 系统调用处理:
    • 内核根据系统调用号,将控制权转交给系统调用处理函数 sys_read
  3. 文件描述符解析:
    • sys_read 函数解析文件描述符,并找到对应的 struct file 结构体。
  4. 文件读取调用:
    • sys_read 函数调用文件读取函数 file_read
    • file_read 函数首先检查文件读取权限,并调用文件系统抽象层函数 vfs_read
  5. 虚拟文件系统抽象层(VFS):
    • vfs_read 函数根据文件描述符,调用 VFS 层的文件读取函数 vop_read
    • vop_read 函数根据文件节点(struct vnode)类型,调用不同文件系统的文件读取函数,如 sfs_read(用于 SFS 文件系统)。
  6. SFS 文件系统读取:
    • sfs_read 函数根据文件节点中的文件读写偏移量(文件指针),找到相应的磁盘块,并调用 sfs_io_nolock 函数读取磁盘块的数据。
  7. sfs_io_nolock
    • sfs_io_nolock 函数负责实际的读取磁盘块操作。
    • 它调用 sfs_read_block 函数从磁盘中读取指定的数据块。
  8. 磁盘读取:
    • sfs_read_block 函数利用设备驱动层的接口函数,将磁盘块数据从硬盘读取到内存缓冲区。
  9. 数据传递:
    • sfs_read_block 函数将读取的数据传递给上层的 sfs_read 函数。
  10. 数据传递至文件读取函数:
    • sfs_read 函数将读取的数据传递给 vop_read 函数。
  11. 数据传递至系统调用函数:
    • vop_read 函数将读取的数据传递给 vfs_read 函数。
  12. 数据传递至系统调用处理函数:
    • vfs_read 函数将读取的数据传递给 sys_read 函数。
  13. 返回用户空间:
    • sys_read 函数将读取的数据传递回用户程序空间,并返回读取的字节数。

sfs_io_nolock 实现

static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write)
{
    bool aligned =  0;
    if ((blkoff = offset % SFS_BLKSIZE) != 0)
    {
        aligned = 1;
        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;   buf += size;
        if (nblks == 0)   goto out;  
    }
    if(nblks - aligned > 0){
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno + aligned, &ino)) != 0){
            goto out;
        }
        if ((ret = sfs_block_op(sfs, buf,  ino, nblks - aligned)) != 0){
            goto out;
        }
        buf += (nblks - aligned) * SFS_BLKSIZE;
        alen += (nblks - aligned) * SFS_BLKSIZE;
    }

    if ((size = endpos % SFS_BLKSIZE) != 0 ){
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno + nblks, &ino)) != 0){
            goto out;
        }
        if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0){
            goto out;
        }
        alen += size;
    }

  1. 首先获取文件的 struct sfs_disk_inode 结构体指针 din,并对文件进行合法性检查,如文件类型不为目录等。
  2. 计算文件读写的结束位置 endpos,并进行参数合法性检查,确保读写操作在有效的范围内。
  3. 根据读写类型(write),选择对应的缓冲区操作函数 sfs_buf_op 和块操作函数 sfs_block_op,分别对应于读操作和写操作。
  4. 接下来,根据文件读写的开始位置 offset 和结束位置 endpos,按块为单位进行读写操作。
  5. 首先,处理未对齐的部分,即当前块的偏移不为0的情况。如果当前偏移不为0,则先读写当前块剩余的数据。
  6. 然后,处理对齐的整块数据,根据起始块号和需要读写的块数,调用相应的函数读写整块数据。
  7. 最后,处理结束位置不为块末尾的情况,即剩余部分不足一块的数据,再次调用相应函数读写剩余的数据。
  8. 在整个过程中,根据读写的大小,更新已读写数据的长度 alen
  9. 如果读写操作结束后,文件大小发生变化,则更新文件大小,并标记文件节点为脏(dirty)。
  10. 最后,返回读写操作的结果。如果操作成功,返回0,否则返回错误码。
练习2: 完成基于文件系统的执行程序机制的实现(需要编码)

这个写起来真的好多好难啊,代码还是不放了,答案也有(鄙人不会,只能抄答案),下面给出一些该函数实现流程吧

  1. 首先,检查当前进程的 mm 是否为空。mm 是进程的内存管理结构,用于管理进程的虚拟地址空间。如果当前进程的 mm 不为空,说明内存管理结构已经被占用,这意味着当前进程已经运行了其他程序或者已经加载了其他程序,因此抛出 panic,表示异常情况。
  2. 创建新的 mm 结构,用于存放当前进程的虚拟地址空间。首先,调用 mm_create() 函数创建一个新的 mm 结构,并将返回的指针存储在 mm 变量中。
  3. 为新的 mm 结构设置页目录表(PDT),通过 setup_pgdir() 函数进行页表项的设置。该函数的目的是为了为用户程序提供一个独立的虚拟地址空间,并将用户程序的代码、数据和栈等内容映射到不同的物理页上。
  4. 解析 ELF 可执行文件头部,读取 ELF 文件的信息,并进行合法性检查。首先,定义一个 elf 结构体和一个指向该结构体的指针 elfp,用于存储 ELF 文件的头部信息。然后,通过 load_icode_read() 函数从磁盘上读取 ELF 可执行文件的头部信息到 elfp 中。接着,检查 ELF 文件的魔数是否正确,如果不正确则说明该 ELF 文件不合法,抛出 -E_INVAL_ELF 错误。
  5. 遍历 ELF 可执行文件的 program header 表,对 TEXT/DATA/BSS 段进行处理。首先,定义一个 ph 结构体和一个指向该结构体的指针 php,用于存储 program header 表项的信息。然后,通过循环遍历 ELF 文件的每一个 program header 表项。
  6. 对 TEXT/DATA 段进行分配虚拟地址空间和读取操作。对于每一个 program header 表项,首先检查其类型是否为 ELF_PT_LOAD,表示该段是可加载的段。然后,检查 p_filesz 是否大于 p_memsz,如果是则说明 ELF 文件不合法,抛出 -E_INVAL_ELF 错误。接着,判断 p_filesz 是否为 0,如果为 0 则说明该段没有数据需要读取,直接跳过。如果以上条件都不满足,说明该段需要加载到用户空间。
  7. 为 TEXT/DATA 段在用户空间分配虚拟地址空间,并将对应的数据从 ELF 文件中读取到用户空间。首先,根据 program header 表项中的虚拟地址 p_va 和大小 p_memsz 以及权限信息 p_flags,调用 mm_map() 函数将该段映射到用户空间的合适位置。接着,计算出每一页的偏移和大小,并逐页分配物理内存,并将数据从 ELF 文件中读取到相应的物理页中。
  8. 对 BSS 段进行分配虚拟地址空间和清零操作。对于每一个 program header 表项,同样检查其类型是否为 ELF_PT_LOAD,表示该段是可加载的段。然后,判断 p_memsz 是否大于 p_filesz,如果大于,则说明 BSS 段需要进行清零初始化。接着,为 BSS 段在用户空间分配虚拟地址空间,并将对应的内存空间清零。
  9. 为用户栈设置虚拟地址空间,并将命令行参数和用户栈的布局写入用户空间。首先,定义一些变量来辅助设置用户栈。然后,根据命令行参数计算出所需的栈空间大小,并调用 mm_map() 函数将用户栈映射到用户空间的合适位置。接着,根据命令行参数的长度和个数,将参数写入用户栈中。
  10. 切换当前进程的页目录表到新创建的用户页目录表,完成从内核空间到用户空间的切换。首先,增加新创建的 mm 结构的引用计数,然后将当前进程的 mm 指针指向新创建的 mm 结构,将当前进程的 CR3 寄存器设置为新的页目录表的物理地址,最后通过 lcr3 指令切换到用户态的页目录表。
  11. 最后,设置用户程序的执行环境。首先,设置中断帧 tf,包括代码段和数据段选择子、栈指针 esp、用户程序的入口地址 eip,以及标志寄存器 eflags。然后,返回 0,表示加载用户程序成功。

如果在加载过程中发生错误,会进行相应的清理工作,并返回相应的错误码。这样,load_icode 函数负责将 ELF 可执行文件加载到用户空间中,设置用户程序执行的环境,并切换到用户态,使用户程序开始执行。

顺便解释一下主要elf格式信息

ELF头部(ELF Header):位于文件的开头,用于描述整个ELF文件的结构和属性。包含了与ELF文件有关的一些基本信息,如文件的类型(可执行文件、共享库或目标文件)、目标硬件平台、入口点地址等。

程序头表(Program Header Table):位于ELF头部指定的偏移位置,用于描述可执行程序或共享库中各个程序段(Program Segment)的属性和位置。每个程序段描述了一个逻辑上连续的内存区域,包含了一段代码或数据。程序头表在目标文件中可以不存在,但在可执行文件和共享库中通常存在。

节头表(Section Header Table):位于ELF头部指定的偏移位置,用于描述目标文件中各个节(Section)的属性和位置。节是ELF文件的组成单位,包含了一些编译器产生的信息,如代码、数据、符号表等。

符号表(Symbol Table):位于一个特定的节中,用于描述目标文件中定义和引用的符号(变量、函数等)的信息。符号表提供了在程序中查找和链接符号的功能。

字符串表(String Table):位于一个特定的节中,用于存储目标文件中使用的字符串,如符号表中的符号名称、节的名称等。通过字符串表,可以根据字符串的偏移值找到相应的字符串。

代码段和数据段:ELF文件中的代码段(Code Segment)和数据段(Data Segment)存储了程序的指令和数据。在可执行文件中,代码段包含了程序的机器指令,数据段包含了程序使用的全局变量和常量等数据。

BSS段:位于数据段之后,用于存储未初始化的全局变量和静态变量。BSS段在ELF文件中并不占据实际的存储空间,而是在程序加载到内存时由系统初始化为零

唔,至此,本人对操作系统的方方面面有了大致的框架,也了解到许多底层知识,中断描述符呀,虚拟内存,同步互斥等等,对自己提升真的好多。ucore结束了,但是操作系统还没结束

说实话,本人水平有限,对于ucore很多地方确实还是不太理解,没有很好掌握操作系统的内部机理,只是大致有了一个整体框架,框架内部的细节还是没有填满,最近由于处理事情较多,过段时间写CSAPP的时候,再接再厉,希望到时候我的基础会扎实一些

祝贺你通过自己的努力,完成了ucore OS lab1-lab8!

这段话还是恭喜你们吧,哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DyingLive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值