操作系统:进程管理

进程和线程的区别

进程(process)与线程(thread)最大的区别是进程拥有自己的地址空间,某进程内的线程对于其他进程不可见,即进程A不能通过传地址的方式直接读写进程B的存储区域。与之相对的,同一进程的各线程间之间可以直接通过传递地址或全局变量的方式传递信息

进程是一个执行中的程序的实例,它是系统进行资源分配和调度的一个独立单位,可以拥有多个线程。

线程是进程的一个实体,是CPU调度和分配的基本单位,线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如:程序计数器、寄存器和栈),但是它可以与同属一个进程的其他线程共享进程所拥有的所有资源。

在实现了线程的操作系统中,进程是资源分配的基本单位,线程是调度的基本单位,是系统中并发执行的单元。

引入线程的主要优点:

  • 易于调度。
  • 提高并发性。
  • 开销小。
  • 有利于发挥多处理器的功能。

在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。相比进程切换,线程切换的开销要小很多。线程与进程相互结合能够提高系统的运行效率。

进程和线程的区别:

  1. 一个线程必定属于也只能属于一个进程;而一个进程可以拥有多个线程并且至少拥有一个线程。
  2. 属于一个进程的所有线程共享该线程的所有资源,不同的进程相互独立。
  3. 线程又被称为轻量级进程(LWP)。线程间切换代价小,进程间切换代价大。
  4. 进程是程序的一次执行,线程可以理解为程序中一段程序片段的执行。
  5. 每个进程都有独立的内存空间,而线程共享其所属进程的内存空间。

内核线程和用户线程

内核线程的建立和销毁都是由操作系统负责、通过系统调用完成,所有管理工作都由内核完成。

用户线程是指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统的核心。用户线程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。速度快,不需要用户态/内核态切换,操作系统不知道多线程的存在。

用户线程的优点:

  • 可以在不支持线程的操作系统中实现。
  • 创建和销毁线程、线程切换等线程管理的代价比内核线程少得多。
  • 允许每个进程定制自己的调度算法,组织管理比较灵活。
  • 线程能够利用的表空间和堆栈空间比内核线程多。

用户线程的缺点:

  • 同一进程中只能同时有一个线程在运行,因为操作系统内核不知道用户线程的存在,此时处理器时间片分配是以进程为基本单位的,如果一个线程阻塞会导致整个进程挂起。也就是说并发效率不高
  • 页面失效也会产生类似的问题。

内核线程的好处是,内核可以将不同线程更好地分配到不同的CPU,以实现真正的并行计算。

现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上

上下文切换

对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换(Context Switch)是一种将CPU资源从一个进程分配给另一个进程的机制。

从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程

系统调用

系统调用(System call)是程序向系统内核请求服务的方式。可以包括硬件相关的服务(例如,访问硬盘等),或者创建新进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。

线程同步机制

线程同步互斥的控制机制,其实是由最基本的4种方法实现的:

  1. 临界区
    通过对多线程的串行化来访问公共资源或一段代码,适合控制数据访问。
  2. 互斥量
    为协调共同对一个共享资源的单独访问而设计。只有拥有互斥对象的线程才有权限去访问系统的公共资源,因为互斥对象只有一个。
  3. 信号量
    为控制一个具有有限数量的用户资源而设计。它允许多个线程在同一时刻去访问同一资源,但是一般需要限制同时访问的最大线程数目。
  4. 事件
    用来通知线程有一些事件已发生,从而启动后继任务的开始。

死锁

形成死锁的条件:

  1. 互斥条件
    指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  2. 请求与保持条件
    指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  3. 非剥夺条件
    指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  4. 循环等待条件
    系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

进程通信 IPC

一个进程不能直接读写另一个进程的数据,两者之间的通信需要通过进程间通信(inter-process communication, IPC)进行。进程通信的方式通常遵从生产者消费者模型,需要实现数据交换同步两大功能。

进程通信(Inter-Process Communication)的方式包括以下几种:

  1. 管道(Pipe)及有名管道(FIFO, named pipe)
    在内核中开辟一块缓冲区(称为管道)用于通信,管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
    管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。
  2. 信号(Signal)
    信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身。
  3. 消息队列
    消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享内存
    使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  5. 信号量(semaphore)
    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
    主要作为进程间以及同一进程不同线程之间的同步手段。
  6. Socket
    更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
  7. 文件和记录锁
    为避免两个进程间同时要求访问同一共享资源而引起访问和操作的混乱,在进程对共享资源进行访问前必须对其进行锁定,该进程访问完后再释放。这是UNIX为共享资源提供的互斥性保障。

进程调度

进程调度,它决定把就绪队列的某进程获得CPU。

  • 非抢占式
    分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
  • 抢占式
    操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。

调度算法

  1. FIFO或First Come, First Served (FCFS)
    调度的顺序就是任务到达就绪队列的顺序。
    公平、简单(FIFO队列)、非抢占、不适合交互式。未考虑任务特性,平均等待时间可以缩短
  2. Shortest Job First (SJF)
    最短的作业(CPU区间长度最小)最先调度。
    可以证明,SJF可以保证最小的平均等待时间。
    Shortest Remaining Job First (SRJF)
    SJF的可抢占版本,比SJF更有优势。
    SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法。
  3. 优先权调度
    每个任务关联一个优先权,调度优先权最高的任务。
    注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象。
    FCFS是RR的特例,SJF是优先权调度的特例。这些调度算法都不适合于交互式系统。
  4. Round-Robin(RR)
    设置一个时间片,按时间片来轮转调度(“轮叫”算法)
    优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;
    如何确定时间片?
    时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
  5. 多级队列调度
    按照一定的规则建立多个进程队列
    不同的队列有固定的优先级(高优先级有抢占权)
    不同的队列可以给不同的时间片和采用不同的调度方法
    存在问题1:没法区分I/O bound和CPU bound;
    存在问题2:也存在一定程度的“饥饿”现象;
  6. 多级反馈队列
    在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。
    可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
    最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。