1.进程的概念
进程是执行中的程序。
-
程序的顺序执行
1.概念
程序由若干操作组成,一个操作对应一个功能,只有前一个功能执行完毕,后一个功能才能开始执行,这是程序内部的顺序执行;
若一个任务由若干程序组成,这些程序之间也存在前后顺序,这是程序外部的顺序性。
2.特点
顺序性;环境的封闭性:按顺序执行程序,程序独占系统全部资源;结果的可再现性。 -
程序的并发执行
1.概念
若干个程序或程序段同时在系统中运行,这些程序的执行时间是重叠的,即一个程序或程序段还未执行完毕,另一个程序或程序段已经开始运行。
2.影响
程序的并发执行时间顺序不同,可能造成结果的错误,原因是:
(1)与各程序的执行时间有关;
(2)由于多个程序都共享了同一个变量或者需要协调同步;
(3)对于变量的共享或者互相协作的过程没有进行有效的控制。
3.特点
并发性;共享性;制约性;交互性。 -
进程的引入
1.定义
进程可以看做三部分的组合:
(1)一段可执行的程序;
(2)程序运行所需要的相关数据(常量、变量、工作区、缓冲区等);
(3)程序运行的上下文环境。
进程实际上是一个有独立功能的程序在某个数据集合上的一次运行。
2.进程与程序的区别
(1)程序是静态的,进程是动态的,且有生存期的;
(2)进程的特点是并发,程序没有;
(3)进程在执行过程中会占用某些系统资源。操作系统分配资源以进程为单位;
(4)进程与程序之间存在多对多关系。一个程序可以运行多次,每次运行对应不同的进程。进程运行过程中加载其他程序时,将使得一个进程包括对多个程序的运行。
3.进程的状态
转换关系:
(1)新建——>就绪①,新建——>运行②:刚创建的进程具备运行条件,插入就绪队列。要是该进程对应紧急事件,可直接运行。
(2)运行——>消亡③,运行——>阻塞④,运行——>就绪⑤:理想状态,进程一直运行到消亡。进程请求等待资源时,进入阻塞。进程时间片用完,进入就绪状态。
(3)阻塞——>就绪⑥,阻塞——>运行⑦:阻塞状态的进程等到资源后,进入就绪状态。⑦和②类似。
(4)就绪——>运行⑧:就绪状态的进程在调度算法调度下获得CPU进入运行。
2.进程控制
进程控制操作可以通过原语程序来实现,原语程序不允许并发执行,运行时CPU不响应中断。
与进程控制有关的操作有进程创建、进程撤销、进程阻塞、进程唤醒4种。
-
进程创建
1.条件
(1)操作系统初启;
(2)用户请求某程序开始执行;
(3)执行中的程序发出使用系统资源的系统调用请求;
(4)某批处理作业的初始化。
2.创建步骤
(1)给进程分配一个唯一的进程标识号和一个进程控制块,并在系统进程表中增加一个表目;
(2)分配内存空间,包括进程映像中的所有元素;
(3)初始化进程控制块;
(4)设置正确的连接。讲进程插入就绪队列中;
(5)创建或扩充其他数据结构。 -
进程撤销
1.条件
(1)进程执行完毕正常退出;
(2)进程执行错误退出;
(3)进程执行中出现严重错误被退出;
(4)进程被其他进程杀死。
2.撤销步骤
(1)从系统的PCB表中找到即将被撤销的PCB,将其状态设置为撤销状态。
(2)释放该进程占用的全部资源;
(3)释放被终止进程的PCB;
(4)调用进程调度程序,重新选择一个进程占用CPU。 -
进程阻塞流程
(1)主动放弃CPU;
(2)保存进程当前的现场环境;
(3)置进程的状态为等待态,并插入到相对应的等待队列中;
(4)调用进程调度程序,重新选择一个进程占用CPU。 -
进程唤醒流程
(1)从阻塞队列中删除被唤醒进程;
(2)置该进程为就绪队列;
(3)加入就绪队列;
(4)转进程调度或返回。
3.进程间通信(IPC)
-
进程之间的关系
(1)互斥:多个进程因不能同时访问临界资源而产生的制约关系称为进程的互斥。
(2)同步:系统中多个进程中发生的事件存在某种时序关系,需要相互合作,共同完成一项任务。
(3)通信。 -
进程通信方式
(1)低级通信:软中断通信、信号量集;
(2)高级通信:管道、消息队列、共享内存和套接字。 -
临界区和临界资源
一次只允许一个进程访问的资源称为临界资源,访问临界资源的程序段称为临界区。对临界资源的访问要遵循以下4个原则。
(1)有空让进:若无进程处于临界区,应允许一个进程进入临界区。
(2)忙则等待:当已经有进程处于临界区时,其他进程必须等待。
(3)有限等待:保证要求进入临界区的进程在有限的时间内进入临界区。
(4)让权等待:进程不能进入自己的临界区时,应释放处理器。 -
用信号量机制实现互斥与同步
1.PV操作
P对应资源减1,V对应资源加1。
2.信号量机制
(1)数据结构struct semaphore { int value; pointer_to_pcb *queue; }
value与相应资源的使用情况相关。value>=0时,表示当前可用资源的实体个数,当value<0时,|value|表示等待使用该资源的个数,即在queue中的进程个数。
-
信号量机制的一般操作
P(s)
{
s.value--;
if (s.value < 0)
{
保留调用进程的PCB现场;
将该进程插入s.queue队列并置为等待标识;
转调度程序;
}
]
V(s)
{
s.value++;
if (s.value <= 0)
{
取出s.queue队列中的第一个进程;
将该进程插入就绪队列中并置为就绪态;
}
}
- 用信号量机制实现互斥
打印机的互斥操作,信号量mutex初值为1,指针域为NULL。
struct semaphore mutex = {1, NULL};
...
Pi
{
...
P(mutex); //申请mutex
使用打印机的临界区;
V(mutex); //释放mutex
...
}
- 用信号量机制实现同步
//P1:
p(bufferEmpty);
P(mutex);
存储数据...
V(mutex);
V(bufferFull);
//P2:
p(bufferFull);
P(mutex);
存储数据...
V(bufferEmpty);
V(mutex);
- 进程通信
(1)消息缓冲机制:发送进程把消息写入缓冲区和把缓冲区挂入消息队列时,应禁止其他进程对缓冲区消息队列的访问。同理,接收进程取消息时也一样。缓冲区没有消息存在时,接收进程不能接收任何消息。
(2)邮箱机制:邮箱头+邮箱体。发送进程发送消息时,邮箱中至少要有一个空格存放消息,接收进程接收消息时,邮箱中至少要有一个消息存在。
4.经典的IPC问题
4.1 生产者与消费者问题
- 有若干个进程—生产者向一个有界缓冲区buffer(共有n个存储单元)中存入数据,另有若干个进程—消费者从buffer中取出数据打印输出并清空单元。
- buffer以互斥的方式使用,且当buffer中至少有一个单元为空时,可允许一个生产者进程操作,当buffer中至少一个单元存储数据时,可允许一个消费者进程操作。
- 两进程既有同步关系,又有互斥关系。对互斥关系需要定义公用信号量管理buffer使用权,对同步关系需要定义各自的私有信号量。
int mutex = 1; //公用
int bufferEmpty = 1, bufferFull = 0; //私用
用PV操作描述某一生产者进程Pi和消费者Cj进程对buffer的使用:
//Pi
P(bufferEmpty);
P(mutex)
存储数据...
V(mutex)
V(bufferFull)
//Cj
P(bufferFull);
P(mutex)
取数据,打印,清空...
V(bufferEmpty)
V(mutex)
4.2 哲学家进餐问题
- 有5位哲学家坐在一张圆桌子边,桌上每两个哲学家之间摆一根筷子。饥饿的哲学家会拿起自己旁边的筷子就餐,但是是一根一根地拿。如果某一根筷子在别人手中,需要等待,只有同时拿到两支筷子才能就餐。
- 5根筷子就是5位哲学家的共享资源。相邻的两位哲学家均要进餐时,它们之间的筷子分给某位哲学家时,另一位必须等待,这是互斥关系。
- 某一个哲学家需要进餐时,仅他左右手的筷子有用,其它3根筷子无法使用,因此5个资源需要分别管理。每位哲学家看作是一个进程。
int chopsticks[5] = [1,1,1,1,1]
Pi()
{
do
{
P(chopsticks[i]); //取左边筷子
P(chopsticks[(i+1)%5]); //取右边筷子
eat...
V(chopsticks[i]);
V(chopsticks[(i+1)%5])
think...
}while(1);
}
上述设计存在死锁,即所有哲学家同时拿起左边的筷子后进入死锁状态。可以通过给哲学家编号,奇数哲学家先申请左手边的筷子,偶数哲学家先申请右边的筷子:
int chopsticks[5] = [1,1,1,1,1]
Pi()
{
if (i%2)
{
do
{
P(chopsticks[i]); //取左边筷子
P(chopsticks[(i+1)%5]); //取右边筷子
eat...
V(chopsticks[i]);
V(chopsticks[(i+1)%5])
think...
}while(1);
}
else
{
do
{
P(chopsticks[i+1]); //取右边筷子
P(chopsticks[(i)%5]); //取左边筷子
eat...
V(chopsticks[i+1]);
V(chopsticks[(i)%5])
think...
}while(1);
}
}
4.3 读者—写者问题
- 加入若干个并发进程共享一个数据区,其中一些进程只要求读数据—读者,另一些进程则要求写数据—写者。
- 允许多个读者同时执行读操作;不允许读写者同时操作;不允许多个写者同时操作。这样问题可分为读者优先和写者优先,其中读者优先应满足下列条件:
当有一个新读者产生:
①既无读者又无写者,新读者可以进行读操作;
②当有写者在等待时,同时其它读者也在读,则新读者也可以读;
③当有写者正在写时,新读者等待。
如果新写者到:
①无读者时,新写者可以写;
②有读者时,新写者必须等待;
③有其他写者时,新写者必须等待。
RW
{
int rc = 0
semaphore rc_mutex = 1;
semaphore write = 1;
void reader()
{
do
{
P(rc_mutex);
rc++;
if(rc==1) P(write);
V(rc_mutex);
读文件...
P(rc_mutex);
rc--;
if(rc==0) V(write);
V(rc_mutex)
}while(1)
}
void Writer()
{
do
{
P(write);
写文件...
V(write);
}while(1)
}
}
5.线程
- 线程的定义
线程是基于进程的一个运行单位,例如一个子函数。一个进程可以有多个线程,每个线程共享这些进程的地址空间(代码和数据)及大部分管理信息。 - 线程与进程的关系
(1)线程是进程的一部分,是进程内的一个执行单元,是调度的基本单位。
(2)在分配资源时,操作系统把资源分配给进程,即进程是分配资源的基本单位。进程的所有线程可以共享分配给它的资源,当然它们也有自己独立的少量信息。
(3)进程可以并发执行,一个进程中的线程也可以并发执行,并发过程中也需要协调同步。
(4)进程切换时涉及到有关资源的保存和地址空间的变化等;线程切换时因其共享进程资源,不涉及资源保存和地址空间变化。 - 线程的分配
(1)用户级线程:完全在用户空间实现的线程,内核是完全不知道的。
(2)核心级线程:它们的创建、调度、撤销等管理都是由操作系统内核以系统调用方式实现。
6.管程
- 管程概念
关于共享资源的数据及在其上操作的一组过程或共享数据结构及其规定的所有操作。 - 管程的组成
管程内部的共享变量、条件变量、内部并行执行的进程、及局部与管程内部的共享数据设置初始值的语句。 - 管程的形式
type monitor_name=MONITOR
共享变量说明
define 本管程内定义、本管程外可调用的过程(函数)名字表
use 本管程外定义、本管程内将调用的过程(函数)名字表
PROCEDURE 过程名(形参表)
过程局部变量说明
BEGIN
语句说明
END
- 管程的三个特性
模块化、抽象数据类型和信息掩蔽。
7.死锁
- 定义
系统中存在2个或2个以上进程,它们中每个进程都占用了某种资源,又都在等待其它进程所占用的资源,由此导致系统无限僵持下去。如果没有其它因素作用,这些进程将永远等待下去。 - 产生死锁的4个必要条件
(1)互斥
(2)不可剥夺
(3)部分分配
(4)环路 - 死锁的预防
(1)静态分配:一次性把进程需要的资源全部分配给它。
(2)资源有序分配:先对资源编号,靠前的资源未被分配时,靠后的资源不会被分配。 - 死锁的避免
银行家算法 - 死锁的检测和修复
可采用类似于银行家算法的策略来检测死锁。修复方法:
(1)撤销死锁的所有进程;
(2)逐个撤销死锁的进程,知道死锁解除为止;
(3)逐个剥夺死锁的进程所占用的资源,重新分配给其它进程。