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

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字节的索引区,且允许采用一级索引进行组织,那么该 文件系统支持的最大文件是多少字节? ...

May 25, 2024 · 1 min · sudo

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结构体主要用于记录已经打开的文件的状态,便于用户直接使用文件描述符对文件进行操作和申请服务。由于文件描述符主要是为用户所用,因此它对应的是磁盘映射到内存中的数据。 ...

May 19, 2024 · 3 min · sudo

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,故对于设备的读写必须通过系统调用实现 ...

May 19, 2024 · 20 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