两套系统接口调用_操作系统学习笔记

操作系统学习笔记

哈尔滨工业大学李治军老师的MOOC《操作系统》的学习笔记。

课程地址:

  • 中国大学MOOC-操作系统
  • B站搬运版本,评论区有课件

Lecture1:什么是OS

操作系统(OS)是计算机硬件和应用之间的一层软件。

f52a46a6de348590ab9b254dc9a65e07.png

846dd983568eb1f70396ac409c0cedf9.png

上层应用使用OS提供的接口来使用计算机硬件。

Lecture2:打开操作系统

计算机说到底就是一个计算模型,从图灵机到通用图灵机,再到冯诺依曼计算机结构。

计算机执行的过程就是:取指执行。

因此要将OS从磁盘/硬盘中,读到内存中,这样才可以正常运行。

开机之后,计算机执行的第一句指令:

dad0c4aee8c6647ed7b6b6403d915b29.png
  • 刚开始在FFFF0处,在BIOS区执行固定的程序。检查RAM,键盘显示器磁盘...
  • 将0磁道0扇区的东西(操作系统的引导扇区,一个扇区512字节)读到0x7c00处
  • 跳到引导扇区执行启动程序。

1929cef88c0dc96ba5792ff10be3b7fd.png
  • 引导扇区代码bootsect.s,是汇编代码
  • 地址都是段地址:偏移地址

0ef9795fb091ff22dd80664f3010858d.png
  • int 0x13是关键,BIOS开始读磁盘扇区的中断。为setup读四个扇区,system模块继续读。

7b826fd3f2ae5110fbde4baa611b1ba6.png
  • 读了setup之后,进入ok_load_setup,int 0x10是关键,BIOS中断,显示字符。

23d27ec33df6fe22d08e0a28550beba2.png

e6627aa8afe95b1b41cd96b7c437932a.png

Lecture3:操作系统启动

将操作系统读入内存+完成初始化。

1. setup模块,setup.s

6a914ddead028e41b32ad46e284c433e.png
  • int 0x15BIOS中断,获得物理内存的大小,放在ax中,然后送到90002保存。方便之后管理内存。
  • 获得显卡参数、设备号等等信息,放在内存中,方便之后管理。
  • 移动system模块到0位置。整个内存中,操作系统一直放在0地址处开始的位置

fb01de55c90a00ee7a5b3cf734db6591.png
  • 最后的gdt:xxxxx,初始化gdt表。

96def220af6bf545cee1500c7d241888.png
  • setup最后执行一个jmpi 0,8,因为已经进入了保护模式,寻址方式进行改变,这个程序跳到零地址处,即system模块。
  • 传统jmpi 0,8,即0给ip,8给cs,偏移地址为cs左移4位+ip,这样最多能表示20位,即1M的地址,太少了。需要改变寻址方式,切换到32位模式(保护模式),启动32位寻址模式(4G)。
  • cr0最后一位如果为1,进入保护模式。

2fe8150e85dc3f2698bb721d14c27d3a.png
  • 使用gdt得到地址。现在cs叫做选择子,存放的不再是地址,而是表中的下标,通过cs在表中(即gdt全局描述表)查找,再+ip,得到地址。
  • gdt是通过硬件实现的,速度很快。
  • 保护模式下的中断,也变为在IDT找,找中断处理函数的入口地址。

2. system模块

system模块的第一部分的代码为head.s。

2e0452c22b80d2fb77a8d8e8a17281f5.png
  • head.s又做了许多事情,在保护模式下,与前面的16位汇编代码不同。

f5c7f2fc6f55d9323ab97baec999fedd.png
  • main永不停止,不会返回,main如果返回了,就进入L6,死循环。

262ad60d6420129f090d68a4e93e4d71.png
  • main中执行各种初始化的函数。

比如,mem_init执行内存初始化。

ab57cfbb6bf205c451cf3aae5d710adc.png
  • 初始mem_map数组,每次解4k内存,4k作为一页。数组表格中保存哪些是内存是使用的,哪些没用。
  • end_mem,即为总的内存,可以求出数组的尺寸大。

Lecture4:操作系统接口(OS Interface)

操作系统的接口:连接上层用户和操作系统软件,屏蔽了实现的细节。

用户使用计算机:通过命令行、图形按钮、应用程序。这是总体的使用,并不是真正的接口。

使用命令行的时候:命令对应的是程序,编译完就是可执行文件,shell命令将可执行文件执行(申请CPU执行,使用一些函数)。

图形按钮(鼠标点击、键盘按下):图形界面就是一个包括画图的C程序。硬件系统实现一个消息队列,当鼠标/键盘点下之后,通过中断送入消息队列;应用程序要实现一个消息循环,getmessage,来拿出内部的这些消息,通过消息处理函数,完成该消息对应的功能。

e1c514cd311f4b0af4e079455d410545.png
  • ==关键在于调用的重要函数,这就是操作系统的接口。==
  • 称为==系统调用==

997f0647c08c062374df5efb6912a93e.png

Lecture5:系统调用的实现

上层的函数,不能直接调用内核中的信息,不能直接jmp/mov,需要使用系统调用提供的接口才可以进入到内核中,得到信息,这样更加安全。

be5d028573f60de81c53cf9cf39c7d83.png
  • 使用一种硬件设计,来将内核程序和用户程序进行隔离,用户态和内核态。
  • DPL描述目标内存段的特权级,0表示是内核段。CPL描述当前的特权级。当前特权级比目标更小(即当前特权更高),才可以访问。

11e7db63f50f07b177ba50a4daabf04d.png
  • 硬件提供了唯一的主动进入内核的方法,即中断。
  • 系统调用的核心,执行int 0x80指令,中断处理函数处理中断,根据该中断执行相应代码完成进入内核的操作。

73495e0fad86064cdf864872d00b668b.png

cfbdfeed24afc5a8354a86f83f829192.png
  • 宏syscall3,展开汇编指令,把NR_write置给eax,int 0x80调用中断,进入内核。

int 0x80:

  • 到IDT表中找到中断处理程序入口位置(使用system call函数),跳到那里执行,处理之后再回来。
  • 会将DPL设为3,这样CPL=DPL,就可以进入内核了。

457a1b63dcbd2e89f75cf4d1f93ed4f4.png

Lecture6:操作系统的历史

  • IBSYS监控系统(1955-1965)
  • OS/360(1965-1980)
  • MULTICS(1965-1980)
  • UNIX(1980-1990)
  • Linux(1990-2000)

ed53df43ca92cac1d16c71e7764c6018.png

8b360142160a572fd7d0bc5ba959b8de.png

Lecture7:我们的任务

80920259f71d15b639f1e6cef58cb79a.png

a944cfa35f9c080f7afc4ea77819ec80.png

Lecture8:CPU管理的直观想法

操作系统在管理CPU的时候引出了多进程图像。

CPU的工作原理

  • 自动的取指执行。PC存放指令地址,取到地址,CPU执行指令,PC会自动更改为下一个指令的地址。
  • 那么最简单的管理CPU方法,就是设置好PC的初值即可。
  • 由于I/O指令比计算指令所需要的时间多得多,近似是10 ^ 6倍,因此简单的设置PC初值,一个一个指令顺序执行,I/O执行的时间,CPU需要等待,因此效率非常低。
  • 让多道程序,交替执行,可以重复利用CPU,提高效率。在一个CPU上交替执行多个程序,即并发。

如何做到并发

  • 我们需要控制PC,在CPU等待的时候,切换到其他程序,等待结束还要可以切换回来。
  • 同时切换回来的时候,要恢复原来的执行状态(这些信息在切换出去的时候需要记录下来)

进程:

  • 进程是执行中的程序,记录程序运行的样子。
  • 进程有开始、结束、程序没有。
  • 进程会走走停停,走停对程序无意义。
  • 进程需要记录ax,bx等,程序不需要。

Lecture9:多进程图像

启动了的程序就是进程,多个进程推进;操作系统只需要将这些进程记录好,要按照合理的次序推进(分配资源、进行调度),这就是多进程图像。

多进程图像从启动开始,到关机结束。是操作系统的核心图像。

99720b8c52e95da8071d2605d139cc0d.png
  • 利用PCB这种数据结构来记录进程信息。

6a25e99d85bbe1c6b249c8ea04304aa2.png
  • 操作系统有序地维护这个进程状态图。
  • PCB描述进程,队列中排队,状态表示进程的状态

多进程的交替切换(队列操作+调度+切换):

dc7bc11e73a46986c18055e41fd240d2.png
  • schedule()函数,完成切换,从就绪队列中找到下一个进程(PCB),切换到下一个进程。
  • getNext()函数,进行进程调度,即找到下一个进程。简单的方法有FIFO,引入优先级。
  • switch_to()函数,进行切换,当前进程的一些信息保存到PCB,然后把下一个进程的PCB加载,就切换到下一个进程了。
  • 多个进程可能会相互影响(因为都在内存中,可能会导致进程2直接访问了进程1的地址,导致进程1出错),因此可以限制对之前进程地址的读写,进行多进程的地址空间分离,进程之间地址互不影响,这是内存管理的主要内容。
  • 多进程合作,核心在于进程同步(合理的推进顺序)。

Lecture10:用户级线程(多个进程如何切换)

bcb75d1401a6e349d7b511062cf2a2a2.png
  • 将资源和指令执行分开,只需要切换PC,不需要切换内存中的映射表,则为线程。
  • 线程是有价值的,许多程序需要共享资源,因此不需要切换资源,使用线程即可。

b554134f2016aaf0157e9525415e9fd0.png
  • create创造第一次切换时的样子,yield进行切换(切换出去再切换回来)。

28ea162c8c04e4591348731351bfba77.png
  • ==每个线程自己一个栈,只有yield可以切换栈==。TCB为线程控制块(Thread Control Block,TCB),与上面的PCB类似。
  • 204是在调用yield时候压栈的,因此在yield结束的时候弹出204,因此不需要jmp 204,yield结束之后会弹出204,执行204的},弹出104,返回到104,yield只需要切换栈即可。按照原本的jmp,会多执行一次204。

0fae2519862465e345d7f93dd9636d6e.png

d27d4692b7d815b56c6699c3ff6437a6.png
  • yield都是用户级线程,只是在用户态切换,不进入内核。如果某个线程需要使用硬件,那么需要经过内核,那么等待的时候,内核看不到该线程还有其他的线程一起运行,会直接切换到其他的进程。这边应该执行的线程也会停止运行。
  • 核心级线程schedule(内核级线程)可以进入内核,并发性更好。由操作系统完成,用户不可见。

336f86adea8ca4964e90d5f978fa74a6.png

Lecture11:内核级线程

946b51fbd6571869dab883bac23d3989.png
  • 多处理器跟多核有区别,多处理器是每个处理器自己一套cache,MMU(相关映射),多核是多个CPU共用一套。
  • 因此多核可以快速进行多内核级线程,因为共用MMU,不需要切换资源。注意只有内核级线程,OS才可以分配硬件,才能用到多核,核是OS管理的。

bc662b0e3d79ea9464b75c652a43c1c3.png
  • 两套栈,每个线程有一套栈(用户栈和内核栈)。
  • 进入内核的时候(中断,进入内核),用到内核栈。

2abcd335d2a5208bea5058942925ae35.png
  • INT中断,启用内核栈,通过指针与用户栈相连,内核栈存放用户栈当前执行到的位置,方便之后退回。
  • SS:SP即用户栈栈顶地址,CS:IP即用户程序开始的地址。

进入内核之后:

d408843bf358126e0f7aaf822f4dd1d8.png
  • 在内核中也会切换线程,switch_to。
  • 比如S和T两个线程,首先进入S的内核(通过中断),需要切换时,在S的内核栈找到T的TCB,切换栈,到T的内核栈,使用iret中断返回指令,再切换到T的用户栈。

dfa688d178cbda431e3b4cdb08e01b88.png
  • 如果是进程切换,还需要切换地址映射表(内存管理)。

a87aa3dd034ef3b7913d8d871f7dc17b.png

2e8154ece0074945c5d56302e0654c3e.png

Lecture12:内核级线程实现

核心是栈的切换。

3e1b07ac673913fa93711313f74e3df8.png

dc1b88146708084a95f246462a3ad828.png
  • fork系统调用,引起中断。在A中执行,遇到fork。
  • 遇到 0x80,中断(找到当前内核栈,将SS,SP送入内核栈,然后执行system_call)

fd6ac9dd60833b491ad49939209820f6.png
  • 将用户态的现场情况,保存到内核栈中。
  • 内核态中执行sys_fork,即中断函数的功能。
  • 在中断函数执行过程中,发现可能需要等待,则就需要进行切换。
  • 即cmp比较,状态state如果不为0,说明该线程阻塞,则schedule切换到其他线程。
  • cmp比较,counter不为0,则时间片用光了,也需要调度切换。
  • 结束之后,进行中断返回函数,从内核栈回到用户态。

a218db4711b2ad992dcb6ae44ad92ff3.png
  • next即找到切换线程/进程的TCB,switch_to完成切换,next具体算法后面讲。

d5e7ed5dc0a4cb3da0c7512f74521f67.png
  • 图里是基于TSS的切换,效率较慢;现在都使用内核栈的切换。
  • tss是一个段,有描述符,因此也有选择子TR,用来找到这个段(TR找到描述子再找到段)。ljmp把新的段的的选择子给TR,因此找到新的tss。
  • ljmp的解释:把当前线程的现场保存到原tss中,找到新的段tssn,把新的tssn中的现场放到CPU中,继续tssn的线程的执行,就完成了切换。
  • ljmp的指令很慢,且不能流水,效率较低。

6bc39928163a814e8078804e76f729ef.png
  • 创建线程,copy_process拷贝父进程,内核栈的内容,都作为该函数的参数传入。

73f82a5e6fd394f6ffe7f5c76b99b7dc.png
  • get_free_page(),申请一页内存,不能用malloc因为malloc是用户态的,这里是内核态。
  • 初始化tss,esp0内核栈,esp用户栈。

f42348f98882152c665fc242853979a2.png
  • 靠这个eax,eax=0,执行子进程,eax!=0,执行父进程。

ae8d34b57c0b12b8969668a71fd63311.png

5d519b64dcd505c534d2181b7a081d1e.png

9afc85c58e696e1e542602925d6e1259.png

Lecture13:操作系统的“树”

  • 要运行CPU
  • 要充分利用CPU
  • 一个栈+yield导致混乱
  • 两个栈+ TCB
  • 引入内核栈的切换
  • 开始实现idea

Lecture14:CPU调度策略

即获得next的过程,调度之后获得next,然后switch_to。

c969b0a94e3c37dffef7ae43943fddf8.png

faec2ae4127eb30d3ccc5f3f4a4b698d.png

c2b9bcc35f47220f08b8cc61baf3bdf7.png

2484cc612ebabdc383b23c6719800112.png

FCFS算法(First Come First Served)

先来先服务调度方法。

40747ff69ac07350f746a590994cffcd.png

SJF:短作业优先法

让短作业向前提。这种方法的周转时间最小。

1f00ff15aa33229e3a392e10828e5b7b.png

96f61674726d96e9eec445ceb8f3da18.png
  • 使用时间片调度RR(轮转调度),可以缩小响应时间。

优先级调度

5dc18366def88232fe30e01d83d7e4e6.png

dfc6575b3ee620a95594b73c06a76a7e.png

任务的优先级应该动态调整。

Lecture15:一个实际的schedule函数

cdfa86c68f1720dfaa9aa3b482df092f.png
  • while之后,找到最大的counter,每次找到counter最大的进程,跳去执行。
  • counter既当做时间片轮转调度,又当做优先级调度。
  • for循环,恢复时间片,且让使用了IO的那些进程的优先级增加。

1291420200650c15d89bf01b7088a4bb.png

90a3a5cddb9f49af4c0f007e7effc606.png

f6d1051c703dac486e49d0e0bae7f77a.png
  • c(∞)趋近于2p,因此最大的时间片为2p。

Lecture16:进程同步与信号量

进程同步:靠信号量来实现多个进程推进的合理有序。多个进程共同完成一个任务。

ecef7d99430735a5c49167575190495d.png

35e192224f5e884d81e572882082da4c.png
  • 缓存区满了,不能继续生产了。又消费了一个,发送信号让生产者继续;
  • 缓存区空了,不能消费了。又生产了一个,发送信号让消费者继续。

fc5c433fe786b2b5cc2737923f606382.png
  • 信号表达的信息太少了。
  • 这里消费者只会发信号一次,把P1唤醒;P2不会被唤醒。
  • 因为有两个生产者,需要使用另一种方式判断,不能只用counter,引入信号量。

aaa4b071793090a21e05249ac29692d7.png

fe9e91211ab420a2bc5daf0547a5e026.png
  • 每睡眠一个,sem--
  • 每唤醒一个,sem++
  • 没有在睡眠的时候,C正常执行(消费者消费),sem++
  • P生产者正常执行,sem--
  • ==sem信号量可以表示空闲的剩余缓冲区数,生产者执行,导致sem--,如果sem<=0,则sleep;消费者执行,sem++,如果此时sem<0,则唤醒P。==

1591dcaf4c6fe8d2ce44ae099702b630.png

c011e674d16bd185dc1904676ba319ad.png

Lecture17:信号量临界区保护

信号量必须是正确的。

共同修改信号量可能会引发问题。

4e4ccfcc697e6c53e9f634ed7fc763af.png
  • 由于时间片调度的顺序问题,共同修改可能会导致共享的信号量出现错误。
  • 调度顺序无法控制,因此需要对信号量进行保护。

4897aaa23d863cbe1af163b0138f9c58.png

8c7475185ed2c279f91c97de27f0dc9c.png
  • 修改中途不会被竞争走,即一次只允许一个进程进入。
  • 即修改empty的代码,一次只有一个进程能进入。即临界区。

8c5cfc93b461dcdc856f335c439f6c2f.png

07c0e331eff08d8c482176fbaaeff07d.png

cdda9759522158a86a68429fa0d13b25.png
  • 轮换法,互相轮换,即使轮到这个进程的时候,这个进程不需要进入,则空转。

1ce4d16576808e5d33e9ac528cfc876a.png
  • 轮换法类似于值日。

d41e521f3ba15a0d078844e28fa56782.png

a847336746213555f8254c92c926dc53.png
  • 按照上面的顺序,会无限等待。
  • 这种无法达成有空让进原则。

27b730cfd28eb940c09157cc76552d2b.png
  • 结合了标记和轮转的两种思想

5da22421f7e376dd6e32c2d887ba2188.png

584c8f6cba76aacbd042956b57fe65be.png
  • 多个进程,面包店算法

3446eee6cf0fe2a65a333c80102e1aca.png
  • while中,让最小号的进入,进入之后,将这个号置为0。

09fb0c290f514fb7aa4fe13ef5d5eb22.png

e69c0a524be6dc6407bde3c20b0f0f43.png
  • 另一种方法是阻止中断,来阻止调度,这样就保护了临界区。
  • 将其中一个CPU对应的INTR置1,对多个CPU无效。

059d28e665ee3f5ccfc37b06e431244c.png
  • 对临界区进行上锁,即使用一个变量,但如果使用变量的话,修改这个变量同时需要保护。
  • 所以要使用硬件原子指令,这样就不会被打断。通过硬件原子指令来修改这个整型变量,来进行上锁。修改变量的过程是硬件的,不会被打断。
  • 多CPU同样适用。

Lecture18:信号量的代码实现

cc76b88836ec4bdc8c64bc08c5616305.png
  • cli和sti使用开关中断进行临界区保护。

a0bfa80ce990492d8beb0c39e220ffea.png
  • 这里面使用while(信号量)来实现。
  • 看sleep_on的原理,把自己放入队列中,然后把自己设为阻塞,然后调用schedule调度。

5111d42673d7a0a2cc49964bb71c7f28.png
  • 当前进程的内核栈中可以找到tmp,tmp指向下一个进程的PCB,下一个进程的PCB可以从其内核栈中找到下一个tmp......

bf467c20bbc6111c83b9b8fd0d8a350b.png
  • 唤醒操作,把等待队列的第一个进程状态变为就绪态,继续执行剩下的语句,唤醒下一个进程,下一个进程同样完成上面的操作,唤醒下一个进程......
  • 这种唤醒是把等待队列中所有进程都唤醒,因此使用的是while循环。虽然唤醒了所有进程,但是还要==挑选优先级高的先执行==。

Lecture19:死锁处理

9549f09afabc7b23c11de58df15a28cb.png
  • 如果生产者和消费者的代码中,两个申请变量的命令顺序调换了、可能会导致死锁。
  • 多个进程由于互相等待对方持有的资源而造成谁都无法执行的情况叫做死锁。
  • 死锁会导致这些进程无法工作,那么多进程图像也无法正常进行,CPU利用率会很低,因为进程无法工作。

d21e66b8f267f35b1178bc3e9449b856.png

0f8c766e9c92f34364d6419619083fbb.png

31a947e956200d75a1c46e9fdefc5029.png
  • 普通PC机,直接忽略死锁,因为会经常开关机,死锁就消失了,且死锁出现的概率也不高。但是对长期不关机的服务器等设备,必须要处理死锁。

5fe2815da805beb2c9a3e8dec466c909.png

ae576646b508aba7042e9d9f2dd1e815.png
  • 使用银行家算法来判断是否会出现死锁。

a97afb7026ce2a99f3f1d688b7e002f1.png

6b0ba6a17fa0c4b53edc71068a897276.png

b9eccbf2d0b61e501283bae4406b28d5.png
  • 发现问题再处理。
  • 选择某个进程回滚,然后使用银行家算法看是否可以。
  • 但是回滚操作比较困难。

d0e214c7ef446f6c14f8f83576527780.png

Lecture20:内存使用与分段

计算机工作就是取址执行。

内存使用:将程序放到内存中,PC指向开始地址。

61b3c2b2ff42b578b845d76929f423ca.png
  • 这里的40是相对地址(逻辑地址)。应该是物理内存地址1040,IP+偏移量。
  • 找一段空闲内存,将程序放到这块,然后修改程序中的地址。这就是重定位的过程。

7e31446dba8158644adcd28a82f288c4.png
  • 阻塞的进程占着内存,很浪费,需要交换把它放到磁盘中,避免内存占用。
  • 因此,运行时候重定位更好。

e063815018351da724c991570eba9179.png

ed7af9387cdaa401f08e433103b0f920.png
  • 执行的时候根据逻辑地址来算出物理地址:地址翻译。
  • PCB中保存了基地址的信息,进程切换的时候,也需要切换。

整理一下:

  • 在内存中找到一个空闲的地方
  • 然后把这块内存的基址放到这个进程的PCB中
  • 每一条执行一条指令的时候,进行地址翻译,找到实际的物理地址
  • 进行进程切换之后,使用执行程序的PCB
  • 切换回来的时候,放在了内存的另一块,要更新PCB中的基地址
  • 这样继续进行的时候,进行地址翻译,可以找到实际的物理地址

引入分段:不是将整个程序一起载入内存中的,要分段。

0c959ff8be280f6421a48f85e852a1d5.png

def754dcf1765f9cb074d9203fff925c.png
  • 每一段的基址不一样,都需要保存,找指令物理地址的时候,从对应的基址+偏移得到。
  • 即类似于GDT+LDT表,操作系统OS对应的进程段表就是GDT表,每个进程的表即为LDT表。

Lecture21:内存分区与分页

962c3d60aec3c2ebcda27530322562dc.png

3cf368954e512b0feece8f1787b706f3.png

d05b083e3efb312103132b56cfc1515a.png

f609b81b89fd31de0840a9318fb7dfb1.png

be44837cdc056066f994909b51aac7d1.png
  • 最佳适配:每次都选择长度最接近的,最后会得到很多很细的空闲区,O(n)
  • 最差适配:每次选择长度最大的,最后得到的剩余分区比较均匀,O(n)
  • 最先适配:选择最先找到的满足要求的分区,O(1)

上面的分割方式是对虚拟内存的管理(后面讲),物理内存使用分页来管理。

引入分页:解决内存分区导致的内存效率问题。

3026d9b4a6b08ed879e1df7153ad7320.png
  • 会造成许多内存碎片。因为段必须连续存放,需要使用内存紧缩将空闲分区合并,花费大量时间。

14d5044f3228c007bba106cf52984306.png
  • 物理内存分成一页一页的(mem_map,每4k分成一页)
  • 对段所需要的内存进行计算,向上取整,一页一页分配给它,这样最多浪费的内存也不到一页,比较少。
  • 操作系统既支持分段又支持分页。

f080594eb076eb7318cda8adbcdaf366.png
  • 需要页表进行重定位,找到对应指令的物理地址,完成地址翻译。
  • 使用MMU内存管理单元硬件来完成。

Lecture22:多级页表和快表

为了提高内存空间利用率,页应该小,但是页小了,页表就大了......

df73c5e861502ad0466214d042802150.png
  • 实际上用不到这么大的页表,希望页表可以缩小。

9f37fae157ebd62a937d76132b6dc13e.png
  • 因为页表不连续,查表的时候不能直接找到位置,需要查找,速度比较慢。即使折半查找,也需要额外的运算量。
  • 因此必须连续,查找快,同时还需要避免页表较大,引入多级页表。

3bdb7742a211ea72347c3a56ea83f29f.png
  • 页目录以及页表中都是连续的,不需要进行查找。

506a8aeab7137d1089c04d909b212266.png
  • 多级页表增加了访存次数,每多加一级页表,就多增加一次访存。
  • TLB快表,是寄存器,可以很快的找到最近使用的物理页号(使用硬件一步找到)。
  • TLB未命中,再去多级页表查找,把找到的结果再放到TLB中。

6211304e0a49ff0f870077b496582738.png

4fc1524afd8fe53b2d1a30a54a5e5b45.png
  • 程序地址访问具有局部性,一段时间访问的总在一定范围内,因此可以放在快表中,这样效率很高,快表也不需要很大。

Lecture23:段页结合的实际内存管理

af1b8fdbf868d43f7ce944fa5d791113.png
  • 段先映射到虚拟内存中某个区域(虚拟内存不能直接使用),虚拟内存(分割成页)再映射到物理内存。这样就段和页结合了。

b19c19264d6ccdda3e03380af9da62e1.png

5d6ea6470448da4681c2b8707df1842d.png
  • set_base设计基址,做段表。把虚拟内存的基址放到new_data_base。
  • 完成了对虚拟内存的分割,然后完成对段表的填写。

aca9ff3396219f03d81350b54e01979f.png
  • 共享一个页表的话,则各进程互不重叠。
  • 现在大多是每个进程有自己的页表。

a615a1007e27951f1c6b5e131f185b72.png

0394ccf79ea5e7e34176d69625e70844.png
  • 右移22位,再乘4,则为右移20位,找到了页目录指针。

3b4554f302cf59ce5092b511caf216be.png
  • to_dir是子进程的页目录,分配给它一个物理内存页,建立新的页,映射过去。

c03e5111f8a575653d706c453e4e92c7.png
  • 接下来拷贝到子进程中。

75fad5b381c30d2391059507bf1340a9.png

5f057b41f5958bdd1da4d59b06085068.png
  • 程序使用内存。
  • 子进程执行时,找到的虚拟地址是不同的,映射到的物理内存地址与父进程相同,若要写入,失败,因为是只读的,所以开一个新的页来写入。
  • 父子进程通过这一段映射表实现了地址的分离。

Lecture24:内存换入-请求调页

段页同时存在的时候,要使用虚拟内存。将虚拟内存映射到物理内存,才完成了指令。

4b0bcc3fa8b1bea0dcf2fea46491aeda.png

如果虚拟内存大于实际的物理内存,比如4G:1G,那么就需要换入换出,来实现这个4G的大的内存。可以先访问0-1G,映射到物理内存,再访问1-2G,映射这一部分......这样就需要换入换出。

92b4ee158e702ba76095274b4769740b.png
  • 对任意的虚拟地址,映射到物理页的时候,如果目前没有映射(即现在没在内存中,MMU查表得到),那么就请求换入(发生中断),把这个页调入。
  • 整个换入过程,对用户透明(用户不知道)。

f59925a74c6c8b3b2e355c076677d795.png
  • 14号中断是请求调页的中断。是在初始化的时候idt表中存放的。

2904a26ce06d3dc5252bfeae358cb9b0.png
  • 页错误虚拟(线性)地址放在寄存器cr2中,压栈,作为后续调用c函数的参数。
  • do_no_page从磁盘中读进来这段虚拟地址,放入内存中。

92233b54564f5418db3adca336f3c2cf.png
  • get_free_page得到物理空闲页
  • bread_page从磁盘中读入这页
  • put_page把这个物理页与虚拟地址建立映射,填页表

a20d966495204bc4570ecb8015acb283.png

Lecture25:内存换出

get_free_page不是总可以获得一个新的物理页,内存是优先的,需要选择一页淘汰,即换出。

8067d8c6b33961967e34d7172deb15cb.png

ef782e856b3cf4c04f43fae1c8066544.png

ff0822f3726fae2239afdbc602ba5583.png
  • MIN算法虽然最优,但是需要知道将来发生的事,实际情况中不现实。

ac5fb9d87ee94942c601fefba5339e9f.png

984e8e4a716762e6b5d7fd0a4842e20e.png
  • 选择时间戳最小的,换出,时间戳最小表示最长时间没有使用。
  • 这个算法使用代码编写比较简单,但是放到实际的操作系统中,很困难。

a362397b2effdc203c84fafa3b540523.png
  • 每次将栈底的页淘汰。

28d8eb01d6c6e34584aa8cc013e2ce62.png
  • 访问过这一页,就置为1,选择淘汰页的时候,开始扫描,如果为1,则置为0(相当于再给一次机会),转到某一位为0,则将这个淘汰。

0d553bc8cd7bc9385ba1b69ab04f3580.png
  • 两个指针,分针更快,定时清除R位,这样时针就很容易找到R=0的位置了。
  • 没有记录很多的历史信息,提高了效率。

ef1529d2840b53fda54940dc6e7afcbd.png

内存管理的整体框架:

68f4ae27c716214158d2b4998e4c2c45.png

Lecture26:I/O与显示器

1de3da92cae053c1be79bd77d6cbf7ea.png
  • CPU向外设的控制器发指令,然后CPU继续多进程
  • 外设准备就绪之后,向CPU发出中断
  • CPU进行中断处理。

操作系统要给用户提供一个简单的视图——文件视图。

1c90c4a3ddd5b5047ee3b15a18320067.png

50d60d40842127b784ceeed3b72e87ea.png

38f47d403ebfe4738385665b659c9d5e.png

24bb6c65f8f79e7e3aa7405f320681d3.png

b5f26f3fd853892e50262e75736b4c5e.png
  • 找到tty0,根据这个找到对应的路,是显示器/键盘......把设备信息读入。
  • write处理这个设备的信息。

320e13413a9cd876dc288a8ed08b652a.png

b9e3f0a8e3a84d363d36d1b718f3d2df.png

33fced212794812b8934927b591796f6.png

33f8d06d4b815dcd4e0caec16b9bfafe.png

61da0b089dad12ddab5ab97ecd1fc551.png

f442bcfa6d6d06e499a98617c09f9f7f.png

d1c637d9d4a3b3756396f5c5406b645f.png

Lecture27:键盘

终端设备包括显示器和键盘。

ac266d568323cc99f87b3bc5e4ba3a0e.png
  • inb,从60端口读入扫描码。
  • 根据不同的扫描码,调用key_table对应的操作。

36bb5d1d57a22d4ab0495caa8fbc9af2.png

42192515f97dfe1f1718e407d5d5aa3f.png

36ddc45a6ae7284f1f4ac1f0a3599908.png
  • 放到缓冲队列中,等待con.read_q来拿

7a132e9ffbeb136b5c41775d1b7c8b3d.png

d845534f1355bf89c24f946651424b11.png

Lecture28:生磁盘的使用

生磁盘:怎么让磁盘工作起来,用盘块号来使用磁盘,而不是用文件。

dd2aa62629d59d1180b65f1f6258bb2c.png
  • 磁盘的单位是扇区:512字节。

4062470e706be4652749179b28fe5c1f.png
  • 移动磁头到磁道,旋转磁头到对应扇区。
  • 读出数据,与内存缓存区进行读写交换,完成写入。

c2f35fc3a2785011535aa1a8606b67ee.png
  • 柱面C,磁头H,扇区S,找到对应的位置。给出这个位置,利用DMA就可以读写磁盘(out指令)。
  • 这样使用比较麻烦。

4e2982bc7bb24797f122541347aac872.png
  • 程序只发送盘块号,磁盘驱动计算出C、H、S,再来访问对应的位置。
  • 上面的过程即使从一维地址解码到三维地址。
  • 为了更加高效,我们希望block相邻的盘块可以快速读出(一般程序不会只访问一个,通常都是相邻的多个),主要的时间发生在寻道上。
  • 我们把相邻盘块号的盘块尽量放在相同的磁道上(放在旋转的旁边)。

25429ccc908c8751fe8f7e08081934f6.png
  • 盘块是扇区的整数倍。这样设置盘块,可以一次读取多个扇区。可以每次读写连续的几个扇区。使用空间换时间,把内存的单位变成了盘块,虽然可能会浪费了空间,但是会提升了时间效率。

0dcc7e64ec3ca8180640a227471d59c3.png

8c9a4e7c5230fe59add0a63ec6e56010.png
  • 多个进程都要使用磁盘,增加一个请求队列。每个进程使用的block放入队列中,需要使用调度算法进行调度(让寻道时间尽可能小),按顺序弹出,然后再进行上述的过程。

bded16850b2e8966cbc051c7ebeed0a3.png
  • 先来先服务的算法,最直观,但是效率不高。

0936c471ef6f73263587f31b3d988955.png
  • 最短寻道时间优先算法,但是一些远处的请求可能划不过去(因为中间磁道可能会不断来请求),出现饥饿问题。

c96f2023b1507ae8f7f6a4d78abb5094.png

c82f1bfeeaa7d45445ad60473c11942e.png

2fdd2e6e7aaeab70453d9f8e087eac09.png

Lecture29:从生磁盘到文件

64cae6e8ac8f3c21b925ef40b16228b6.png
  • 文件:建立字符流到盘块集合的映射关系。

c4700389a42a77b7905f0a0fd1fdc7fb.png

c26ada01960cfbb9019744cee7c0c392.png

df6e08dc0cb66f7c54bddf6e89af7f3d.png

522f449a6f682e84f0ed8f5a9018e137.png

Lecture30:文件使用磁盘的实现

01691ad0c52a63bb0e1cfbc72ff7f256.png
  • 在内部被换算成盘块号。根据字符流换算出数据盘块。

25fe72296036041532808d713e406575.png

972074ccf7ab42c137441fcaa18993f5.png

d221288e0ce33ed050cb7181d2584fbf.png

1e2a653d2406ee6d6dfa61f8e384eef7.png

4de8ccc94c679d7d7bef2db2d696c697.png
  • inode来决定是去磁盘,还是执行其他函数进行显示...

Lecture31:目录与文件系统

6d64c2743affd1f06c6dbec30d24539e.png
  • 将整个磁盘按照一定的方式进程组织,抽象整个磁盘。
  • 使得在用户眼里,是目录树的结构。这种文件系统插在不同的机器、系统中,都是一样的,兼容的。
  • 文件系统:将整个磁盘抽象成目录树结构。

9468a8b1ca43fb58e0a1ec5e6bb34b38.png

a7c1d17f6d99c04ccaa7544df272a5ca.png

cab04078115500cba58e042bc824b0ae.png
  • 使用路径名来定位a,从树根到某个节点/目录。
  • 根据路径找到文件的FCB(文件控制块)。
  • 得到FCB就可以找到它在磁盘中对应的位置。
  • 直观想法是目录下存放所有文件的FCB,放在这个目录下。这样的话,在找该目录时,要把目录下所有的文件字符串和FCB都读入,对每个进行字符串匹配,找到匹配到的目标的FCB。只找一个文件,需要读入所有的FCB,但是其实只需要匹配字符串就行了。

e2bf1b5e3e33a3ada4b301a685abb792.png
  • 我们不存放FCB,而是存放文件名字符串+对应FCB的“地址”(这里是在FCB数组中的下标)。
  • 这样匹配到对应的字符串,再通过下标来找到对应FCB,完成后续过程。

531f7794a6068ba81da69dfd0e330e3b.png
  • 根目录放在inode数组的第一项(FCB数组),前面存一些位图等。

2a1695e6b652d6ed49ceec6b43555dfa.png
  • 目录树找到文件的inode
  • 根据FCB(inode)找到对应的盘块
  • 加到电梯队列
  • 磁盘中断,算出对应盘块的磁盘物理位置
  • 进行读写。

Lecture32:目录解析代码实现

ddc6dc83a35d46fbfdd1b69788ae8bef.png
  • 核心就是找到inode

887508e36110e5d6337a4a1218d1242e.png

573adc97437d1231f9df6656dce77150.png

02624a20190b3ed814d25cf2826317d6.png
  • get_super,找到前面的超级块,知道了位图的尺寸。
  • block找到对应的i节点块位置。
  • bread去磁盘里读出这块。

fd230c110be46e024d5c6ec77d27fff1.png

2686f1e6df58efa72ebc55c969150eee.png
  • 找到对应的目录项,进行匹配,找到就读出了inode,然后继续iget,循环。

==操作系统全图:==

  • 核心是多进程视图。
  • CPU执行程序是取址执行,完成计算指令等。若进行 *p = 7这种指令,需要将7放入p对应内存的位置。那么就进行重定位,段页表结合,找到对应的内存位置。
  • CPU执行的如果是open、read、write等指令,那么就需要文件视图。
  • 文件视图执行,对目录进行解析,电梯队列排队,最后得到inode,如果是I/O外设,那么就得到了设备号,完成对应的输入输出操作;如果是磁盘,最后得到是磁盘上的C、H、S,完成磁盘的读写。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值