一、进程的概念
进程是操作系统中非常重要的一个概念,它可以笼统的认为是一个正在运行的程序,但这并不准确。进程包含以下内容:
(1) 程序的代码
(2) 程序的数据
(3) CPU寄存器的值,包括通用寄存器,程序计数器
(4) 堆(heap)是用来保存进程运行时动态分配的内存空间
(5) 栈(stack)有两个用途,1保存运行的上下文信息。2在函数调用时保存被调用函数的形参或者局部变量
(6) 进程所占用的一组系统资源,如打开的文件
从上面可以看出,进程是包含程序的。下面来看《现代操作系统》一书中的一个比喻来更好的理解进程和程序间的区别:想象一下一个计算机科学家正在为他女儿做蛋糕,他有做蛋糕的食谱,厨房里有所需的原料(面粉、鸡蛋、糖等)。在这个比喻中计算机科学家就是CPU(处理各项事宜),做蛋糕的食谱就是程序(使用适当程序描述的算法),蛋糕的各种原料就是数据。进程就是厨师阅读食谱使用原料做出蛋糕这一系列的总和。从这个例子可以看出,进程是一个动态的过程,而程序只是一个静态的代码块。
进程控制块PCB
进程在操作系统内用进程控制块(process control block,PCB)来表示,PCB石金成存在的证明。PCB包含了进程状态、程序计数器、cpu寄存器、cpu调度信息、内存管理信息、记账信息、I/O状态信息等信息。
- 进程状态(Process state): 状态可包括新的,就绪,运行,等待,终止等。
- 程序计数器(Program counter) : 计数器表示进程要执行的下个指令的地址。
- CPU寄存器(CPU registers):与程序计数器一起,这些寄存器的状态信息在出现中断时也需要保存,以便进程以后能正确的执行。
- CPU调度信息(CPU scheduling information):这类信息包括进程优先级、调度队列指针和其他调度参数。
- 内存管理信息(Memory-management information):根据内存系统,这类信息包括基址和界限寄存器的值,页表或段表。
- 记账信息(Accounting information):这类信息包括CPU时间、实际使用时间、时间界限、记账数据、作业或进程数量等。
- I/O状态信息(I/O status information):这类信息包含分配给进程的I/O设备列表、打开的文件列表等。
二、进程的状态
进程的状态模型图有三状态,五状态,七状态三种,下面主要来看一下五状态模型,如下所示:
- 新的(NEW):进程正在被创建
- 运行(RUNNING):指令正在被执行
- 等待(WAIT):进程等待某个事件的发生(如I/O完成或受到信号)
- 就绪 (READY):进程已经准备好,只需要分配CPU就可以执行(等待CPU)
- 终止(TERMINAL):进程完成执行
三状态模型是五状态模型的简化版不在详述,七状态模型划分的更为详细,如下图所示:
就绪挂起:是指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有就绪态进程,或者是挂起就绪态进程具有更高的优先级,系统将把挂起就绪态进程调回主存并转换为就绪。
阻塞挂起:是指进程对换到辅存时的阻塞状态,一旦等待的事件产生便进入阻塞挂起状态。
三、进程的调度
为什么要有进程调度呢?我们知道CPU的运行速度非常快,也是非常宝贵的资源,多道程序设计的目的就是无论何时都有进程执行,从而使CPU利用率达到最大。而如何使CPU无论何时都有进程执行, 该执行哪一个进程就是进程调度要处理的内容。简单地说,进程调度的目的就是为了更好的使用资源。
调度队列
1.作业队列:进程进入系统时被加入到作业队列中,该队列包含系统中所有进程。
2.就绪队列:驻留在内存中等待运行的程序保存在就绪队列中,该队列常用链表来实现,其头节点指向链表的第一个和最后一个PCB块的指针。每个PCB包括一个指向就绪队列的下一个PCB的指针域。
3.设备队列: 操作系统也有其他队列。等待特定IO设备的进程列表称为设备队列。每个设备都有自己的设备队列。
新进程开始处于就绪队列,它就在就绪队列中等待直到被选中执行或被派遣。当进程分配到CPU执行时,可能发生:
- 进程发出一个IO请求,并放到IO队列中。
- 进程创建新的子进程,并等待其结束
- 进程由于中断而强制释放cpu,并被放回到就绪队列。
进程会在各种调度队列之间迁移,为了调度,操作系统必须按某种方式从这些队列中选择进程。进程的选择是由相应的调度程序(scheduler)来执行的。调度程序可以分为以下几种:
1.短程调度(short-term scheduler),又称CPU调度。从已经就绪的进程中选取一个放在CPU上执行,即使进程从ready状态变到running状态。
2.中程调度 (medium-term scheduler),将进程从内存移到外存上,对应进程状态是从ready/waiting状态转为挂起状态。
3.长程调度(long-term scheduler),又称作业调度(job scheduler)。从输入井中选取一个进程加载到内存中去执行,对应的进程状态是从new状态变到ready状态。
上下文切换
中断使CPU从当前任务改变为运行内核子程序。当发生一个中断时,系统需要保存当前运行在CPU中进程的上下文,从而能在其处理完后恢复上下文。进程的上下文用PCB来表示。通常通过执行一个状态保存(state save)来保存cpu当前状态,之后执行一个状态恢复(state restore)重新开始运行。将CPU切换到另一进程需要保存当前状态并恢复另一进程状态,这叫做上下文切换(context switch)。当发生上下文切换时,内核会将旧进程的状态保存在PCB中,然后装入经调度要执行的并已保存的新进程的上下文。
四、进程操作
进程在大多数系统中可以同时执行,它们可能会被动态的创建或删除。因此系统需要提供创建和销毁进程的方法,下面就来看一下进程创建和终止的操作。
进程创建
进程在执行时,可以创建多个新进程。创建进程为父进程,而新进程叫做子进程。新进程都可再创建其他进程,从而形成了进程树。大多数操作系统根据一个唯一的进程标识符(process indentifier,pid)来识别进程,pid通常是一个整数值。
当进程创建新进程被创建时,有两种执行可能:
1.父进程与子进程并发执行
2.父进程等待,直到某个或全部子进程执行完
新进程的地址空间也有两种可能:
1.子进程是父进程的复制品(具有与父进程相同的程序和数据)。
2.子进程装入另外一个新程序
Unix ,linux系统可以通过fork函数创建新进程,对于fork函数,当其返回值大于零时,表示处于父进程的上下文环境,此时它的返回值代表子进程的pid值;当其返回值等于0时,表示处于子进程的上下文环境;小于0代表创建进程过程中出现错误。调用getpid()函数能够获得当前正在运行进程的pid,调用getppid()函数可以获得当前进程父进程的pid值。
看下面的代码:
在这个程序中else if和else中的语句都会执行。在这段代码中fork创建一个子进程后,子进程会复制父进程的代码和数据。在子进程中fork执行后返回的pid为0,执行else if中的内容(调用ls命令);而在父进程中执行else中的代码,else中调用了wait函数,wait函数会等待子进程的执行,只有当子进程执行完毕,才会继续向下执行,输出"Child Complete",然后退出。执行顺序如下图所示:
进程终止
进程执行完最后的语句并使用系统调用exit()请求系统删除自身时,进程终止。此时,进程可以返回状态值(通常为整数)到父进程(通过系统调用wait())。所有进程资源(物理和虚拟内存、打开文件和I/O缓冲)会被操作系统释放。
进程终止的可能原因:
1.子进程使用了超过它所分配的一些资源。(为判定是否发生这种情况,要求父进程有一个检查其子进程状态的机制)
2.分配给子进程的任务已经不需要
3.父进程退出,如果父进程终止,那么操作系统不允许子进程继续(有些操作系统,对于这类操作系统这种现象称为级联终止)。
孤儿进程与僵尸进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。僵尸进程的危害:如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
四、进程间通信(IPC)
按进程是否与其他进程共享数据,可分为独立的和协作的,即独立进程或协作进程。
协作进程需要一种进程间通信机制(IPC)来允许进程相互交换数据与信息。进程间通信有两种基本模式
1、共享内存:通过建立一块供协作进程共享的内存区域并在此区域读写数据来交换信息。
对于以上两种模式,消息传递对于交换较少数据很有用,并且更易于实现,但需要更多内核介入的时间。 共享内存比消息传递快,允许以最快速度进行方便的通信。
共享内存
共享内存系统需要建立共享内存区域。通常一块共享内存区域驻留在生成共享内存段进程的地址空间。其他希望使用这个共享内存段进行通信的进程必须将此放到它们自己的地址空间上。数据的形式或位置取决于这些进程而不是操作系统,进程还负责保证他们不向同一区域同时写数据。
消息传递系统
消息传递提供一种机制以允许进程不必通过共享地址空间来实现通信和同步,如果进程P和Q需要通信,他们之间要有通信线路(communication link).这里不关心线路的物理实现只讨论逻辑实现。下面是一些逻辑线路和接收/发送操作的方法:
1.直接或间接通信
2.同步或异步通信
直接通信:每个进程必须明确的命名通信的接受者或发送者,定义如下:
send(P,message)发送信息到进程P
receive(Q,message)接收来自Q的消息
.同步或异步
消息传递可以是阻塞或非阻塞——也称为同步或异步。
- 阻塞 send: 发送进程阻塞,直到消息被接收进程或邮箱所接收。
- 非阻塞 send:发送进程发送消息并再继续操作。
- 阻塞 receive: 接收者阻塞,直到有消息可用。
- 非阻塞 receive:接收者收到一个有效消息或空消息。
缓冲
不管进程是直接的还是间接的通信进程所交换的消息都驻留在临时队列中,队列实现有三种方法
- 零容量:队列的最大长度为0.线路中不能有消息处于等待,对于这种情况,消息必须阻塞发送,直到接受者接收到消息。
- 有限容量:最多有n个消息驻留在其中,如线路满,阻塞发送者
- 无限容量