操作系统相关子问题
进程相关
僵尸进程
一个进程使用fork创建子进程,子进程结束后 父进程并没有调用wait或者waitpid方法获取子进程的终止状态,子进程的描述符依然保存在操作系统的进程表中,这样的进程就是一个僵尸进程。
僵尸进程是一个已经死亡的进程,但是没有被完全销毁。它几乎不占用任何内存资源,没有什么可执行的代码,只是在进程表中保留了一个位置,占用了一个进程id。
任何子进程在结束之后都会经历僵尸进程的阶段,状态为Z,直到被父进程处理。
产生原因
Unix的机制来保证父进程可以知道子进程结束时的状态信息:就是子进程结束后释放它占有的资源,包括打开的文件,内存等。但是还保留了一定信息,包括进程号,退出状态等,这些信息会直到父进程调用wait或者waitpid方法时才释放。其中wait会使父进程暂停执行,waitpid则可以加入wait-no-hang选项,如果没有结束的进程会立即返回,不回阻塞进程
危害
如果父进程没有调用上述方法,就会产生僵尸进程,保留的那些信息不会被释放,而进程号是有限的,有可能因此而不能产生新的进程
问题和解决方法
问题:可能会有一个进程定期产生一个子进程,做了一些工作后就结束,但是没有被父进程处理,这样一段时间后就会产生大量僵尸进程。
方法:解决产生僵尸进程的进程,这样那些僵尸进程就会变成孤儿进程,由init来处理
具体方法:
通过信号机制:子进程结束时发送SIGCHILD信号给父进程,父进程处理信号,在信号处理函数中调用wait方法处理僵尸进程。
孤儿进程
一个父进程已经退出,但是它的子进程还在运行当中,这些进程就成了孤儿进程。孤儿进程将被init进程(id号1)收养,并由init进程完成状态收集工作。孤儿进程并不会有什么危害。
几种常见的IO方式
阻塞式IO
当用户进程调用IO操作,应用程序被阻塞,直到数据从内核缓冲区复制到进程缓冲区才恢复。进行拷贝过程时继续阻塞。所以IO的两个阶段:等待和拷贝,都处于阻塞状态
但是阻塞期间其他进程还可以执行,所以CPU利用率挺高。
非阻塞式IO
进程执行系统调用后,如果数据还没准备好,内核会返回一个错误码,进程可以继续执行。但是进程会定期执行系统调用来询问内核有没有准备好,如果准备好了并且收到了系统调用,就进行拷贝。第二阶段拷贝依然是阻塞的。
不断主动询问占用了很多CPU资源
IO多路复用
异步IO
进程执行IO操作后可以立即返回,并继续执行,不会被阻塞,内核会在所有操作完成之后发送信号。比如说要进行读操作,内核会在把读取的数据放到用户指定的缓冲区内后通知进程。
用户态和内核态
是系统的两个权限等级,代表了不同的访问能力,先说内核态,内核态可以访问内存的所有数据以及外围设备,例如硬盘网卡,但是用户态只能受限访问内存,并且不能访问外围设备,不能占用CPU。
所有应用程序都运行在用户态,需要进行一些内核操作,例如读取硬盘数据时就要进行系统调用,CPU切换到内核态,执行完服务之后切换回用户态并返回系统调用的结果。
为什么要分
- 安全性:防止用户程序执行一些危险指令,恶意或无意破坏系统、内存、硬盘资源
- 封装性:把系统层面的操作封装起来,通过操作系统来调用,操作系统可以检验一些操作的安全性
如何从用户态切换到内核态
- 系统调用:
- 异常:如果用户进程出现了某些异常,需要切换到内核态来通过某些程序处理该异常
- 外设中断:当外设完成某些用户请求的操作后会发出一个中断请求,CPU会转而处理该请求。如果CPU正在处理用户态的程序,就会转而切换到内核态执行相关的处理方法。
死锁
死锁就是两个或者多个并发进程,都持有一定的资源,并且都等待其他进程释放他们持有的资源,进度无法推进,就发生了死锁
死锁产生的必要条件
- 互斥:一个资源同一时间只能被一个进程使用
- 占有等待:一个进程至少占有一个资源,并且在等待其他进程释放资源
- 非抢占:一个进程一旦占有某样资源就不能被抢夺,只能等待它主动释放该资源
- 循环等待:若干进程形成一个头尾相接的环形等待关系,每一个进程都在等待下一个进程释放资源
死锁的处理办法
- 鸵鸟策略:直接忽视死锁 - 指发生死锁不会产生很严重的后果,或者死锁发生的概率很低
- 死锁预防
- 破坏互斥:资源可以被多个进程访问,但是某些资源不具备这个属性,所以无法操作
- 破坏占有等待
- 一次性申请所有需要的资源,否则不运行
- 申请资源之前先释放自己占有的资源
- 但是很多时候不知道一个进程需要的所有资源,降低系统并发性
- 破坏不可抢占:允许进程抢占别的已占有资源
- 破坏循环等待:给资源同一编号,进程只能按照编号顺序来请求资源,只有占用小号资源才能申请大号资源
- 死锁避免
- 动态检测资源分配,确保系统处于安全状态,只有处于安全状态时才会进行资源分配。安全状态是指:即使所有进程突然请求所需要的资源,也有某种资源分配的顺序使得每一个进程执行完毕
- 银行家算法
- 死锁解除
- 利用抢占,挂起某些进程然后占有它的资源
- 回滚:让某些进程回滚到之前的状态,自愿释放资源到解除死锁的阶段
- 杀死进程:按照优先级杀死进程直到解除死锁
内存相关
分页和分段
页式存储:页是对虚拟地址空间的划分,页框是对物理内存空间的划分。内存管理单元MMU管理着地址空间和物理内存的转换。其中的页表存储着页(地址空间)和页框(物理内存空间)的映射表。虚拟地址分成两部分,一部分存储页面号,一部分存储偏移量。
按进程需要的页数分配,逻辑上相邻的页物理上不一定相邻
段式存储:将进程自己的地址空间按照逻辑划分成若干个段,如代码段、数据段等。内存空间页被动态划分成不同长度的区域,分配时以段为基本单位。
段页式:用户进程的地址空间先按段划分,段内再按页划分,内存空间按照页来划分
区别是什么
目的:分页是为了管理内存,通过虚拟内存获得更大的地址空间。分段是为了满足用户需要,程序和数据可以被划分为逻辑上独立的地址空间
大小:段的大小不固定,由其所完成的功能决定;页的大小固定,由系统决定
地址空间维度:页是一维的,给定一个虚拟地址,页号和页偏移可以被计算出来;段是二维的,需要通过段号加段内偏移来确定。
虚拟内存
每个程序都有自己的地址空间,地址空间被划分为大小相等的页,页映射到物理内存空间。但不需要所有页都在物理内存中,有一部分被存储在磁盘上。当程序需要用到某些不在物理内存空间中的页时,需要由操作系统把它从磁盘置换到内存中。这样,逻辑上有很大的内存空间,其实有一部分时存储在磁盘上的
地址空间到物理空间的映射
由MMU内存管理单元实现,其中的页表存储了页到页框的映射表,其中还包含有效位(是在内存还是磁盘中)、访问位(是否被访问过)、修改位(是否被修改过)等
页面置换算法
最佳页面置换算法:把未来很长一段时间内不会被用到的页面置换掉-理论算法
最近未使用LRU:将最近最久未使用的页面置换出去。实现方式是链表,每次一个页面被访问,把它移动到链表头,置换时候置换掉链表尾部的页面
先进先出FIFO:置换掉在内存中停留时间最长的页面-会把常用的页面置换出去
第二次机会算法:相当于优化FIFO,解决了常用页面被置换的问题。为每一个页面设置一个R位,被读和写时置为1,需要置换链表头部的页时,检查R位,如果是0就置换,如果是1,就将R置0,放入链表尾部。
时钟:省去第二次机会算法在链表中移动的开销,将页面首尾相连,指针指向最老的那个页面
局部性原理
时间上:最近被访问的页在不久的将来还会被访问
空间上:内存中被访问的页周围的页也很可能被访问
颠簸
指频繁的页调度,置换掉一个页,那个页有马上被需要,又置换回来,频繁发生缺页中断。
解决方法(根本原因就是内存不足)
- 降低运行的程序量(释放内存)
- 增加物理内存(增加内存)
- 修改页面置换算法
磁盘调度
磁盘
- 盘面(找到对应的盘面)
- 磁道(盘面上的同心圆 - 寻道时间)
- 扇区(旋转时间)
磁盘调度算法
寻道时间最长,磁盘调度是使磁盘的平均寻道时间最短
-
先来先服务FCFS
公平简单,但是不做任何优化,平均寻道时间较长
-
最短寻道时间优先
优先调度与所在磁道最近的磁道。平均时间较短,但是会出现饥饿
-
电梯算法
按照一个方向来进行磁盘调度,等到该方向没有未完成的请求,改变方向