操作系统课程学习笔记

系列课程:https://www.bilibili.com/video/av30708793/?spm_id_from=333.788.videocard.1

https://www.bilibili.com/video/av51437944?from=search&seid=16500105152254661287

操作系统中算法没有对错只有优缺点。
哈工大课程笔记:

  1. P1:介绍操作系统是什么? 是硬件和应用之间的一层软件。 起源于图灵机:一个模拟人脑的计算模型。后面有了通用图灵机:可以自定义控制器逻辑。----> 冯诺依曼祖师爷提出了程序存储思想,把程序存入内存中实现控制器逻辑(感谢祖师爷赏饭吃)。
  2. P2:操作系统是如何工作的:其本质是自动地取址执行。以及详细介绍了开机时的那一段汇编程序。。。具体可以看一下。
  3. P8-cpu管理图像:起因cpu执行程序(逐行取址执行)时,I/O操作太慢了,cpu不应该等待程序的I/O操作。所以会多道程序、交替之行(并发)。这里可以引出进程和PCB。“进程”这个概念实际上就是描述此时此刻的运行中程序的概念!
  4. P9-cpu是如何进行切换(schedule函数): 进程存在几种状态:新建态->就绪态->运行态->阻塞态->就绪态->运行态->终止态。状态也存在队列:物理内存实际正在运行的进程队列、就绪队列、磁盘等待队列。
    1 先把当前进程切换状态
    2 把当前进程在物理CPU上的信息都保存到PCB。
    3 从就绪队列中获得下一个进程的PCB,并将其写入cpu中。
    4 这里会引入一个问题就是,多个进程在内存中可能出现内存冲突的问题,所以每个进程都会维护映射表(页表),映射表实现虚拟内存对应的物理内存分离。
    5 在切换的时候,还可能涉及到一些合理性,比如加锁但还没有释放的时候。此时就不能够切换。
  5. P16- 进程同步与信号量。 在进程合作的情况下,存在a进程需要等待b进程某个步骤做完才能往下做的实例。要明白信号到信号量的不同:有时候只发信号不能解决所有的问题,比如课中说的第二个睡眠的进程永远不会被唤醒的问题。所以我们还应该记录一个量,比如在这记录多少个睡眠的进程。所以这里就引出信号量。 感悟:可以想想互斥锁里面的信号量,0和1的变化
  6. P17-信号量的临界区保护。 当调度过程中多个进程并发获取(竞争)共享变量(比如1)。显然容易出错。 一个容易想到的操作就是加锁、修改、释放锁。 等于说其实就是只有一个进程能够进入修改这个变量的代码段(临界区)。临界区显然会多个一起出现。当一个进程a进入其临界区时,其他进程都不能进入相关临界区。
    那么如何做到呢?基本原则:互斥; 其他原则:有空让进、有限等待
    a 轮转法(值日):多个进程轮流使用。 但是不满足有空让进(1 进程得等2进程做一遍才能进入)
    b 标记法: 但是存在ab 同时标记、导致都自旋。无限等待
    c 非堆成标记:有一个进程会做更多的事情。
    多个进程----
    1 软件算法:面包店算法(类似排队叫号的过程,每次等待取号,号小的先接受服务)
    另一个角度:多个进程进入是因为调度而产生、所以我们阻止调度。(但如果是多核cpu就没办法,只能阻止一个cpu)
    2 硬件算法:
    前面说的上锁无疑是一种很好的做法。(锁也是另外的一个=1的信号量)。但是这里有一个难理解的地方: 不能用信号量去实现锁,因为本来信号量就需要保护,保护的方法之一就是加锁,加锁又用信号量实现= 又需要加锁,这就死循环了。
    所以锁是一个信号量没错,但对于这个信号量我们不需要保护,这里就引入了硬件原子指令。通过硬件来保证原子性,要么获得锁、要么获得不到。
    ------上述中很容易引出CAS指令,不详细展开。
  7. P18- 进程阻塞之后怎么唤醒? 在进程中隐藏着一个世界上最隐蔽的队列,在进程运行的内核栈中隐藏着tmp指向下一个进程的pcb等,这块就没听太懂。。。但被唤醒之后,会将所有等待信号量的进程唤醒,这个时候会由schedule决定是谁执行,这里可以引出aqs算法。(hahahaha)
  8. P20中 关于进程如果阻塞,会从内存进入磁盘,当其再重新回到内存是,此时的物理地址可能发生变化,所以程序应该在运行时重定位其在物理内存中的位置(base+x)。这个过程叫地址翻译。 这些信息都会放在PCB里面(进程信息描述文件)。而且程序并非整个都放入内存中,而是分段:main函数、变量、栈、函数库都是分开的,每个段都有自己独有的特点例如main函数段一般都只读,栈段只会递增等等。 且内存扩容时不需要变动整个程序,只需要移动某一段。这里就引出了段表LDT表, 存储每一段的基地址) ,GDT表也是类似段表的东西,不过存的是OS。
    感悟:所以一个程序在编译的时候会分成多个段,放入内存中,记录这些信息的就是LDT表(这里是否需要整理一下LDT表和页表的关系)于是实现了虚拟内存地址和物理内存之间的逻辑关系,而这些信息都存储在进程PDB中。联系到golang 中的主协程和goroutine之间的内存关系(例如逃逸等等),实际上是属于更高层的策略,主协程认为的连续地址,说不定在操作系统的页表中表现出来的也是分段罢了,不过是靠ldt表来联系在一起。
  9. P21-承接p20:内存分区分页。分区解决的是如何在内存中找到一段空闲的分区,分页是解决地址联系问题。 首先知道用的是可变分区 ,当进程申请内存和释放内存时都会维护空闲分区表和已分配表。这里会引出一个内存分配算法:首先适配、最佳适配、最差适配:传送门。可变分区造成的问题:内存碎片,容易造成一个进程请求160k内存,但没有任何一个内存分区>160k,但存在150k+10k的两端内存碎片,这个时候迫于无奈就只能进行内存紧缩(移动已占用分区,合并),这个过程中上层进程都无法进行,所以效率会非常低。
    于是我们引出了连续到离散的思想,实现这个思想的机制就是分页:针对进程中每个段的内存请求,系统会一页一页的分配。(其实也存在类似碎片的不连续单位,但这个单位是可分配的。 而且和固定分区有类似的地方,但是每一页只有4k,每一段都最多浪费一页内存,这个是完全可以接受的)。当然这都是从物理内存的角度,但用户希望是分段的,所以还需要将两者联系起来。这里就引出了页表(寄存器=cr3),页表会存储每一页的长度等信息,进程取出地址addr,会根据addr去页表中算出其属于哪一页(这些过程都由硬件来实现)。然后根据其页号去页表中获得的物理地址(页框+偏移量等等):这一块可以看看这个
  10. P22- 因为页小,所以页表会很大(32位地址=2^32 :4G/4k=1M的页号)。而每个进程都会维护一个页表,所以n个进程就需要nM的内存来存储页表这显然很不科学。一个优化就是页表中只存放用到的页,但这样就不连续了,那就需要访问多次内存,显然会很慢。 这里引入多级页表(其实这里让我想到了跳表):页目录10位(索引)+页号10位(2^10 = 1000节)+偏移量12位(4096=4k)。此时一个进程只关心自己占用的某一章(页目录)下的某一页的信息+一些驻留内存(没看太懂)=16k。
    思考🤔:多级页表解决了空间利用问题,但增加了查询次数(多一级=多一次),等于增加了访问内存次数。于是这里引入了快表TLB:一个寄存器,很小(64条),只记住最近使用的页号和对应的物理页(页框号)。等于用寄存器加一层页表的缓存。。。寄存器真是nb啊,比内存还要快。 至于为什么是存最近使用的,因为程序访问具有局部性(时间、空间局部性)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值