OO-Unit3

OO第三单元——JML 一. 单元架构设计 ​ 相比于前两单元需要自行设计架构,充当架构设计师的角色,U3不需要设计架构,需要完成的任务是依据课程组给出的架构设计合理的类协作关系以及依据JML写对应的代码(码农日常)。 ​ 关于课程组给出的架构可以简单概括如下图 二. 算法实现与性能 ​ 本单元更加关注的是对于既定的JML或者说给定的功能要求,具体的算法实现和效率。强测中对于性能有一定要求,性能不佳会爆CTLE。我的理解中,CTLE字面意思上是CPU时间超时,换句话说其实就是**“算的次数太多“**,在本单元作业中,可以采用缓存一些计算复杂度较高的结果进行优化等,下面基于每一次作业进行分析。 1. hw9 ​ 这一次作业中主要的性能考察点是关于query_block_sum中查询两个点是否联通的功能。在本次作业中,通过addRelation和modifyRelation可以建立起一个社交图,这其中需要注意的是modifyRelation操作可能删边。很多大佬同学实现了可以删边的并查集,我选择了实现较为简单(懒)的BFS。使用BFS进行联通查询面对大量查询指令有CTLE风险,故可以考虑对BFS进行一点简单优化,例如使用双向BFS。关于双向BFS,其实就是从起点和终点同时开始搜索,面对图中点非常多的情况可以大幅减少开销。 ​ 下面是一张摘自广度优先搜索之双向bfs(实操篇)-CSDN博客的一张很形象的图片。 ​ 双向BFS的优势其实就是大幅减少搜索宽度,减少遍历一些点,让它看上去没有那么暴力。由此还有一种优化的思路就是均衡取节点。当我们从给起点和终点分别设置的队列中取节点时,可以选择从size较小的那一个队列中取出(size较大的那一方说明他每一层中的宽度较大,遍历开销大)。下面给出我的具体实现(主要想法是起点终点两个队列两个visited数组,当前遍历如果另一边如果遍历过,结束): 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 import java.util.HashMap; import java.util.LinkedList; import java.util.Queue; public class BreadthFirstSearch { public static boolean isConnected(MyPerson person1,MyPerson person2) { if (person1.getId() == person2.getId()) { return true; } HashMap<Integer, Boolean> visited1 = new HashMap<>(); HashMap<Integer, Boolean> visited2 = new HashMap<>(); visited1.put(person1.getId(),true); visited2.put(person2.getId(),true); Queue<MyPerson> queue1 = new LinkedList<>(); Queue<MyPerson> queue2 = new LinkedList<>(); queue1.add(person1); queue2.add(person2); while (!queue1.isEmpty() && !queue2.isEmpty()) { if (queue1.size() < queue2.size()) { MyPerson now = queue1.poll(); if (next(now,visited1,visited2,queue1)) { return true; } } else { MyPerson now = queue2.poll(); if (next(now,visited2,visited1,queue2)) { return true; } } } return false; } private static boolean next(MyPerson now,HashMap<Integer,Boolean> visited, HashMap<Integer,Boolean> visited2,Queue<MyPerson> queue) { for (Person next : now.getAcquaintance().values()) { if (visited.containsKey(next.getId())) { continue; } if (visited2.containsKey(next.getId())) { return true; } queue.add((MyPerson) next); visited.put(next.getId(),true); } return false; } } ​ 对于另一个查询指令query_triple_sum也需要进行动态维护,在addRelation加边和modifyRelation删边时进行判断,并更新qts的值。 ...

May 16, 2024 · 3 min · sudo

OS第五次理论作业

OS第五次理论作业 有五个进程 P1、P2、P3、P4、P5,它们同时依次进入就绪队列,它们的优先数和需要的处理器时间如下表 进程 处理器时间 优先级(数小优先级高) P1 10 3 P2 1 1 P3 2 3 P4 1 4 P5 5 2 忽略进行调度等所花费的时间,回答下列问题: 写出采用 “先来先服务”、“短作业(进程)优先”、“非抢占式的优先数” 和 “轮转法” 等调度算法,进程执行的次序。(其中轮转法的时间片为 2) 先来先服务: P1,P2,P3,P4,P5 短作业优先: P2,P4,P3,P5,P1 非抢占式的优先数: P2,P5,P1,P3,P4 轮转法: P1,P2,P3,P4,P5,P1,P5,P1,P5,P1 分别计算上述算法中各进程的周转时间和等待时间,以及平均周转时间 先来先服务 进程 周转时间 等待时间 执行时间 P1 10 0 10 P2 11 10 1 P3 13 11 2 P4 14 13 1 P5 19 14 5 平均周转时间:$(10+11+13+14+19)/5=13.4$ 短作业优先 进程 周转时间 等待时间 执行时间 P2 1 0 1 P4 2 1 1 P3 4 2 2 P5 9 4 5 P1 19 9 10 平均周转时间:$(1+2+4+9+19)/5=7$ 非抢占式的优先数 ...

May 13, 2024 · 2 min · sudo

OS第四次理论作业

OS第四次理论作业 1.读者写者问题(写者优先): 1)共享读; 2)互斥写、读写互斥; 3)写者优先于读者(一 旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。 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 int readcount = 0; int writecount = 0; semaphore mutex = 1;//信号量为1相当于互斥锁 semaphore write = 1; semaphore read = 1; semaphore r_wait = 1; writer() { P(write); // 保护 writecount writecount++; if(writecount == 1) { P(r_wait); // 有写入的,暂停读 } V(write); P(mutex); // 互斥写 write_data(); V(mutex); P(write); writecount--; if(writecount == 0) { // 没有写入的,恢复读 V(r_wait); } V(write); } reader() { P(r_wait); // 如果有写入的或等待写入的,暂停读,实现写优先于读 P(read); // 保护 readcount if(readcount == 0) { P(mutex); // 第一个读的,加锁 } readcount++; V(read); V(r_wait); read_data(); P(read); readcount--; if (readcount == 0) { // 最后一个读的,解锁 V(mutex); } V(read); } 2.寿司店问题。假设一个寿司店有 5 个座位,如果你到达的时候有一个空座位,你可以立 刻就坐。但是如果你到达的时候 5 个座位都是满的有人已经就坐,这就意味着这些人都是一 起来吃饭的,那么你需要等待所有的人一起离开才能就坐。编写同步原语,实现这个场景的约束。 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 int eating = 0, waiting = 0; Semaphore mutex = 1, queue = 1; bool must_wait = false; Customer(){ P(mutex); if (must_wait){ waiting++; V(mutex); // 对 waiting 变量的保护可以释放 P(queue); // 被阻塞,坐着等待排队,等待被唤醒 } else { eating++; must_wait = (eating == 5); V(mutex); // 对 eating 变量的保护可以释放 } // 上一部分已经解决了进店后是等待还是吃的问题 Eat_sushi();// else 的人和被唤醒的排队者成功进入这一步 P(mutex); // 开启对 eating, waiting 变量保护 eating--; // 吃的人 -1, 如果 5 个没全吃完,不可以换下一批人吃 if (eating == 0){ // 最后一个吃完的人离开才可以进顾客 int n = min(5, waiting); // 放顾客进来的数量,不超过 5 个 waiting -= n; eating +=n; must_wait = (eating == 5); for(int i = 0; i<n; i++){ V(queue); // 唤醒排队的 n 个人继续进程 } V(mutex); // 允许下一个吃完的人对变量和队列进行操作 } } 3.进门问题。(1)请给出P、V操作和信号量的物理意义。(2)一个软件公司有5名员工, 每人刷卡上班。员工刷卡后需要等待,直到所有员工都刷卡后才能进入公司。为了避免拥挤, 公司要求员工一个一个通过大门。所有员工都进入后,最后进入的员工负责关门。请用 P、 V 操作实现员工之间的同步关系 P操作:请求一个资源,V操作:释放一个资源 ...

May 6, 2024 · 3 min · sudo

OS:lab4实验报告

OS:lab4实验报告 Thinking 4.1 内核在保存现场的时候是如何避免破坏通用寄存器的? 通过执行SAVE_ALL对现场中的所有寄存器进行保存 系统陷入内核调用后可以直接从当时的$a0-$a3参数寄存器中得到用户调用msyscall 留下的信息吗? 不可以,a0-a3寄存器中的参数可能会发生改变,在陷入内核时将需要传递的参数保存进了一个Trapframe中,可以从传递的trapframe指针中获取需要的参数 我们是怎么做到让sys开头的函数“认为”我们提供了和用户调用msyscall时同样 的参数的? 用户调用时的参数保存进了一个Trapframe中,在do_syscall函数中从传入的trapframe指针中取出传递的参数再传入对应的系统调用处理函数sys_*中 内核处理系统调用的过程对Trapframe做了哪些更改?这种修改对应的用户态的变化是什么? 在处理系统调用过程中改变了trapframe指针中v0寄存器(返回值寄存器)的值,这个返回值可以标志系统调用是否成功,如果失败则返回对应的错误码 Thinking 4.2 思考 envid2env函数: 为什么 envid2env中需要判断 e->env_id != envid 的情况?如果没有这步判断会发生什么情况? 相关判断代码 1 2 3 if (e->env_status == ENV_FREE || e->env_id != envid) { return -E_BAD_ENV; } 在mkenvid中,envid的生成是通过asid与在envs中的偏移地址得到的,所以可能有envid越界的情况,所以需要判断申请到的env的id是不是等于请求的envid Thinking 4.3 思考下面的问题,并对这个问题谈谈你的理解:请回顾 kern/env.c 文件 中 mkenvid() 函数的实现,该函数不会返回 0,请结合系统调用和 IPC 部分的实现与 envid2env() 函数的行为进行解释。 mkenvid函数不会返回0,故不会有进程的envid为0,而IPC中其他函数有时候需要获取当前进程的PCB,故可以用0作为比较快速的索引 Thinking 4.4 关于 fork 函数的两个返回值,下面说法正确的是:C A、fork 在父进程中被调用两次,产生两个返回值 B、fork 在两个进程中分别被调用一次,产生两个不同的返回值 ...

May 6, 2024 · 1 min · sudo

OS:lab4课下基础

OS:lab4课下基础 1.系统调用 1.1 系统调用相关概念 ​ 在MIPS中,syscall是用于执行系统调用的自陷指令,它使得进程陷入到内核的异常处理程序中,由内核根据系统调用时的上下文执行相应的内核函数,完成相应的功能,并最终返回到syscall的后一条指令。 存在只能由内核来完成的操作(读写设备、创建进程、IO等) C标准库中的一些函数的实现依赖于操作系统 通过执行syscall指令,用户进程可以陷入到内核态,请求内核提供的服务 通过系统调用陷入到内核态时,需要在用户态与内核态之间进行数据传递与保护 ​ 系统调用保证了系统的安全性:内核将自己能够提供的服务以系统调用的方式提供给用户空间,用户程序只能将服务相关的参数交予操作系统执行 API : Application Programming Interface,程序之间的接口 ​ 直接使用系统调用较为麻烦,于是产生了一系列用户空间的API定义,他们在系统的调用的基础上,实现了更多更高级的常用功能。用户在编写程序时可以直接调用高层次的API来实现各种功能。 ​ 通过层级划分使得程序具有更好的可移植性,只要程序以来的API不变,无论底层的系统调用如何变化,都不会影响 1.2 系统调用机制的实现 ​ 异常分发向量组(exception_handlers)中的8号异常,即为操作系统处理系统调用时的异常。 MOS实验代码中,kern目录下即为内核态代码,user目录下即为用户态代码 ​ 以user/lib/debugf.c中的debugf函数来学习处理系统调用的流程(debugf函数是一个debug信息输出函数,进行了IO方面的系统调用) ​ debugf函数的调用链为(系统调用请求从用户态向内核态传递) debugf调用字符串输出函数debug_output debug_output调用了用户空间的syscall_*函数(这里的*为通配符,代表着用户空间进行系统调用的一组操作,都定义在用户态代码syscall_lab.c中,这里调用的是syscall_print_cons) syscall_*函数调用msyscall函数,系统陷入内核态(msyscall是汇编代码,直接调用syscall)。 内核态中将异常分发到handle_sys函数,将系统所需信息传递进内核(输出字符串s) 内核取得信息,执行对应的内核空间的系统调用函数sys_*(kern/syscall_all.c) 系统调用完成,返回值传递回用户态 从系统调用函数返回,回到debugf调用处 通过上述描述,对于系统调用的处理实际上是从用户空间向系统空间进行传递的,用户空间中的syscall_*函数与内核中的sys_*是一一对应的,syscall_*函数是用户空间中最接近内核的函数,他调用msyscall中的汇编代码syscall直接陷入内核态,sys_*函数是内核中系统调用的具体实现。 ​ 直接调用syscall陷入内核的msyscall函数具有六个参数,其中第一个参数是与调用名相似的宏,例如SYS_print_cons,被称为系统调用号(include/syscall.h),用来区分不同的系统调用,其余还有五个参数,即为系统调用时需要传递给内核的参数。 回忆MIPS函数调用规范中的参数传递,前四个参数保存在寄存器中 Exercise 4.1 msyscall 进行系统调用(syscall),并返回到msyscall的调用者处(jr),syscall_* #include <asm/asm.h> LEAF(msyscall) // Just use 'syscall' instruction and return. /* Exercise 4.1: Your code here. */ syscall #陷入内核 jr ra #返回调用者 syscall_* END(msyscall) ​ 通过汇编指令syscall陷入内核态后,处理器将PC寄存器指向一个内核中固定的异常处理入口(见lab3中不同异常处理跳转到的地址) ...

May 6, 2024 · 15 min · sudo

OS:lab3实验报告

OS:lab3实验报告 Thinking 3.1 请结合 MOS 中的页目录自映射应用解释代码中 e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_V 的含义。 UVPT是用户地址空间中页表项的起始地址 结合页目录自映射我们知道,只要给定了二级页表项的起始地址,我们就能通过自映射机制计算出页目录的起始虚拟地址 UVPT为二级页表基地址 则页目录基地址为$UVPT + (UVPT»12)*4 = UVPT+UVPT»10$ 映射到页目录的页表项的地址为$UVPT + ((UVPT+UVPT»10)»12)*4 = UVPT + UVPT»10 + UVPT»20$ 该项相对于页目录的index:或者说该项相对于页目录的地址偏移为$UVPT»20$,对应的偏移量为UVPT»22,即为PDX(UVPT) PDX(UVPT)可以得到二级页表起始虚拟地址UVPT的页目录号 e->env_pgdir[PDX(UVPT)]即为指向页目录中指向页目录自身的页目录项 PADDR(e->env_pgdir)得到页目录的物理地址并赋予有效位PTE_V Thinking 3.2 elf_load_seg 以函数指针的形式,接受外部自定义的回调函数 map_page。请你找到与之相关的 data 这一参数在此处的来源,并思考它的作用。没有这个参数可不可以?为什么? data是传入的进程控制块指针 作用:在增加虚拟地址到物理地址的映射时提供当前进程地址空间的页目录基地址pg_dir和asid(load_icode_mapper),所有这个参数是必要的 Thinking 3.3 结合 elf_load_seg 的参数和实现,考虑该函数需要处理哪些页面加载的情况 elf_load_seg函数的实现(elfloader.c) 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 int elf_load_seg(Elf32_Phdr *ph, const void *bin, elf_mapper_t map_page, void *data) { u_long va = ph->p_vaddr; size_t bin_size = ph->p_filesz; size_t sgsize = ph->p_memsz; u_int perm = PTE_V; if (ph->p_flags & PF_W) { perm |= PTE_D; } int r; size_t i; u_long offset = va - ROUNDDOWN(va, PAGE_SIZE); if (offset != 0) { if ((r = map_page(data, va, offset, perm, bin, MIN(bin_size, PAGE_SIZE - offset))) != 0) { return r; } } /* Step 1: load all content of bin into memory. */ for (i = offset ? MIN(bin_size, PAGE_SIZE - offset) : 0; i < bin_size; i += PAGE_SIZE) { if ((r = map_page(data, va + i, 0, perm, bin + i, MIN(bin_size - i, PAGE_SIZE))) != 0) { return r; } } /* Step 2: alloc pages to reach `sgsize` when `bin_size` < `sgsize`. */ while (i < sgsize) { if ((r = map_page(data, va + i, 0, perm, NULL, MIN(sgsize - i, PAGE_SIZE))) != 0) { return r; } i += PAGE_SIZE; } return 0; } ...

April 21, 2024 · 2 min · sudo

OS理论期中考试复习

OS理论期中考试复习 一.引论 1. 操作系统定义 操作系统是一组管理计算机硬件资源的软件集合,他向计算机程序提供共性的服务 2. 操作系统发展史 电子管时期:软件:手工修改硬件逻辑单元连接 解决人和CPU的矛盾:软件和硬件分离 2.1 批处理系统 加载在计算机上的一个系统软件,在他的控制下,计算机能够自动地、成批地处理一个或多个用户的作业 用户将一批作业提交给操作系统后就不再干预 2.1.1 联机批处理系统 在主机和输入机之间增加一个存储设备——磁带,在运行于主机的监督程序(OS)的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行,然后把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,重复处理 优点:监督程序不停地处理各个作业,实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,克服了人机矛盾。 缺点:CPU与慢速的外设之间的矛盾:在作业输入和结果输出时,主机的高速CPU需要等待慢速的输入输出设备完成工作,主机处于“忙等”状态 2.1.2 脱机批处理系统 增加一台不与初级直接相连而是与输入输出设备打交道的卫星机 优点:主机不直接与慢速的输入输出设备打交道,而是与速度相对较快的磁带机发生关系,主机与卫星机并行工作,发挥主机的高速计算能力 缺点:主机内存中仅存放一道作业,每当该作业运行期间发生输入输出请求后,高速的CPU便处于等待低速的IO完成状态,COU空闲 2.2 多道程序系统 多道程序设计技术,指允许多个程序同时进入内存并运行。即同时把多个程序放入内存中,并允许他们交替在CPU中运行,共享系统中的各种硬软件资源 当一道程序因为IO请求而暂停运行时,CPU便立即转去运行另一道程序,不同程序间的切换运行 使CPU得到充分利用,同时改善IO设备和内存的利用率,提高整个系统的资源利用率和系统吞吐量 宏观上并行:进入系统的几道程序都处于运行过程中,都开始了各自的运行,但都未运行完毕 微观上串行:各道程序轮流地使用CPU,并交替运行 2.3 单道程序系统 内存中只有一道程序,存在计算时IO设备空闲或进行IO操作时CPU空闲 2.4 多道批处理系统 多道:系统内可同时容纳多个作业 成批:在系统运行过程中,不允许用户与其作业发生交互 优点:系统吞吐量大,资源利用率高 缺点:平均周转时间长,单个用户不能交互,多用户使用和单独控制的矛盾 2.5 分时系统 分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把CPU分配给各联机作业/程序使用,给不同的用户提供程序的使用 一台计算机同时连接多个用户终端 特点 多路性:宏观上看多个用户并行工作,微观上看是各用户轮流使用计算机 交互性:用户可以根据系统对请求的响应结果,进一步向系统提出新的请求(交互式系统) 独立性:用户之间相互独立,互不干扰 及时性:系统对用户的输入及时作出响应 2.6 网络操作系统 传统单机OS上加单独软件层,提供联网功能和资源的远程访问,多机互联 2.7 分布式操作系统 多台机器统一管理形成单一系统 2.8 实时系统 在某个时间限制内及时完成某些及时任务不需要时间片排队 3. 操作系统的基本实现机制 用户态和内核态 用户态和内核态所在的内存空间不一样 CPU的运行状态不一样 指令的执行权限不一样 从用户态转入内核态 ...

April 16, 2024 · 4 min · sudo

OS第三次理论作业

OS第三次理论作业 一.第一题 (1) 答 32位虚拟地址空间,故整个的地址空间大小为4GB;页内偏移量为12位,故一页有4096字节 (2) 答 考察了二级页表的自映射机制,第一级页表所在的逻辑地址即为第一级页表中第一个页表项的逻辑地址,第一个页表项映射到第一个二级页表,第一个二级页表对应的虚拟页号为$0x80000000 » 12 = 0x00080000$,故该页表项为第$0x0008000$个页表项,相对于页表项起始地址的偏移量为$0x00080000 * 4$,故对应的逻辑地址为$PT_{base} + (PT_{base} «12) *4 = 0x80200000$ (3) 答 逻辑地址0x0:访问逻辑地址0x0时,对应的页目录偏移量为0,对应的有效位为0,引发了缺页中断,需要进行重填 逻辑地址0x00803004:访问逻辑地址0x00803004时,对应的页目录偏移量为2,有效位为1,对应的物理地址为0x5000,逻辑地址对应的二级页表偏移量为3,对应的页表项有效位为1,对应的物理地址为0x20000,页内偏移量为4字节, 若系统为小端存储(数据的低字节放在低地址空间,大小端的顺序是以字节为单位的,而不是bit),则数据排布为0000_9000,0032_6001,则访问到的数据为0x1 若系统为大端存储(数据的高字节放在低地址空间),则数据排布为0090_0000,0160_3200,则访问到的数据为0x0 逻辑地址0x00402001:访问逻辑地址0x00402001时,对应的页目录偏移量为1,有效位为1,对应的物理地址为0x1000,逻辑地址对应的二级页表偏移量为2,对应的页表项有效位为1,对应的物理地址为0x5000,页内偏移量为1字节,访问到的数据为0x0 (4) 答 ​ 要想访问物理地址0x326028,先看他的物理页号,物理页号为$0x326028 » 12 = 0x326000$则对应起始物理地址为0x20000页表中偏移量为1的项,对应页目录中偏移量为3的页表项的映射,页内偏移量为$0x28$,故逻辑地址为$0x00c01028$ 二.第二题 前提:LOAD STORE操作的均为虚拟地址 大尾端为将数据的高位存在低地址端 0000430: e684 6c4e 0100 1800 53ef 0100 0100 0000 0000440: b484 6c4e 004e ed00 0000 0000 0100 0000 在大端模式下,前32位应该这样读: e6 84 6c 4e 在小端模式下,前32位应该这样读: 4e 6c 84 e6 ...

April 14, 2024 · 1 min · sudo

OO-Unit2-HW7

OO第二单元第三次作业 [toc] 0.本次作业新增需求 新增一种RESET请求,可以将单部电梯重置为双轿电梯 [时间戳]RESET_ACCEPT-电梯ID-换乘楼层-每个轿厢的满载人数-每个轿厢移动一层的时间(单位s) 重置电梯两个轿厢的参数相同(满载人数、移动一层的时间) 重置完成后轿厢A默认在换乘楼层的下一层,轿厢B默认在换乘楼层的上一层 轿厢A只能在换乘楼层及以下运行,轿厢B只能在换乘楼层及以上运行 两个轿厢不能同时处于换乘楼层 特别地,双轿厢电梯可以不受RECEIVE约束地从换乘楼层移动一层以离开换乘楼层 保证双轿厢电梯不会接收到第一类重置请求和第二类重置请求。 换乘楼层在3层和9层之间 双轿厢电梯耗电量为$\frac 1 4$ 1.处理流程分析 ​ 本次作业中主要的任务即为处理新增的RESET请求,对于上次作业已有的调度策略没有进行改变,目标比较明确。下面是时序图 1.1 UML时序图 1.2 新增RESET请求的处理 1.2.1 Elevator ​ 本次作业中对于双轿厢RESET请求的类协作与第二次作业中普通重置请求相同。这里我对于RESET请求的处理方式是接收到双轿厢RESET请求时新建一个线程,将原来的线程作为A轿厢,新建的电梯线程作为B轿厢。这里首先给出电梯新增的几个属性 1 2 3 4 5 6 7 //Elevator private char elevatorType = 'C'; // 轿厢类型 A B private int transferFloor = -1; private int lowerLimit = 1; private int upperLimit = 11; private Flag busyFlag = null; // 一组电梯共享一个flag进行通信 private AtomicInteger personSatisfied; elevatorType:电梯的类型,初始时电梯的类型为C类型,双轿厢重置后修改为A/B类型 lowerlimit/upperlimit:电梯的运行楼层限制,用于Dispatcher中对于符合运行范围电梯的筛选 transferFloor:换乘楼层(似乎没用,不是lowerlimit就是upperlimit) busyFlag:用于控制双轿厢电梯在换乘楼层互斥的需求,这里借鉴了讨论区中实现线程安全类的思路 personSatisfied:计数器,所有电梯线程和输入线程的共享变量,当一个乘客需求被满足(送到指定楼层),该原子类型+1 ​ 参考NormalReset在第二次作业中的实现方式,DoubleCarReset我选择相同的实现方法,当电梯拿到DoubleCarReset请求之后立即进行重置 ...

April 9, 2024 · 6 min · sudo

OO-Unit2-HW6

OO第二单元第二次作业 [toc] 0.题目新增需求 乘客不再固定电梯接送,设计电梯调度策略 增加RECEIVE输出,避免自由竞争策略 增加RESET请求,时长1.2s,在RESET期间电梯处于静默状态(不可以开关门、移动、RECEIVE等),重置电梯 1.处理流程分析 1.1 电梯调度策略 ​ 在本次作业中,需要设计将乘客分配给合适的电梯的调度器(补第五次作业偷的懒),我的设计中选择将调度器作为一个线程实现,输入线程与调度器线程交互,调度器线程与六个电梯线程交互。对于单个电梯运行的策略我保留了第五次作业的LOOK算法,对于多部电梯的分配策略,我选择了性价比较高的调参方法,性价比体现在代码量较少的同时能够拿到比较好的性能分数。UML时序图如下 1.1.1 UML时序图 1.1.2 调参算法 ​ 所谓调参算法其实就是选取几个有关电梯的指标,给这些指标赋予合适的参数,为每部电梯计算出得分,选择得分最高的电梯进行分配。我选取的指标有电梯接到该乘客需要走的距离,电梯中人数,电梯等待队列中人数,电梯容量,电梯速度。 距离:这里距离的计算是不准确的,没有找出电梯运行的上确界或下确界,即没有找出电梯运行到哪里就可以转向,而是同一按照1/11处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private int getDistance(int fromFloor,int toFloor,int curFloor,boolean direction) { int distance = 0; int flow = (direction) ? 1 : -1; if ((toFloor - fromFloor) * flow > 0) { // 乘客移动方向与电梯当前移动方向相同 if ((fromFloor - curFloor) * flow >= 0) { // 电梯沿当前方向能接到乘客 distance = abs(fromFloor - curFloor); } else { if (flow == 1) { distance = 20 - curFloor + fromFloor; } else { distance = 20 + curFloor - fromFloor; } } } else { if (flow == 1) { distance = 22 - curFloor - fromFloor; } else { distance = curFloor + fromFloor - 2; } } return distance; } 电梯状态:电梯状态这个参数实际上是电梯容量、电梯中人数、电梯等待队列中人数三个量经过调参得来的,可以适当增加电梯等待队列中人数的权重,避免给一部性能好的电梯分配太多乘客,这样的性能可能还不如大家都运行 ...

April 9, 2024 · 4 min · sudo