1、进程
所谓进程,可以认为是一个程序及其正在运行的过程。相对来说,程序是一个静态的概念,而进程是一个动态的概念,更加强调程序运行的过程和状态。一般一个进程至少要包含几个内容,即程序代码、程序处理的数据、CPU寄存器的值、堆和栈以及进程所占用的系统资源。
进程的概念所以和程序相区分,关键在于进程具有三个特性,即动态性、独立性和并发性。所谓动态性,指的是进程表示动态执行的程序,每时每刻进程的状态都是在变化的,例如CPU寄存器的值、堆栈中的数据等。所谓独立性,指的是进程和进程之间一般不会相互影响,每一个进程都有自己独立的逻辑的状态,如寄存器在某时刻的值。所谓并发性,指的是在宏观意义上将,多个进程在一个CPU上可以实现并发运行,如果存在多个CPU核则可以实现真正的并行运算。
再一次完整的执行过程中,一个进程可能处于三种状态之一:运行态、就绪态和阻塞态。所谓运行态指的是进程占据CPU,使用CPU进行程序运行的过程。就绪态指的是CPU由于某种原因不能执行当前的进程,该进程已准备就绪,随时等待CPU执行的状态。而阻塞态指的是进程由于在等待某种事件(如IO输入等)而处于暂停状态,此时即使给它CPU使用权限也无法运行该进程。这三种状态可以相互转化(除了就绪态不能直接转到阻塞态,阻塞态不能直接转到运行态)。这三种进程状态转换给了操作系统可以对进程进行调度基本的可能,否则操作系统无法控制各个进程的运行和暂停,进程的调度也就从无谈起。
每一个进程都会包含一个响应的控制模块,即“进程控制模块PCB”,这个模块有操作系统为各个进程分配和维护,主要包括一些进程管理的内容(CPU寄存器的值、程序状态字/计数器的值、进程的ID/状态/优先级等信息)、存储管理(基地址和长度地址寄存器的值)、文件管理(根目录、当前目录和进程打开的文件列表)等内容。当进程出现状态转换的时候,进程的信息就被存放在其PCB中,当该进程再次恢复运行之前就从这个PCB中重新获取状态。
操作系统中可能有多个进程并发运行,每个进程又有三种状态。为了解决不同进程、不同状态的管理,操作系统设立了一组队列来表示系统中所有进程的状态。处于运行态的进程构成运行队列,处于就绪态的进程构成就绪队列,处于阻塞态的进程构成阻塞队列。随着操作系统对进程的调度,某个进程依照状态的变化被移除某个队列并加入到另一个队列中。
2、线程
引入了进程的概念后,多个程序可以并发运行了,但是由于每个进程之间的地址空间是独立的,某个进程无法直接访问其他进程的数据,于是提出了比进程更小的调度单位,即线程。线程一般运行于进程内部,是进程的一条代码执行流程,它与进程最主要的区别就是对系统资源的占有。一个线程基本上不占有系统资源,其所使用的资源归属于线程所在的进程,而这个进程中的各个线程都可以共享这些资源。同一个进程中的线程可以共享的资源主要有:大部分进程管理信息(进程的ID/状态/优先级等)、存储信息(代码段、数据段、堆)和文件管理信息(进程打开的文件)。另外,每一个线程还包含部分独享信息,主要有逻辑寄存器和栈。这样就可以得出结论:进程拥有完整的一套资源平台,是系统资源的分配单位;而线程只独享少量必须的资源,是CPU的调度单位,进程的三种状态及转换同时也适用于线程。
操作系统实现线程的方式主要有两种,即用户线程和内核线程。所谓用户线程指的是在用户空间实现线程的操作,利用用户空间的线程管理库函数实现线程的管理,如创建、终止、同步和调度等操作。用户线程具有一个缺点,就是操作系统内核并不了解这种线程,而依然采用对进程的调度方式,这样一旦进程中的用户线程发生了阻塞,那么整个进程也将被阻塞,起不到并发的作用。线程实现的第二种方式是内核线程,即有操作系统内核实现线程的操作,这种线程就没有用户线程在阻塞时候的问题。
3、进程间通信
在操作系统中通常有多道进程并发运行,这些进程之间或相互独立,或相互关联。相互独立的进程之间不存在进程通信的问题,而相互关联的进程则几乎肯定要遇到进程间相互交换数据的过程,这个过程就称作“进程间通信”(Inter-Process Communication)。一般进程间通信需要考虑三个问题,即两个乃至更多个进程之间如何通信、如何传递信息,进程在访问共享资源的时候如何保证不发生冲突,如何调整进程之间的运行次序。
(1)进程间通信方式
进程间通信方式基本上分为低级通信和高级通信两类。前者可以在进程间传递少量的控制信息,通常为一个字节或整型变量,方式有信号或信号量等;后者可以在进程间传递大量的数据,如一个文件或缓冲区内容,可以通过共享内存、消息传递和管道等。
共享内存是操作系统提供的一种功能。操作系统采用虚拟存储管理技术,每个进程都有自己的独立内存地址空间,操作系统再讲这些地址空间映射到实际的物理内存中去。操作系统可以提供一些API,使得进程将一部分地址空间共享出来映射到同一块物理内存,实现进程间信息的交流和共享。
消息传递是进程间通过发送和接收消息来交换信息。首先需要在两个进程之间建立一个通信链路,然后调用发送和接受操作交换信息。
管道是链接在两个进程之间的打开的共享文件,发送进程从一端写入数据,读取进程从另一端读出数据。这种方式涉及到了文件操作,因此比共享内存慢一些。
(2)进程的互斥
当两个或多个进程对同一块数据进行读写时,若运行的事件和顺序不同,那么最后的结果可能不同,这种状态被称作竞争状态。解决这个问题的一个有效方法就是在同一时刻只允许一个进程访问共享数据。访问共享数据的程序称为临界区,被访问的共享数据称为共享资源。对临界资源进行互斥访问的条件有:①任意两个进程不能同时进入临界区;②CPU的个数和速度不能事前假定;③临界区外的进程不能阻止其他进程进入临界区;④任何进程进入临界区的要求应在有限时间内满足。
实现互斥访问的主要方法有:
①关闭中断法:一个进程进入临界区之后,首先关闭IO中断,退出临界区时再打开。对总体进程而言,这种方法效率较低。
②繁忙等待法:主要思想是当一个进程试图进入临界区时,首先检查是否允许进入临界区,是则直接进入,不是则循环等待。
③信号量:信号量表示系统空闲的资源数,由操作系统来维护。信号量是一种结构体类型,主要包含一个整型计数变量表示资源数或等待资源的进程数,以及一个进程等待队列表示该信号量阻塞的进程。用户进程可以使用信号量的初始化、P和V三种方法;初始化方法会用一个资源数目初始化一个信号量;P方法将申请一个空闲资源,若成功则返回,失败则阻塞本进程并加入进程队列;V方法释放一个占用资源,并唤醒一个正在阻塞的进程(若有)。
(3)进程的同步
进程的同步可以认为就是确定进程间执行的相互顺序。一种有效的方法是定义一个初始值为0的信号量,在进程A执行结束前进行V操作,在进程B的最开始执行P操作,这样可以确保B在A执行之前不会启动。
4、进程的调度
操作系统中由调度程序从就绪队列中选择进程或线程到CPU运行,在这个决策中采用的算法称作调度算法。一般进程的调度有两种方法:不可抢占调度和可抢占调度,前者一旦一个进程被选中,在它被阻塞或主动交出CPU之前不会被中止,后者可能随时被调度程序打断。
在批处理系统中,采用的调度算法主要有先来先服务法和短作业优先法。
在交互式系统中的调度算法主要有时间片轮转法、优先级算法、多级队列算法和多级反馈队列算法等。
5、死锁
(1)死锁的概念
死锁是由于进程之间对资源的竞争访问引起的相互等待、相互妨碍的现象。在死锁过程中,每一个进程都在占据着若干资源,同时又在等待另一个进程占用的其他资源,造成所有进程都无法继续运行的局面。如一座很窄的独木桥,两个人都要过桥却谁也不肯相让,谁都在等待对方首先退步,结果就是谁也无法过桥。
(2)死锁的产生
系统资源通常分为可抢占和不可抢占两种,引发死锁的通常是不可抢占资源。只有下面的四个条件同时成立时才会引发死锁:
①互斥条件:每一个资源最多只能由一个进程使用,如一座窄独木桥只能容许一人通过;
②请求和保持:进程占有了部分资源的同时又要去请求另外的资源,如一个人想要过桥,那么他在占据了桥的一头,同时又要求占据桥的另一头才能过桥;
③不可抢占:进程已经占用的资源只能由该进程主动放弃,不能被强制拿走,如一个人在走到桥的另一头之前不可能把身后的空间让给另一边的人;
④环路等待:存在若干个进程构成的环路链,每一个都在等待链中下一个进程的资源,如必须在桥的两端都有人想要过河,否则肯定很顺利就通过了。
(3)死锁的预防
防止产生死锁,主要靠破坏以上的四个条件,其中主要有破坏互斥条件、破坏请求保持条件、破坏环路等待条件等。