interview

并发

进程

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示

进程的概念主要有两点:

进程的基本状态

执行态 -> 阻塞态:往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的。

阻塞态 -> 就绪态:则是等待的条件已满足,只需分配到处理器后就能运行。

执行态 -> 就绪态:不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。

就绪态 -> 执行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态

进程调度

调度种类

高级、中级和低级调度作业从提交开始直到完成,往往要经历下述三级调度:

非抢占式调度与抢占式调度

调度策略的设计

CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。

调度算法

  1. FCFS:调度的顺序就是任务到达就绪队列的顺序。对短作业不公平。

公平、简单(FIFO队列)、非抢占、不适合交互式。未考虑任务特性,平均等待时间可以缩短

  1. SJF:最短的作业(CPU区间长度最小)最先调度。

可以证明,SJF可以保证最小的平均等待时间。

SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法。

  1. HRN:最高响应比优先法,是FCFS和SJF的综合平衡,响应比R定义如下: R =(W+T)/T

  2. 优先权调度:每个任务关联一个优先权,调度优先权最高的任务。

注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象。

FCFS是RR的特例,SJF是优先权调度的特例。这些调度算法都不适合于交互式系统。

  1. Round-Robin(RR):设置一个时间片,按时间片来轮转调度

优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;

时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。

  1. 多级队列调度

存在问题1:没法区分I/O bound和CPU bound;

存在问题2:也存在一定程度的“饥饿”现象;

  1. 多级反馈队列:在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。

最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。

进程同步

临界资源与临界区

在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。

对于临界资源的访问,必须是互斥进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。

对于临界区的访问过程分为四个部分:

  1. 进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞
  2. 临界区:在临界区做操作
  3. 退出区:清除临界区被占用的标志
  4. 剩余区:进程与临界区不相关部分的代码

解决临界区问题可能的方法:

  1. 一般软件方法
  2. 关中断方法
  3. 硬件原子指令方法
  4. 信号量方法

信号量

信号量是一个确定的二元组(s,q),其中s是一个具有非负初值的整形变量,q是一个初始状态为空的队列,整形变量s表示系统中某类资源的数目:

除信号量的初值外,信号量的值仅能由P操作和V操作更改,操作系统利用它的状态对进程和资源进行管理

P操作

P操作记为P(s),其中s为一信号量,它执行时主要完成以下动作:

s.value = s.value - 1;  /*可理解为占用1个资源,若原来就没有则记帐“欠”1个*/

s.value ≥ 0,则进程继续执行,否则(即s.value < 0),则进程被阻塞,并将该进程插入到信号量s的等待队列s.queue中

实际上,P操作可以理解为分配资源的计数器,或是使进程处于等待状态的控制指令

V操作

V操作记为V(s),其中s为一信号量,它执行时,主要完成以下动作:

s.value = s.value + 1;/*可理解为归还1个资源,若原来就没有则意义是用此资源还1个欠帐*/

s.value > 0,则进程继续执行,否则(即s.value ≤ 0),则从信号量s的等待队s.queue中移出第一个进程,使其变为就绪状态,然后返回原进程继续执行

实际上,V操作可以理解为归还资源的计数器,或是唤醒进程使其处于就绪状态的控制指令

在Java中synchronized,ReentrantLock,Object.wait() / notify()都属于阻塞锁。

CAS

比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值

在使用上,通常会记录下某块内存中的旧值,通过对旧值进行一系列的操作后得到新值,然后通过CAS操作将新值与旧值进行交换。如果这块内存的值在这期间内没被修改过,则旧值会与内存中的数据相同,这时CAS操作将会成功执行使内存中的数据变为新值。如果内存中的值在这期间内被修改过,则一般来说旧值会与内存中的数据不同,这时CAS操作将会失败,新值将不会被写入内存。

死锁

死锁是指多个进程因循环等待资源而造成无法执行的现象。死锁会造成进程无法执行,同时会造成系统资源的极大浪费(资源无法释放)。

死锁产生的四个必要条件
死锁避免

银行家算法:判断此次请求是否造成死锁若会造成死锁,则拒绝该请求。

进程间通信

本地进程间通信的方式有很多,可以总结为下面四类:

线程

线程是 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈,自己的寄存器环境,自己的线程本地存储。

线程的属性:

线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。

线程是程序执行的一条路径,在多线程的OS中,线程是调度和分配的基本单位,而进程是拥有资源的基本单位。

IO多路复用

基本概念

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

常见的IO复用实现