CS162 23Fall总结

news/2025/1/24 12:28:15/文章来源:https://www.cnblogs.com/is-zq2003/p/18689372

CS 162 23Fall Lab 总结

PintOS是一个x86架构的教育用操作系统,它支持多线程,加载并运行用户程序,以及文件系统。骨架代码只提供了很简单的实现,本Lab需要丰富并增强这些模块的功能。本实验提供Bochs2和QEMU3模拟器模拟x86 CPU和相应外设来运行并调试PintOS.

PintOS源码结构:

threads/: PintOS的基础内核相关源码,包括bootloader,内核入口, 基础中断处 理,内存分配,CPU调度等。

userprog/: 用户程序支持相关源码,包括页表管理,系统调用处理,缺页处理, 各种traps,以及程序加载器。

filesys/: 文件系统相关源码,可以使用初始的接口进行文件操作,后续会进行扩 展以及支持树形目录等。

devices/: I/O设备接口相关源码,包括键盘,磁盘,时钟等等。

lib/: 部分C标准库相关源码,有kernel子目录和user子目录,分别被链接进内 核程序和用户程序。

PintOS启动过程:

1. BIOS将Pintos引导加载程序(threads/loader.S)从磁盘的第一个扇区读取到地址0x7c00的内存中。

2. 引导加载程序将内核代码从磁盘读取到地址0x20000的内存中,然后跳转到内核入口点(threads/start.s)。

3. 内核入口点的代码切换到32位保护模式1,然后调用main(threads/init.c)。我们称运行init.c的线程为OS的主线程。为了使其能运行用户程序,需要给它分配一个pcb来管理子进程,即使它不是一个进程。

4. OS主线程通过初始化线程调度器、内存子系统、中断向量、硬件设备和文件系统来启动Pintos,并根据命令行输入(argv)来加载用户程序或运行测试,运行完毕后shutdown.

PintOS内存模型:

虚拟地址一共4GB,其中0-PHYS_BASE(3GB)是用户空间,PHYS_BASE-4GB是内核空间。

内核空间是所有进程共享的,并且直接映射到物理地址,pa = va – PHYS_BASE. 即虚拟地址PHYS_BASE的物理地址是0.

用户空间每个进程不同,通过二级页表映射,在进程切换时由pagedir_activate()通过修改CR3寄存器(页目录基址寄存器)进行切换。

内核中分配内存以页为单位,通过内存池管理连续空闲物理帧,并且分为用户池和内核池,前者分给用户空间,后者分给内核空间。

CS162没有实现完整的虚拟内存,page_fault异常处理未实现。

Proj0: Introduction

程序启动时有一个段错误,是某一行代码的问题,调试一下找出来改了就好了,该项目只是让我们熟悉一下PintOS的运行和调试。

Proj1: User Programs

实现用户程序支持和浮点数操作。该proj涉及tcb,pcb,syscall,FPU管理等,此外还有task-state segment (tss) ,主要用于中断时栈的切换(用户态到内核态需要切换栈帧,而内核态到内核态不用,且CPU是先根据tss切换到内核栈再保存断点的)。使用tss而不用tcb保存栈信息的原因是tss是x86硬件支持的,而tcb不是。

Task1. Argument Passing

实现用户程序加载时的命令行参数传递,即argc和argv[]. 进程创建过程首先是 由当前进程执行process execute调用thread_create创建一个对应的内核线程并由该 线程调用start_process,然后在start_process中进行pcb、中断栈帧等的初始化,在 load可执行程序,在加载成功后通过移动中断栈帧中的esp并进行相应赋值将参数存 到栈中,最后通过中断返回指令将控制权转交给用户程序即可。

以下是参数在栈中的放置方式,注意x86由于call的时候esp必须与16字节对 齐,因此最后将argc传入后需要进行对齐。

Task2. Process Control Syscalls

实现进程控制相关系统调用。要注意系统调用和函数调用一样通过栈传递参数, 系统调用处理时要先检查参数是否合法,如是否空指针,是否访问了内核空间,是否 访问了为映射空间。系统调用的第一个参数是系统调用类型,根据该类型调用对应的 处理函数,并将返回值保存在中断栈帧的eax中。

exit()

进程退出系统调用。释放进程的各类资源,如页表,打开文件,子进程链表 等,此外如果父进程还在,需要signal对应的信号量,以便父进程进行wait,并 设置退出状态供父进程获取。

exec()

进程执行系统调用。功能相当于UNIX中的fork()+exec(),即创建新进程并执 行新的可执行程序。为了支持并发加载,设置一个全局的load_list,将待加载的 进程放入链表,并使用信号量对每个节点进行同步。当父进程创建了线程去加载 子进程时,父进程必须阻塞等待看是否加载成功。子进程加载完毕后唤醒父进 程,然后等待父进程将自己加入到其子进程链表中,才可以开始运行。

wait()

进程等待系统调用。等待子进程退出并获取其退出状态。通过子进程链表中 的信号量进行wait,然后获取退出状态,再将其从链表中移除即可。要注意每个 进程只能wait他的子进程,且子进程不存在继承关系,即不能wait孙子进程,否 则直接返回-1. 此外如果某个进程意外退出(kill),也要直接返回-1,因此在还要修 改userprog/exception.c中的kill()函数,将退出状态设为-1.

Task3. File Operation Syscalls

实现文件操作系统调用。骨架代码已经实现了基本的文件系统,因此这里直接调 用相应接口就好。注意这些接口不是线程安全的,因此要进行同步,这里直接加一把 全局文件操作锁。

create(), remove()

创建一个文件和删除一个文件,调用相应文件系统接口即可。要注意这里的 删除文件指的是将其从目录中删除,若该文件已被某进程打开,该进程还可继续 对文件进行读写,并且由于其已从目录中删除,其他进程无法再打开该文件。当 所有对应的文件描述符都关闭时该文件才真正消失。这一规则与UNIX一致。

open()

打开一个文件并返回一个文件描述符。文件描述符(fd)是一个非负整数,是下 图fd Table中元素的下标,每个元素都是一个struct file* 指针,指向一个struct file结构体,该结构体除了存储读写权限,偏移量外,还有成员struct Inode* 指 针指向对应的struct Inode,Inode对应唯一的文件,存储着该文件在磁盘中的位 置等信息。每次open一个文件,不管是不是同一进程,都会构造新的struct file,并返回新的fd. 并且在PintOS中子进程不会继承父进程的文件描述符表。要 注意文件描述符0 (STDIN_FILENO)和1 (STDOUT_FILENO)分别表示标准输入和标 准输出,不能被open()返回。

read(), write()

对fd对应的文件进行读写操作。若fd == STDIN_FILENO则从键盘读入,调 用devices/input.c中的input_getc();若fd == STDOUT_FILENO则输出到控制 台,调用lib/kernel/console.c中的putbuf().

seek(), tell()

设置或获取当前fd对应file的读写位置。要注意seek如果超出EOF需要对 文件进行扩展,这将在proj 3中实现。

close()

关闭fd对应的文件。调用file_close()释放struct file,file_close会调用 inode_close减少引用次数,当系统中该文件inode的引用次数减为0时才真正关 闭一个文件。

Task4. Floating Point Operations

实现支持浮点数操作。x86中浮点数没有专用寄存器来存储,而是使用栈来存 储。由于CPU中只有一个floating-point-unit (FPU),线程要共享它必须存储相关状态 FPU save state,共108字节,在线程切换或中断时进行状态切换,在线程切换栈帧和 中断栈帧中添加FPU save state并在switch.S和intr-subs.S中添加相应指令即可,这 和寄存器值等其他上下文类似。此外FPU save state还需要在线程/进程创建时进行初 始化,需要修改start process()中初始化中断栈帧的代码,这是寄存器切换不需要的。

Proj2: Threads

实现线程优先级调度和用户进程的多线程支持。PintOS每个线程在内核中都有一页内存用于存储它的TCB (struct thread)和内核栈,如下图所示。线程切换时会调用thread.c中的schedule(),根据调度算法获取下一个线程的tcb,并调用switch.S中的switch_thread()来进行上下文切换,这是一个汇编函数,在切换完成后当前线程也切换了,也就是说每次线程重新获得CPU都是从switch_thread()中返回,之后再调用thread.c中的thread_switch_tail()进行状态设置、页表切换等收尾操作。

另外PintOS是分时系统,在计时器中断处理函数中会查看时间片是否用完,用完了会在中断结束时让出CPU (intr_yield_on_return).

Task1. Efficient Alarm Clock

实现更高效的timer_sleep()函数。原来的实现是忙等(busy wait)直到时间到,可以 通过thread_block()将其阻塞并将其加入到sleep_list中,在每次时钟处理函数中检查 是否到时间,到了将其thread_unblock()即可。

Task2. Strict Priority Scheduler

实现线程的优先级调度。除了schedule()外还需要修改3个同步原语(互斥锁,信 号量,条件变量)的相关调度代码。

另外,为了避免优先级逆转(priority inversion)现象,即高优先级的线程A由于同 步机制等待低优先级的线程B,而B由于中优先级的线程C得不到CPU,我们需要实 现优先级捐赠(priority donation)机制,即将B的优先级暂时提高到和A相同。只需实 现lock的优先级捐赠。要考虑嵌套捐赠,即A等C,C等B的情况(将C和B都提高 到A). 我的实现方法是在tcb中添加一个waiting_lock成员,表示正在等待的锁,锁中 有holder成员,表示持有锁的线程,这样就可以形成一个等待链表,每次lock()时遍 历等待链表对链表中的线程进行donation.

而unlock()时捐赠情况可能会变,如H(high)和M(mid)分别在等L(low)的lock1和 lock2,则L的优先级是high,当lock1释放时,H不再捐赠L,而变成M在捐赠L, 即L的优先级要变为mid. 这可以通过在tcb中添加一个lock_list链表,表示所有持有 的锁,当释放一把锁时遍历其他锁获得最高的优先级来捐赠。

实现优先级调度的数据结构选择有多种方法:

(1)链表:这是最简单的方法,插入O(1),查找O(n),修改O(n).

(2):用堆来维护就绪队列,插入O(logn),查找O(1),但缺点是不方便修 改优先级,需要遍历堆来找到目标修改后再上滤或下滤,复杂度O(n).

(3)位图+多级队列:这是我想到的相对好实现的最好的方法,每个队列对应 一个优先级,插入是O(1);如果没有位图则查找需要遍历所有队列O(n),而使 用位图表示某一队列是否为空,查找优化为O(1);修改在最坏情况下仍是O(n).

(4)红黑树:复杂度稳定但难实现,插入,查找,修改都是O(logn).

Task3. User threads

实现部分pthread线程库和线程同步syscall以支持用户多线程。PintOS是一对一 的线程模型,也就是说每个用户线程对应一个内核线程。在实现用户多线程后还需要 修改之前User Program的部分代码,主要是exit(),分以下情况:1.主线程调用 pthread_exit(), 等待其他线程结束然后以状态0退出;2.任何线程调用exit(n),整个进 程应该以状态n退出;3.任何线程意外终止,整个进程应该以状态-1退出。上述中的 “整个进程退出”指该进程其他线程也必须马上终止。当同时发生时三种情况的优先级 是3>2>1.

pthread_create()

创建一个用户线程。与process_execute()类似,创建一个新的内核线程让其 运行start_pthread()进行初始化,创建栈,设置参数,并设置中断栈帧中的eip, esp等以在返回时运行用户线程,此外还要将该线程加入到pcb的thread_list 中。注意多用户线程栈结构如下。

pthread_exit()

终止当前用户线程,如果是主线程,则join所有其他线程然后退出。其他线 程:先signal join()等待的信号量,然后调用thread_exit()将其从调度队列中去掉 并释放其内核栈即可。主线程:为了防止其他线程join主线程,提前signal对应 信号量,然后再join其他所有线程,最后调用process_exit().

pthread_join()

等待指定线程结束。只能join同一进程的线程,且每个线程只能被join一 次。设置信号量进行同步即可。

lock_init(), lock_acquire(), lock_release()

互斥锁系统调用。在tcb中用一个链表管理该进程所有互斥锁然后使用其接 口进行相应操作即可。

sema_init(), sema_down(), sema_up()

信号量系统调用。在tcb中用一个链表管理该进程所有信号量然后使用其接 口进行相应操作即可。

Proj3: File Systems

丰富扩展文件系统的功能,如实现内存缓冲区,支持可扩展文件,树形目录结构等。

Task1. Buffer cache

实现文件读写的内存缓冲区,替换算法自行选择,我选择的是enhanced-clock算 法,即考虑used位和dirty位,扫描缓冲区,按00,01,10,11的优先顺序进行淘 汰。要注意保证对缓冲区访问的线程安全,可以对每个block都加一把读写锁。此外 如果只在淘汰或关机时进行写回会使系统在崩溃时更脆弱,因此可以定期写回,可通 过时钟处理函数实现。

Task2. Extensible files

实现可扩展文件,使得文件大小可变。这里使用的是类似UNIX-FFS的方法,前 12个block使用直接索引,往后128个block使用一级索引,再往后128 * 128个 block使用二级索引,每个block 512B,也就是说最大可支持8MB的文件大小。主要 修改inode_create()改变分配block的方式,注意要一块一块地分配,因为索引不需要 连续块,还要修改inode_write_at()以支持动态文件扩展。

Task3. Subdirectories

实现树形目录结构和目录操作syscall并且支持相对路径,还需要修改p1实现的 部分文件操作syscall

树形目录结构通过路径解析并递归读取目录文件即可实现,路径解析时开头如果 是 ’/’ 表示绝对路径,否则是相对路径,相对路径则直接从cwd开始解析,且要注意. 和..这两个特殊的文件名,表示本目录和上级目录。注意创建和删除文件时还要在其目 录文件中增加或删除目录项,若某目录是某进程的cwd或已被某进程打开,则不允许 删除(另一种做法是允许删除但不允许再打开该目录下的文件)。

系统调用部分,目录文件也是个特殊的文件,和普通文件一样使用文件描述符来 操作,因此在fd Table中还需要区分其指向的是目录文件还是普通文件,我在pcb中 增加了一个位图来描述,此外在操作时也要判断类型区分接口的使用。

chdir()

更改当前进程的工作目录。更改pcb中的cwd指向的struct dir即可。

mkdir()

创建新的目录。注意在父目录中添加目录项。

readdir()

读取目录中下一个目录项的文件名。struct dir中有pos成员来记录当前读取 的目录项的位置。

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

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

相关文章

《汇编语言》阅读笔记

如题汇编语言 首先,我必须赞扬王爽老师,这是我见过写的最好的教科书了. 然后要注意,汇编语言这本书的前提是8086PC机十六位处理器.还要学x86和x64以及其他奇奇怪怪的架构的汇编 全称 AH&AL=AX(accumulator):累加寄存器 BH&BL=BX(base):基址寄存器 CH&CL=CX(co…

【unity】学习制作2D横板冒险游戏-3-

添加野猪添加刚体2D组件,碰撞组件,该碰撞组件是确保野猪能跟地面碰撞添加玩家图层和敌人图层,并应用,使野猪和玩家不会彼此被推着走碰撞剔除中选择Player和Enemy再增加一个碰撞组件当作触发器,设置如下,该碰撞组件是确保人物和怪物的互动基本属性及其计算 再如图路径下创建…

远控流畅游戏,我用过最棒工具ToDesk

孤独乏味人又懒,能够干嘛来消遣?手游、端游都好玩,没有高端设备在身边,也是能够轻松玩!究竟怎么实现的?其实就是通过ToDesk这种专业的远程控制软件来随时随地操作异地的目标设备从而帮助实现更丝滑给力的远程游戏畅玩体验!当然,用户能够借助完成这需求的辅助工具并不止…

C# Odbc Informix读取中文方法

一。 部署好informix服务器后 二。 在windows主机安装好Informix Client-SDK, 这个软件客户端时免费的,可以在官网下载 三。配置好ODBC连接 1.用户名密码服务端口自行填写 2.关于客户语言的配置要使用以下参数: Client Locale -> en_US.CP1252 Database Locale -> en_…

ToDesk远程连接几项模式区别何在?

对于很多用过ToDesk远程控制软件进行随时随地跨系统、跨设备操作的小伙伴们来说,它无疑能解决做种场景下问题,无论是在职场工作中,还是日常生活中,均能带来很多便捷!虽然轻松使用很简单,但很多所不知道的是其实应对不同的情况,选择不同的连接模式更有益!以下木木小编就…

如何在网站后台修改首页?

修改网站后台首页通常涉及对后台管理界面的调整。以下是详细步骤:登录后台:使用管理员账号登录网站后台管理系统。 进入模板管理:在后台管理系统中找到“模板管理”或“模板文件管理”选项。 编辑模板文件:找到后台首页的模板文件(如index.htm或index.php),使用代码编辑…

一些关于软件测试中登录模块的测试用例

以下是一份针对软件测试中登录模块较为完整的测试用例,涵盖了各种常见情况,尽量保证较高的覆盖率:一、功能测试1. 正常登录 2. 用户名错误 3. 密码错误 4. 用户名和密码都错误 5. 用户名和密码为空 6. 仅用户名为空 7. 仅密码为空 8. 用户名和密码长度极限值 9. 记住用户名…

如何修改网站管理员admin的信息?

登录网站后台:使用管理员账号(通常是admin)登录到网站的后台管理界面。进入用户管理页面:在后台管理界面中,找到“用户”、“用户管理”或类似的菜单选项,点击进入用户管理页面。找到管理员账号:在用户管理页面中,找到管理员账号(通常是admin),点击账号名称或编辑按…

如何在网站上修改源代码?

要在网站上修改源代码,您可以按照以下步骤进行操作:确定要修改的代码:确定您想要修改的网站源代码文件。 找到代码文件:根据您的网站结构,找到包含要修改代码的文件。 编辑代码文件:使用文本编辑器或专业的网页设计工具,编辑代码文件中的代码。 保存修改:修改完成后,保…

如何在CMS系统中动态修改网站栏目?

在CMS系统中动态修改网站栏目通常涉及后台管理界面的操作。以下是详细步骤:登录后台:使用管理员账号登录CMS后台管理系统。 进入栏目管理:在后台管理系统中找到“栏目管理”或“频道管理”选项。 编辑栏目:选择需要修改的栏目,进行编辑。可以更新栏目名称、描述、排序等信…

补充篇:Unity中Compute Shader的基本使用

补充篇:Unity中Compute Shader的基本使用 Compute Shader 可以充分利用GPU来帮助我们处理大规模的并行任务。虽然名字带Shader,但它可不光用于图形学,所以即便对渲染相关的知识不甚了解,也不妨碍学习它的用法。 基本流程 对任意 Project的文件夹右键Create/Shader/Compute …

修改网站首页大图通常涉及到更新网站首页的图片文件

找到图片文件:首先需要找到网站首页大图的图片文件所在的位置。通常,网站的图片文件会存储在服务器的特定目录中,例如 images 目录。 备份原图片:在修改之前,建议先备份原图片,以防修改过程中出现问题需要恢复。 准备新图片:准备好要替换的新图片,确保新图片的尺寸和格…