layout: post
title: 操作系统(一)操作系统
description: 操作系统(一)操作系统
tag: 操作系统
启动:
操作系统(OS)起初放置在硬盘(DISK)上,需要BIOS(basic I/O system,基本I/O系统)操作,BIOS使用BootLoader(一个加载程序)把OS从硬盘加载到内存
系统调用:
应用程序向操作系统发出指令,调用系统的资源,控制权从用户态转变为系统态,完成指令的效果。
中断:
由各种外设或者中断定时器触发的中断效果,系统会保存程序运行的状态,以便后序回复。
异常:
控制指令触发了操作系统不希望、不应该执行的内容,引发工作异常。
异常和中断都要相应的类型编码ID
物理地址空间——硬件支持的地址空间
逻辑地址空间——一个运行的程序拥有的内存范围
两者可以通过CPU中的MMU(Memory Management Unit)进行映射。
应用程序中的变量和代码的逻辑地址中通过编译器分配和生成。这期间可能经过多种编译转换,如果C语言编译器、汇编语言编译器等等。
在分配单元间(外部碎片)和分配单元中(内部碎片),这些内存碎片空间不能被利用。内存分配时应该尽量规避内存碎片问题。
分配策略包含:
首次适配:
从0开始找到并使用第一个可用的空闲块。它实现简单,但容易产生外部碎片。
最优适配:
找到能满足分配要求且溢出空间最少的内存地址段分配。它减小了外部碎片产生的尺寸,但重分配慢,容易产生很多没用的微小碎片。
最差适配:
使用空间最大的内存段分配。效率快,但是外部碎片多,易于破碎大的块,以致于后边没用大块可用了。
碎片整理策略:
压缩式分配:
将已分配的内存段压缩到一块,使得外部碎片得以合并
交换式分配:
将硬盘空间看做内存的备用空间,执行交换处理。
分段管理,各个段间进行隔离,保证内存访问安全。
分段需要段号和段的偏移,分页同样需要页编号和页偏移量,所不同在于,页大小是固定的而段大小是不定的。
物理内存地址就是按照类似页的帧来分配的:
逻辑地址空间被划分为大小相同的页,分页管理机制下,逻辑地址到物理地址的映射过程:
页表可能非常大,且导致访问一个内存单元需要两次内存访问(一次用于获取页表项(因为页表也在内存中),一次用于访问数据)。
TLB利用缓存的思想,将经常使用的页表项存在TLB缓冲中,这就极大的缩减了页表查询的耗用时间。
页表是利用逻辑页的页编号找到物理帧的页帧号。反向页表则是利用物理页的页帧号来查找对应的逻辑页的编号。在反向页表中通过哈希算法搜索每个页对应的帧号。
存储器的种类层次:
把程序按照其自身的逻辑结构,划分为若干个功能上相对独立的程序模块,那些不会同时执行的模块共享同一块内存,分时运行
。
缺点。
将暂时不能运行的程序送到外存,从而获得空闲内存空间。
一条指令的一次执行和下次执行都集中在一个较短的时期内
)和空间局部性(当前指令和临近几条指令对于数据访问都集中在一个较小区域内
)。缺页中断请求
,系统在处理这个中断时,将外存中相应的页面调入内存。最优页面置换算法:
当一个缺页中断发生时,对于保存在内存中的每一个逻辑页面,计算它的下一次访问之前,还需等待多长时间,从中选择等待时间最长的那个,作为被置换的页面。最优置换是一种理想情况,实际上操作系统无从知道每个页面需要等待多久才可再次被访问,它只能作为一种参考。
先进先出算法(FIFO):
选择在内存中驻留时间最长的页面并淘汰之。这样做实现简单,但性能较差,调出页可能是经常要访问的页。
最近最久未使用算法(LRU):
缺页中断发生时,选择最久未使用的页面并淘汰之。这种算法的缺点是开销比较大。
时钟页面置换算法:
它是对先进先出和最近最久未使用算法的组合优化。
1、需要用到页表项当中的访问位,当一个页面被装入内存时,把该位初始化为0,然后如果这个页面被访问,则把该位置为1.
2、把各个页面组织成闭环链表(类似钟表面),把指针指向最老的页面(最先进来的)
3、当发生一个缺页中断时,考察所指向的最老的页面,若它的访问位是0,立即淘汰,若访问位是1,则把该位置为0,然后指针往下移动一格,如此下去,直到找到被淘汰的页面。
二次机会法或称enhanced clock 算法:
同时使用脏位(就是写位,如果有写操作,则为1,否则是0)和使用位来指导置换。所谓页面置换,就是要将硬盘存放读到内存中,如果只是读操作,则不需要后边再把内存写入到硬盘中,而如果有写操作,后续就还需要把更改后的内容写回到硬盘中。通过脏位
的指示可以减少写回的次数,从而提高效率。
在这种机制下,写位为1和使用位为1的页面会在时钟双向链表中被扫描两次,因此被形象的称为二次机会法
,二次机会法将给予访问频繁(used为1)和写操作频繁(脏位为1)的内存,更多的机会留在内存中。
最不常用算法:
当缺页中断时,选择访问次数最少的那个页面并淘汰之。
如果分配给一个进程的物理页面太少,不能包含整个工作集,即常驻工作集,那么进程就会造成很多的缺页中断,需要频繁在内存与外存之间替换页面,从而促使进程的运行速度变得很慢,我们把这种状态称为“抖动”。
抖动产生的原因:随着驻留内存的进程数目增加,分配给每个进程的物理页面数不断减小,缺页率不断上升。OS需要选择一个适当的进程数目和进行需要的帧数,以便于在并发水平和缺页率之间达到一个平衡。
进程:
一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
进程是动态的,程序是静态的,进程是暂时的,程序是永久的,进程是程序的执行,进程拥有核心态与用户态。
进程的特点:
进程控制块(PCB,process control block):操作系统控制进程运行所用的信息集合,操作系统用PCB来描述进程的基本情况以及运行变化的过程,PCB是进程存在的唯一标志
。
PCB包含一下三大类信息:
PCB一般采用链表来组织
进程只能自己阻塞自己,因为只有进程自身才能知道何时需要等待某种事件的发生
进程只能被别的进程或OS唤醒
进程拥有3个基本的状态:运行态、就绪态、阻塞态。可以由运行与就绪可以相互转换、运行可转为阻塞、阻塞可转为就绪。
进程在挂起状态意味着进程没有占用内存空间,而是映像在磁盘上
挂起的进程分为两类:
把一个进程从内存转为外存或者说挂起,可以分为以下几种情况:
解挂/激活一个进程,即把进程从外存转到内存可能有以下几种情况:
操作系统维护一组队列
用来表示系统中所有进程的当前状态,不同的状态分别用不同队列表示
(就绪队列、各类阻塞队列)
每个PCB都根据它的状态加入到相应的队列,每个进程状态改变时,它的PCB从一个状态队列脱离
加入另一个队列
线程:
线程是进程当中的一条执行流程。进程把一组相关的资源(包括地址空间中的代码段、数据段、打开的文件等)组合起来,构成了一个资源平台(环境),而线程是在进程这个资源平台上的一条线性的执行流程,简称线程。
线程简答讲 = 进程 - 共享资源
线程的优点是:
线程的缺点:一个线程崩溃会导致所属进程的所有线程崩溃。
线程的实现主要有三种方式:
进程切换:
进程加载和执行:
进程的等待和终止:
CPU调用原则需要考虑以下因素:
FCFS先来先服务
的算法思路实现简单,但假如先来的是执行耗时的事件,则后边事件的等待时间就会很长,因此又引入了SRT短进程优先
的策略。这个策略的平均等待时间会缩短很多。但可能会出现连续的短任务流的出现使得长任务饥饿,长任务的平均等待时间显著增加。为解决这个问题可以考虑HRRN最高响应比优先
算法,响应比的计算公式为R = (W(等待时间) + S(执行时间)/ S(执行时间)),R越大说明等待时间越长,可以优先执行。存在的问题是无法有效的获知程序的执行时间。
时间片轮询算法:
每个进程分配一个运行的时间片,如下边例子中的20,时间片内任务执行完毕后,则时间片提前结束。时间片大小对于性能有很大的影响,时间片过小则进程切换的开销会大,时间片过大则平均等待时间会长。
多级反馈队列(MFQ):
任务等待时间越长,则设置更高的优先级,任务执行时间越长则优先级越低。通过反馈动态调节。
公平共享调度:
有时候需要满足一台计算机多个用户使用,在用户级上应该公平共享的分配计算资源。
多个CPU并行调度:
在下图的例子中,T1-T3优先级依次下降,T3从t1到t2后执行了访问共享资源,但此时到t3时刻被优先级更高的T1进程打断,转而执行T1,T1执行到t4时刻,发现自己也需要刚刚T1访问的那个恭喜资源,于是切换为T3,让它释放那个占用的共享资源,但是在释放期间t5时刻,T1被优先级更高的T2再次打断,转而执行T2,等待T2执行完毕,才会到T3释放共享资源,释放完毕后,执行T1。这期间,优先级更低的T2抢在了优先级更高的T1之前完成了任务,称为优先级反转。造成优先级反转的主要原因是低进程影响了高优先级进程的运行,解决方案是T3在释放共享资源时应该继承T1的优先级,以避免被进程T2抢占。
另外一种解决方案称为优先级天花板
的思路:
personA与personB之间对于面包的处理没有同步!!!
临界区的设计原则:
i
处于入口区,那么在i的请求被接受之前,其他线程进入临界区的时间是有限制的// 测试target的结果,测试完毕将其赋值为true
bool TestAndSet(bool *target){
bool rv = *target;
*target = true;
return rv;
}
// 交换a和b的值
void Exchange(bool *a, bool *b){
bool temp = *a;
*a = *b;
*b = temp;
}
TestAndSet与Exchange被封装为整体的机器语句,成为原子指令。
采用这两个函数设计锁进入临近区和退出临近区:
在进入临界区时:如果锁被释放,那么testandset读取到0,并将值设置为1,锁被置为忙并且需要等待完成。如果锁处于忙状态,那么testandset读取到1,并将值设置为1,不改变锁的状态并且需要循环(自旋spin,即等待)。
当占用临界区的进程结束,调用release,将value设置为0,那么循环就可以退出了。
class Lock{
// value用于设置状态位
int value = 0;
}
Lock::Acauire(){
while(TestAndSet(value)){
}; // 盲等
}
Lock:: Release(){
value = 0;
}
信号量是一个整形数据,他有自增和自减两种操作。
信号量机制是一种进程同步机制,但每个要访问临界资源的进程都必须自备同步操作wait(S)和signal(S)。这样大量同步操作分散到各个进程中,可能会导致系统管理问题和死锁,在解决上述问题的过程中,便产生了新的进程同步工具——管程
。
管程是包含了一系列共享变量以及针对这些变量操作的函数的组合,它有一个锁,有0或多个条件变量。
管程相对于锁又增加了很多的条件变量,用于确定某些共享资源是否得到满足。进入管程后就可以操作各种条件变量,当某些条件无法得到满足时,会进入等待(wait),当再次满足时会唤醒(signal)。
管程是一种程序设计语言的结构成分,它和信号量有同等的表达能力,从语言的角度看,管程主要有以下特性:
读写的操作是共享的,而读与写是互斥的。
// writer
sem_wait(writemutex);
write;
sem_post(writemutex);
// reader
sem_wait(countmutex);
if(Rcount == 0){
sem_wait(writemutex); // 把写者挂起后再增加Rcount
++Rcount;
}
sem_post(countmutex);
read;
sem_wait(countmutex);
--Rcount;
if(Rcount == 0)
sem_post(writemutex); //如果没有读者了,唤醒写者
sem_post(countmutex)
思路1:采用互斥的思想,每个哲学家拿起叉子就进入临界区。
思路2:避免单个叉子在手的资源浪费现象,即要么不要叉子,要么同时拿起两把叉子。
死锁可能出现如果四个条件同时成立:
死锁预防的思路
:死锁避免的思路:
银行家算法(Banker’s Algorithm)是一个死锁避免的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础。判断并保证系统的安全运行。
如果基于银行家算法得到的结果是safe则不会出现死锁,如果返回结果是unsafe则有可能出现死锁
死锁的检测与银行家算法思路很像:
死锁检测算法的开销很大,而且它借鉴了银行家算法的思路,需要提前知道各个进程最大使用资源,但这些信息可能是无法获取到的。
死锁恢复的思路:
IPC按照通信的信道类型分为:
IPC按照通信对于进程的影响可以分为:
信号:
管道:
将一个进程的输出作为另一个进程的输入。例如下边这个例子,ls | more,ls列出,more显示更多,more是ls的子进程。
消息队列:
管道实现借助于父子关系,且传递的是字节流,消息队列则可以存储结构型数据,且无需进程间具备父子关系。
共享内存:
信号、管道、消息队列都是间接通信,共享内存是直接通信方式。
文件描述符
在形式上是一个非负整数
。实际上,它是一个索引值
,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
通文件描述符查到文件表中存储到的文件信息。
目录需要满足的操作:
文件名遍历:
文件别名:
文件名与文件的链接有硬链接(多个文件项指向一个文件)和软链接(以“快捷方式”指向其他文件)之分。
硬链接会使得引用计数减一(只有当引用计数减少为0时,文件真正删除),软链接则会使得连接变为空指针。
文件系统种类:
操作系统通过虚拟文件系统层,对于文件系统抽象封装,使得上层用户可以简化对于文件的操作。
磁盘分为不同的扇区,磁头移动访问不同扇区。
可以将奇偶检验磁盘均匀分布在各个盘内: