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!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/25257.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接口测试——python接口开发(二)

目录 1. python接口开发框架Flask简介与安装 2. 使用Flask开发一个Get接口 3. 使用Flask开发一个Post接口 4. Flask结合PyMySQL接口与数据库的交互 1. python接口开发框架Flask简介与安装 Flask接口测试框架的简介与安装Flask是轻量级的web开发框架相比于其他框架&#xff…

C++ Primer(第5版) 全书重点学习笔记

目录 第12章 动态内存 12.1 动态内存与智能指针 12.1.6 weak_ptr 12.2 动态数组 12.2.1 new和数组 12.2.2 allocator类 第12章 动态内存 12.1 动态内存与智能指针 12.1.6 weak_ptr weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_pt…

岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。 岛屿的面积是岛上值为 1 …

《Zookeeper》从零开始学Zookeeper源码(二)之数据序列化与通信协议

目录 序列化与反序列化通信协议请求头的数据结构响应头的数据结构 序列化与反序列化 zookeeper的客户端与服务端、服务端与服务端之间会进行一系列的网络通信,在进行数据的传输过程中就涉及到序列化与反序列化,zookeeper使用Jute作为它的序列化组件&…

【雕爷学编程】Arduino动手做(187)---1.3寸OLED液晶屏模块3

37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&#x…

77 | Python数据分析篇——Pandas之DataFrame对象总结

Pandas是Python中最受欢迎和广泛使用的数据处理库之一,它提供了灵活、高效的数据结构和数据分析工具,让数据分析和处理变得更加简单和便捷。Pandas中最重要的两个数据结构是Series和DataFrame,它们分别对应于一维标签数组和二维标签表格,为数据分析和处理提供了强大的功能。…

koa框架介绍以及核心原理实现

介绍 Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有…

LangChain+ChatGLM整合LLaMa模型(二)

开源大模型语言LLaMa LLaMa模型GitHub地址添加LLaMa模型配置启用LLaMa模型 LangChainChatGLM大模型应用落地实践(一) LLaMa模型GitHub地址 git lfs clone https://huggingface.co/huggyllama/llama-7b添加LLaMa模型配置 在Langchain-ChatGLM/configs/m…

C++入门之stl六大组件--List源码深度剖析及模拟实现

文章目录 前言 一、List源码阅读 二、List常用接口模拟实现 1.定义一个list节点 2.实现一个迭代器 2.2const迭代器 3.定义一个链表,以及实现链表的常用接口 三、List和Vector 总结 前言 本文中出现的模拟实现经过本地vs测试无误,文件已上传gite…

Word中如何断开表格中线段

Word中如何断开表格中线段_word表格断线怎么弄_仰望星空_LiDAR的博客-CSDN博客有时候为了美观,需要实现如下的效果,即第2条线段被断开成3段步骤如下:选中需要断开的格网,如下,再选择段落、针对下框标即可。_word表格断…

python列表查询方法

列表索引,如a[1,2,3,4,5],用a[2]来提取第三个元素列表切片,a[:3],a[-2:],a[0:2]等切片来提取多个元素;列表成员运算符,如2 in a结果为True;列表方法:append()在列表末尾添加一个元素…

两个集合取交集、并集、差集

1. 准备数据&#xff1a; List<String> list1 new ArrayList<String>();list1.add("1");list1.add("2");list1.add("3");list1.add("5");list1.add("6");System.out.println("---- list1 ----");Sys…

ROS激光雷达数据解析和获取

整体思路 catkin_create_pkg lidar_pkg roscpp rospy sensor_msgs lidar_node.cpp #include<ros/ros.h> #include<sensor_msgs/LaserScan.h>void LidarCallback(const sensor_msgs::LaserScan msg) {float fMidDist msg.ranges[180];//解析数据msg里面的数组是最…

大数据-数据内容分类

大数据-数据内容分类 结构化数据 可以使用关系型数据库表示和存储&#xff0c;可以用二维表来逻辑表达实现的数据 结构化数据&#xff1a;二维表&#xff08;关系型&#xff09; 结构化数据&#xff1a;先有结构、再有数据 数据以行为单位&#xff0c;一行数据表示一个实体…

web前端html

文章目录 快捷方式一、html5的声明二、html5基本骨架 2.1 html标签 2.2 head标签 2.3 body和head同级 2.4 body标签 2.5 title标签 2.6 meta标签 三、标题标签介绍与应用 3.1 标题的介绍 3.2 标题标签位置摆放 3.3 标签之段落、换行、水平线 3.3 标签之图片 3.3.1 图…

springboot松散绑定

目录 问题引进 宽松绑定 Value&#xff08;补充&#xff09; 问题引进 在进行属性绑定时&#xff0c;可能会遇到如下情况&#xff0c;为了进行标准命名&#xff0c;开发者会将属性名严格按照驼峰命名法书写&#xff0c;在yml配置文件中将datasource修改为dataSource&…

【Docker】DockerFile

目录 一、镜像原理 二、如何制作镜像 1、容器转镜像 2、DockerFile 三、DockerFile关键字​编辑 四、案例&#xff1a;部署SpringBoot项目 一、镜像原理 docker镜像是由一个特殊的文件系统叠加而成的&#xff0c;他的最低端是bootfs&#xff0c;并使用宿主机的bootfs&…

ruoyi若依 组织架构设计--[ 角色管理 ]

ruoyi若依 组织架构设计--[ 角色管理 ] 角色新增后端代码 角色修改后端代码 角色查询角色删除角色分配数据权限后端代码 角色分配用户 角色新增 后端代码 有一点&#xff0c;我认为新增的时候&#xff0c;也需要修改redis中的权限。 角色修改 后端代码 因为修改了role_menu表了…

Java8 list多属性去重

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;一个奋斗在互联网的打工人。 在 Java 开发中&#xff0c;我们经常会面临对 List 中的对象属性去重的需求。然而&#xff0c;当需要根据多个属性来进行去重时&#xff0c;情况会稍微复杂一些。本篇…

51单片机程序烧录教程

STC烧录步骤 &#xff08;1&#xff09;STC单片机烧录方式采用串口进行烧录程序&#xff0c;连接的方式如下图&#xff1a; &#xff08;2&#xff09;所以需要先确保USB转串口驱动是识别到&#xff0c;且驱动运行正常&#xff1b;是否可通过电脑的设备管理器查看驱动是否正常…