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时光。

June 10, 2024 · 1 min · sudo

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中的&&与||,需要满足短路原则。 ...

June 8, 2024 · 14 min · sudo

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 被“安排”为 标准输入和标准输出?请分析代码执行流程,给出答案。 ...

June 8, 2024 · 2 min · sudo

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两个文件描述符分配空间(物理页) ...

June 8, 2024 · 11 min · sudo

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 加载内核 … 三. 内存管理 地址空间:逻辑地址空间 存储空间:物理地址空间 ...

June 3, 2024 · 5 min · sudo