CPP:STL库 C++泛型编程和STL技术 1.模版 学习模版并不是为了写模版,而是在STL中能够运用系统提供的模版 1.1 函数模版 模版就是建立通用的模具,大大提高复用性 C++中另一种编程思想称为泛型编程,主要利用的技术就是模版 C++中提供两种模版机制:函数模版和类模版 1.1.1 函数模版语法 建立一个通用函数,其函数的返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表 语法:template关键字声明一个通用数据类型T 1 2 template<typename T> 函数声明或定义 template声明创建模版 typename表明其后面的符号是一种数据类型,可以用class代替 T:通用的数据类型,名称可以替换 1 2 3 4 5 6 template <typename T> void swap(T &a, T &b) { T temp = a; a = b; b = temp; } 自动类型推导:我们在模版中使用通用类型T,当传入具体的数据类型时,根据该数据类型推导出T的类型 ...
CPP面向对象
CPP面向对象 1. 内存分区模型 代码区:二进制代码 全局区:全局变量和静态变量以及常量 栈区:编译器自动分配释放,存放局部变量 堆区:程序员分配和释放 1.1 程序运行前 程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域 代码区 存放CPU执行的机器指令 代码区是共享的 代码区是只读的 全局区 全局变量、静态变量(static) 常量区:字符串常量和const修饰的全局变量 该区域的数据在程序结束后由操作系统回收 1.2 程序运行后 栈区:由编译器自动分配释放,存放函数的参数值和局部变量 不要返回局部变量的地址 1 2 3 4 int * func() { int a = 10; // 函数中的局部变量,存放在栈区,栈区的数据在函数执行完后自动释放 return &a; } 局部变量:在函数中定义;全局变量:在函数外定义 在函数中定义的局部变量存放在内存中的栈区,在函数运行结束后编译器自动释放 回想MIPS函数调用规范中的分配栈 堆区:由程序员分配释放 在c++中由new关键字在堆区中申请内存 1 int * p = new int(10); 该局部变量不会随函数结束被回收,即堆区的变量与栈区的变量有不同的生命周期 1.3 new操作符 用new操作符在堆区开辟数据 基本语法:new 数据类型 (初始值)|[元素个数],返回该数据类型的指针 1 2 3 4 int *p = new int(10); // 创建int型变量 int *arr = new int[10]; // 创建一个数组 [] delete p; delete[] arr; 用delete操作释放内存,释放数组加[] ...
OO-Unit4
OO第四单元总结——UML 一.正向建模与开发 在本单元中,主要的训练目的是通过画UML图进行正向建模与开发,即首先通过画UML图(类图/状态图/顺序图)等设计程序的架构,然后进行代码实现。 在本单元作业中,我主要通过类图进行正向建模与开发。在进行代码实现之前,首先画类图来确定出大致的属性、方法、交互关系等,关于一些比较具体的实现例如参数等先略去,在完成代码过程中进行补充。 二.架构设计 本单元的架构设计在三次迭代中保持的相对稳定,除了类中的方法等细节变动,在类的层面上遵循着面向对象的奥义,对于出现的每一个事物都建一个类,在第二次作业中新增漂流角类。 第一次作业 第二次作业 第三次作业 在三次作业中,我首先进行大略的UML类图的绘制,然后根据第一版类图进行代码构建,在编写代码的过程中不断完善细节和类之间的交互关系,反过来对UML图中的关系进行修改,达到了代码设计和UML模型之间的追踪关系。 三.架构设计思维演进 第一单元聚焦于层次化设计,通过表达式计算这一实际问题来引导同学们进行表达式->项->因子的递归下降建模。在第一单元中,我对于递归下降的理解是不断深入的,在架构设计上,针对递归下降的结构进行设计,即对表达式、项、因子三个层次进行建模,其中因子设计为一个接口,不同种因子设置为接口的实现。 第二单元聚焦于多线程设计,通过新主楼电梯运行这一实际问题来引导同学们进行多部电梯之间的多线程协作编程实践(个人体感上第二单元是最难的,难以复现的bug让人恼火)。在第二单元中,我的架构设计主要在于多线程之间的交互关系上:输入线程、调度器线程、电梯线程之间使用怎样的数据结构,怎样降低耦合度,提高内聚度。 第三单元聚焦于JML规格化设计,通过迷你社交网络这一实际问题来引导同学们根据JML代码来编写实际的JAVA代码。这一单元实际上对于架构设计没有要求,同学们要完成的任务在于根据已经给出的架构进行规格化代码编写,主要的问题是规格与实现分离:给定了JML规格,但不指定具体实现,这其中的算法效率需要同学们进行设计。我感触比较深的点除了使用复杂度较优的算法之外还有进行复杂度分摊:对于一个常使用的复杂度较高的算法可以将他的复杂度分摊在其他较少使用的方法中,达到全局优化的效果。 第四单元聚焦于UML正向建模与设计,在这单元中我尝试首先通过UML类图进行代码架构的建模与设计,然后在代码编写的过程中优化类之间的协作关系以及数据结构,最后反过头来完善UML图中的细节以及修改与代码不符的设计。在第四单元中,我更加深刻地感受到架构设计的重要性,一个好的架构设计可以为代码实现减小实现难度同时维持较好的扩展性,而架构设计的奥义在于高内聚低耦合,在OOpre以及OO课程中我的感受就是“对每一种事物都建一个类来完成对应的职责”。 四.测试思维演进 在四个单元中,我都是采用边缘数据测试+大量随机数据压力测试的方法,在第三单元中,采用了参数化JUnit测试。 在代码编写过程中,我会编写一些简单的样例对已经编写好的代码进行测试,相当于对每一个方法都进行一个小测试,在过程中不断debug,防止最后bug堆积增加debug难度。 完成代码编写后(首先跑样例),手动构造一些从简单到极端数据测试数据范围、运行时间、算法效率等(当然本机运行时间和评测机完全不同,只是进行不同实现间的比较)。然后进行大量随机数据压力测试,这里要感谢DPO的评测机以及Kai_ker的评测机支持。 以上都是进行黑盒测试,在时间充裕的情况下,我还会进行白盒测试,即从头到尾读几遍自己的代码,再推敲一遍实现细节,往往白盒测试能够触碰到一些黑盒测试碰不到的角落bug。 五.课程收获 从OOpre初次接触JAVA编程,到OO课程结束已经可以独立编写千行级别有一定质量的代码,我在一次次代码作业中提高编程能力,在一次次博客作业中总结编程经验。从赤手空拳到满载而归,这是一个充满艰辛的过程。 面向对象思想是一种好用且高效的思想,理解面向对象思想并不难,通俗的说就是对每一种事物都理解为一个类,有属于自己的职责;但是要想真正写好面向对象代码则需要在不断实践中掌握各种设计模式、不断提高架构设计能力、不断体会高内聚低耦合的设计思路,这也正是课程组在作业中要求我们掌握的。 彼时的少年,站在成长的巅峰,回首来时,满路崎岖。诚实地说学习OO的过程于我而言是痛苦的,难忘深夜新主楼debug的种种艰辛,但是带给我的代码能力的成长也是非常之大的。 在写OO的过程中,我喜欢用Pomodoro Logger记录自己努力的时间,hhhhh真是花了很多时间(只展示时间最长的两次作业)。 如今在OO课程结束的节点,我很赞同OO是一门绝世好课,无论从课程制度还是难度设置上都越来越合理(可以实实在在地给予同学们一个完整的青春)。感谢老师、助教、研讨课上高谈阔论的同学、Kai_ker哥在我的OO课程中赠予的帮助。写完了这篇博客,我就要和2024春季的OO课程说再见了,心头感慨万分,甚至有些舍不得,我深知对我能力提高这样大的课程恐怕再难遇到,诚惶诚恐,用每一行代码,每一篇博客小心翼翼地记录下属于我的OO时光。
OS:Shell挑战性任务
Shell挑战性任务设计文档:MOS_SUDO_SHELL 一.实现不带.b的后缀指令 实现不带.b的后缀指令同时兼容带有.b后缀的指令,这一点可以通过**首先尝试打开不带后缀的指令名,例如ls,失败后再尝试打开带后缀的指令名ls.b实现。**修改spawn.c/spawn函数中的打开文件逻辑即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int spawn(char *prog, char **argv) { // Step 1: Open the file 'prog' (the path of the program). // Return the error if 'open' fails. int fd; if ((fd = open(prog, O_RDONLY)) < 0) { /*Shell Challenge: *.b*/ char *ext = ".b"; char prog_b[1024]; strcpy(prog_b, prog); // strcat for (int i = strlen(prog_b), j = 0; j < strlen(ext); i++, j++) { prog_b[i] = ext[j]; } if ((fd = open(prog_b, O_RDONLY)) < 0) { return fd; } } //... } 二.实现指令条件执行 需要实现Linux Shell中的&&与||,需要满足短路原则。 ...
OS:lab6实验报告
OS:lab6实验报告 Thinking 6.1 示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想 让父进程作为“读者”,代码应当如何修改? 父进程关掉写端fildes[1],打开读端fildes[0] 子进程关掉读端fildes[0],打开写端fildes[1] Thinking 6.2 上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/lib/fd.c 中 的 dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup 函数中为什么会出 现预想之外的情况? 原理同pipe_close一致,都是在一个函数两个相关的页面map或者unmap的之间发生了时钟中断,所以会导致不一致的现象。 dup函数中有两次map,将newfd所在的虚拟页映射到oldfd所在的物理页,将newfd的数据所在的虚拟页映射到oldfd的数据所在的物理页 Thinking 6.3 阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是 所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析说明。 系统调用一定是原子操作,进程切换是通过定时器产生时钟中断,触发时钟中断切换进程。但是syscall跳转到内核态时,CPU将SR寄存其的IE位置0,关闭了时钟中断。 Thinking 6.4 按照上述说法控制 pipe_close 中 fd 和 pipe unmap 的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程 在两个unmap之间发生时钟中断时,若先解除pipe再解除fd,会导致pageref(pipe)==pageref(fd),写端关闭的错误判断。不会若调换顺序,可以总保证pageref(pipe) > pageref(fd)发生错判。 我们只分析了 close 时的情形,在 fd.c 中有一个 dup 函数,用于复制文件描述符。 试想,如果要复制的文件描述符指向一个管道,那么是否会出现与 close 类似的问题?请模仿上述材料写写你的理解。 原来dup的map顺序为首先映射fd再映射pipe即fd的映射次数先+1,此时若在两个map之间发生时钟中断,会导致pageref(pipe)==pageref(fd)的错判,因此需要调整顺序,首先映射pipe再映射fd。 Thinking 6.5 认真回看 Lab5 文件系统相关代码,弄清打开文件的过程 spawn 函数是通过和文件系统交互,取得文件描述块,进而找到 ELF 在“硬盘”中的位置,进而读取。 回顾 Lab1 与 Lab3,思考如何读取并加载 ELF 文件 在 Lab3 中填写了 load_icode 函数,实现了 ELF 可执行文件中读取数据并加载到内存空间,其中通过调用 elf_load_seg 函数来加载各个程序段。在 Lab3 中我们要填写 load_icode_mapper 回调函数,在内核态下加载 ELF 数据到内存空间;相应地,在 Lab6 中 spawn 函数也需要在用户态下使用系统调用为 ELF 数据分配空间。 在 Lab1 中我们介绍了 data text bss 段及它们的含义,data 段存放初始化过的全局变量,bss 段存放未初始化的全局变量。关于 memsize 和filesize ,我们在 Note1.3.4中也解释了它们的含义与特点。关于 Note 1.3.4,注意其中关于“bss 段并不在文件中占数据”表述的含义。回顾 Lab3 并思考:elf_load_seg() 和 load_icode_mapper()函数是如何确保加载 ELF 文件时,bss 段数据被正确加载进虚拟内存空间。bss 段在 ELF 中并不占空间,但 ELF 加载进内存后,bss 段的数据占据了空间,并且初始值都是 0。请回顾 elf_load_seg() 和 load_icode_mapper() 的实现,思考这一点是如何实现的? bss段应该与text段data段连续的放在一起,但是ELF中没有空间,在分配映射页面时,text段与data段没有占满的空间置为0给了bss段,然后再给他另外分配的时候,只使用syscall_mem_alloc而不映射。 Thinking 6.6 通过阅读代码空白段的注释我们知道,将标准输入或输出定向到文件,需要 我们将其 dup 到 0 或 1 号文件描述符(fd)。那么问题来了:在哪步,0 和 1 被“安排”为 标准输入和标准输出?请分析代码执行流程,给出答案。 ...
OS:lab6课下基础
OS:lab6课下基础 1. 管道 1.1 初窥管道 管道是一种典型的进程间单向通信的方式,分为有名管道和匿名管道两种,匿名管道只能在有公共祖先的进程间使用,在MOS中,我们要实现匿名管道。 管道是一种只存在于内存中的文件,在MOS中,父进程调用pipe函数时会打开两个新的文件描述符:一个表示只读端,一个表示只写端,两个描述符都映射到同一片内存区域。 在fork的配合下,子进程会复制父进程的两个文件描述符,从而在==父子进程间形成了四个(父子各有一读一写)的指向同一片内存区域的文件描述符,父子进程可根据需要关掉自己不用的一个,从而实现单向管道通信。== 1.2 MOS中pipe的使用与实现 MOS中pipe的实现 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 int pipe(int pfd[2]) { int r; void *va; struct Fd *fd0, *fd1; /* Step 1: Allocate the file descriptors. */ if ((r = fd_alloc(&fd0)) < 0 || (r = syscall_mem_alloc(0, fd0, PTE_D | PTE_LIBRARY)) < 0) { goto err; } if ((r = fd_alloc(&fd1)) < 0 || (r = syscall_mem_alloc(0, fd1, PTE_D | PTE_LIBRARY)) < 0) { goto err1; } /* Step 2: Allocate and map the page for the 'Pipe' structure. */ va = fd2data(fd0); if ((r = syscall_mem_alloc(0, (void *)va, PTE_D | PTE_LIBRARY)) < 0) { goto err2; } if ((r = syscall_mem_map(0, (void *)va, 0, (void *)fd2data(fd1), PTE_D | PTE_LIBRARY)) < 0) { goto err3; } /* Step 3: Set up 'Fd' structures. */ fd0->fd_dev_id = devpipe.dev_id; fd0->fd_omode = O_RDONLY; fd1->fd_dev_id = devpipe.dev_id; fd1->fd_omode = O_WRONLY; debugf("[%08x] pipecreate \n", env->env_id, vpt[VPN(va)]); /* Step 4: Save the result. */ pfd[0] = fd2num(fd0); pfd[1] = fd2num(fd1); return 0; err3: syscall_mem_unmap(0, (void *)va); err2: syscall_mem_unmap(0, fd1); err1: syscall_mem_unmap(0, fd0); err: return r; } 首先为fd0和fd1两个文件描述符分配空间(物理页) ...
OS理论期末复习
OS理论期末复习 一. 引论 ==1. 批处理系统== 把用户提交的作业成批送入计算机 由作业调度程序自动选择作业运行 目的 缩短作业之间的交接时间 减少处理机的空闲等待,提高系统效率 1.1 联机批处理系统 作业的输入输出由CPU处理 **优点:**监督程序不停地处理各个作业,实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间 不足:在作业输入和结果输出时,主机的高速CPU仍然处于空闲状态,等待慢速的输入输出设备完成工作 1.2 脱机批处理系统 作业的输入输出脱离CPU处理 **优点:**主机不与慢速的输入输出设备打交道,而是与速度相对较快的磁带机发生关系,缓解了主机与设备的矛盾 **缺点:**每次主机内存中仅存放一道作业,每当它运行期间发出I/O请求后,高速的CPU处于等待低速的I/O完成状态,CPU空闲 2. 多道程序系统 **多道程序设计技术:**允许多个程序同时进入内存并运行 当一道程序因I/O请求暂停时,CPU便立即转去运行另一道程序 宏观上并行,微观上串行 **优点:**使CPU得到充分利用,同时也改善I/O设备和内存的利用率,提高了整个系统的资源利用率和系统吞吐量 单道程序系统:I/O时CPU空闲 多道程序系统:交替使用CPU 3. 多道批处理系统 优点: 系统吞吐量大 资源利用率高 缺点: 平均周转时间长 ==不能提供交互能力== 4. 分时系统 多个用户分享使用同一台计算机,多个程序分时共享硬件和软件资源 多路性:多路连接,宏观上用户共享,微观上分时 独立性:用户相互不干扰 及时性:响应时间 交互性:人机对话 分时技术:处理机的运行时间分成很短的时间片,轮流分配给各联机作业使用 5. 实时系统 及时响应 高可靠性和安全性 实时信息处理、实时控制 6. 异常、陷阱和中断 同步异常:执行指令的过程中发生 系统调用为一种同步异常:自陷指令(trap) 二.引导 加载BIOS 读取MBR BootLoader 加载内核 … 三. 内存管理 地址空间:逻辑地址空间 存储空间:物理地址空间 ...
OS:第六次理论作业
OS第六次理论作业 分析磁盘访问数据的时间。假设磁盘请求以柱面10、35、20、70、2、 3 和 38 的次序进入磁盘驱动器。寻道时磁头每移动一个柱面需要 5ms,以下各算法所需的寻道时间是多少: (1) 先来先服务 (2) 最短寻道时间优先 (3) SCAN算法 (4) LOOK算法 说明:假设以上三种情况磁头初始位置为15。对于(3)和(4), 磁头 当前向大柱面号方向运行,磁盘最大柱面号为85。 寻道时间:是把磁头从当前位置移动到指定磁道上所经历的时间 该时间是启动磁盘的时间s与磁头移动n条磁道所花费的时间之和 Ts = m * n + s 先来先服务: $5*(5+25+15+50+68+1+35)=995ms$ 最短寻道时间优先: 这里起始位置是15,距离10和20距离相等 若首先访问10,序列为:$15,10,3,2,20,35,38,70$ $5*(5+7+1+18+15+3+32)=405ms$ 若首先访问20,序列为:$15,20,10,3,2,35,38,70$ $5*(5+10+7+1+33+3+32)=455ms$ SCAN电梯算法 序列为:$15,20,35,38,70,85,10,3,2$ $5*(5+15+3+32+15+75+7+1)=765ms$ LOOK算法 序列为:$15,20,35,38,70,10,3,2$ $5*(5+15+3+32+60+7+1)=615ms$ 在I/O系统中引入缓冲区的主要目标是什么?某文件占8个磁盘块, 现要把该文件的磁盘块逐个读入主存缓冲区,并送用户区进行分析。一个缓冲区与磁盘块大小相等。把一个磁盘块读入缓冲区的时间为 100μs,缓冲区数据传送到用户区的时间是50μs,CPU对一块数据 进行分析的时间为50μs。分别计算在单缓冲区和双缓冲区结构下, 分析完该文件的时间是多少? 在I/O系统中,CPU计算速度较快,而外设I/O速度较慢,为了解决CPU与外设速度不匹配的问题,提高CPU的利用率,减少CPU的忙等状态,设计了缓冲区 使用单缓冲区:第一块磁盘读入缓冲区的时间为100us,而后的磁盘读入和CPU进行分析的50us可以并行执行,对过程进行简单分析,第一块磁盘读入到传送到用户区:150us,此时CPU对第一块数据进行计算,并行地,第二块磁盘开始读入,需要50+50+50完成对第二块磁盘的计算,后边的磁盘同理。即第一块磁盘从读入到完成分析需要200us,后边每一块需要150us,总体$150*7+200=1250us$ 使用双缓冲区:CPU和I/O设备分别使用一块缓冲区,一块缓冲区由I/O设备进行磁盘块读入后将数据传送到CPU并由CPU进行分析,并行地,另一个缓冲区进行磁盘块读取,即第一块磁盘需要200us,后边每块磁盘需要100us,总体$200+100*7=900us$ 请结合操作系统课所学习的内容总结从哪些方面可以提高文件系统 的性能。 可以采用目录项分解、当前目录、磁盘碎片整理、块高速缓存、磁盘调度、提前读取、合理分配磁盘空间、信息的优化分布、RAID等技术 简述文件控制块(FCB-File Control Block)中所管理的主要信息 基本信息:文件名、物理位置、文件逻辑结构、文件物理结构 访问控制信息:文件所有者、访问权限 使用信息:创建时间、上一次修改时间、当前使用信息 在文件系统中,访问一个文件f时首先需要从目录中找到与f对应 的目录项。假设磁盘物理块的大小为1KB,一个目录项的大小为128 字节,文件的平均大小为100KB。该 文 件 系 统 的 目 录 结 构 如 下图所示。 假定不考虑磁盘块的提前读和缓存等加速磁盘访问技术。 请回答以下问题: (1) 按照当前的目录结构,且采用串联文件方式对数据块进行组织,并且根目录的目录项已读入内存中。如果目标文件f 在第三级目录下, 且其对应的第三级目录的目录项可以一次从磁盘读出,访问文件f中的 一个块平均需要访问几次磁盘? (2) 如果采用i节点的方法来构建文件目录,假定文件名占14个字 节,i节点的指针占2个字节。如果仅采用直接索引,每个第三级目录 下的文件数不超过50个,且根目录的i节点已读入内存,访问第三级 目录下的一个文件的一个块平均需要访问几次磁盘? (3) 假设该文件系统的空间最大容量为16ZB(1ZB=$2^{70}$B)。如 果 文 件 的 FCB 中包括512字节的索引区,且允许采用一级索引进行组织,那么该 文件系统支持的最大文件是多少字节? ...
OS:lab5实验报告
OS:lab5实验报告 Thinking 5.1 如果通过 kseg0 读写设备,那么对于设备的写入会缓存到 Cache 中。这是 一种错误的行为,在实际编写代码的时候这么做会引发不可预知的问题。请思考:这么做 这会引发什么问题?对于不同种类的设备(如我们提到的串口设备和IDE磁盘)的操作会 有差异吗?可以从缓存的性质和缓存更新的策略来考虑 当外部设备自身更新数据时,如果此时CPU写入外设的数据仍然保留在Cache中,而没有被及时写入到设备,则缓存的数据会在外设自身更新后再写入外设,发生错误 串口设备读写频繁,而IDE磁盘读写频率相对较小,因此串口设备发生错误的概率要远大于IDE磁盘 Thinking 5.2 查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件控制 块?一个目录下最多能有多少个文件?我们的文件系统支持的单个文件最大为多大? 一个磁盘块的大小为4KB,一个文件控制块的大小为256B,故能存储16个 一个目录文件最多可以使用1024个磁盘块存储文件控制块,故最多有16384个文件 一个文件最多使用1024个磁盘块存储数据,1024*4KB=4MB Thinking 5.3 请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少? 块缓存的地址空间大小为DISKMAX即0x4000_0000页即为1GB大小 故实验支持的的最大磁盘大小为1GB Thinking 5.4 在本实验中,fs/serv.h、user/include/fs.h 等文件中出现了许多宏定义, 试列举你认为较为重要的宏定义,同时进行解释,并描述其主要应用之处 比较重要的一个宏定义是FILE_STRUCT_SIZE 1 #define FILE_STRUCT_SIZE 256 这个宏定义指出了file结构体的大小 在f_pad域对file结构体空闲空间进行补全时用到,让整数个结构体占用一个磁盘块 Thinking 5.5 在Lab4“系统调用与fork”的实验中我们实现了极为重要的fork函数。那 么fork前后的父子进程是否会共享文件描述符和定位指针呢?请在完成上述练习的基础上 编写一个程序进行验证。 一个进程所有的文件描述符都存储在[FDTABLE, FILEBASE)这一地址空间中。在fork函数执行时,会将KSTACKTOP以下的地址空间复制到子进程地址空间中,因此fork前后的父子进程会共享文件描述符和定位指针。 Thinking 5.6 请解释File,Fd,Filefd结构体及其各个域的作用。比如各个结构体会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明形式自定,要 求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系与设计框架 给出以上三种结构体的定义以及解释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct File { char f_name[MAXNAMELEN]; // 文件名 uint32_t f_size; // 文件的大小 uint32_t f_type; // 文件类型 uint32_t f_direct[NDIRECT]; // 存储的直接磁盘块号 uint32_t f_indirect; // 存储的间接磁盘块号,其中保存间接指向的磁盘块号 struct File *f_dir; // 文件的目录,只有在内存中有效 char f_pad[FILE_STRUCT_SIZE - MAXNAMELEN - (3 + NDIRECT) * 4 - sizeof(void *)]; } __attribute__((aligned(4), packed)); // 填充剩余空间,只保存整数个文件结构体 // file descriptor struct Fd { u_int fd_dev_id; // 文件对应的设备id u_int fd_offset; // 文件指针指向的位置(在设备中的偏移量) u_int fd_omode; // 文件打开模式(open mode) }; // file descriptor + file struct Filefd { struct Fd f_fd; // 文件描述符结构体 u_int f_fileid; // 文件id,表示文件在opentab中的位置 struct File f_file; //文件控制块 }; Fd结构体主要用于记录已经打开的文件的状态,便于用户直接使用文件描述符对文件进行操作和申请服务。由于文件描述符主要是为用户所用,因此它对应的是磁盘映射到内存中的数据。 ...
OS:lab5课下基础
OS:lab5课下基础 1. 文件系统概述 计算机文件系统是一种存储和组织数据的方法,便于访问和查找数据。文件系统使用文件和树形目录的逻辑抽象屏蔽了底层硬盘和光盘等物理设备基于数据块进行存储和访问的复杂性。 即用户不必关心数据实际保存在硬盘/光盘的哪个数据块上,而只需要记住这个文件所属的目录和文件名。 在写入新数据之前,用户不必关心硬盘上哪个块是空闲的,硬盘上的存储空间管理(分配/释放)由文件系统自动完成,用户只需记住数据写入到了哪个文件 常见的文件系统通常基于硬盘和光盘等块存储设备,并维护文件在设备中的物理位置。 广义上,一切带标识的、在逻辑上有完整意义的字节序列都可以称为文件,文件系统将外部设备中的资源抽象为文件,从而可以统一管理外部设备,实现对数据的存储、组织、访问和修改。 2. 文件系统的设计与实现 MOS是一种微内核设计 将传统操作系统的文件系统移出内核 将一些内核数据暴露到用户空间 将传统操作系统的设备驱动移出内核 3. IDE磁盘驱动 为了在磁盘等外部设备上实现文件系统,我们必须为这些外部设备编写驱动程序。(MOS已经在kern/mechine.c中实现了一个简单的控制台驱动程序) 本次要实现的硬盘驱动程序与已经实现的串口驱动都采用MMIO内存映射技术,并且本次编写的硬盘驱动程序完全运行在用户空间(微内核)。 3.1 内存映射I/O(MMIO) 几乎每一种外设都是通过读写设备上的寄存器来进行数据通信,外设寄存器也称为I/O接口,主要用来访问I/O设备。 外设寄存器主要包括控制寄存器、状态寄存器、数据寄存器,这些寄存器被映射到指定的物理地址空间 在MIPS的内核地址空间中(kseg0/kseg1)实现了硬件级别(高位清0)的物理地址和内核虚拟地址的转换机制,其中,对于kseg1段地址的读写不经过MMU映射,且不使用高速缓存,这正是外部设备驱动所需要的。我们可以通过简单地读写某些固定的内核虚拟地址来实现驱动程序的功能。 在之前的实验中,我们曾经使用KADDR宏将一个物理地址转换为kseg0段的虚拟地址,即加上kseg0段的偏移量(0x8000_0000) 在编写设备驱动时,我们需要将物理地址转换为kseg1段的内核虚拟地址,即给物理地址加上kseg1的偏移值0xA000_0000 以已经编写完成的串口设备驱动为例,MALTA提供的console设备基地址为0x1800_03F8,设备寄存器映射如表所示 例如写操作:通过向kseg1段的地址写入字符,就能在shell中看到对应的输出。 kseg0_offset + device_op_addr_base = KSEG1 + MALTA_SERIAL_DATA 1 2 3 4 5 //写 void printcharc(char ch) { //... *((volatile uint8_t *)(KSEG1 + MALTA_SERIAL_DATA)) = ch; } 在本次实验中,我们需要编写的IDE磁盘驱动位于用户空间,用户态进程若是直接读写内核虚拟地址会引发一个地址错误ADEL/S,故对于设备的读写必须通过系统调用实现 ...