操作系统原理一:进程管理

进程管理

进程

进程是程序的一次执行

是一个程序及其数据在处理机上顺序执行时所发生的活动

是具有独立功能的程序在一个数据集合上的一次运行过程

是系统进行资源分配和调度的一个基本单位

PCB结构、程序和数据的集合

设备分配只针对现有进程,不会创建进程

进程的特征:

  • 动态性:进程的实质是程序的一次执行过程,因此,动态特征是进程最重要的特征
  • 并发性:没有为之建立进程的程序是不能并发执行的,仅当为之建立一个进程后才能参加
    并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可
    预知的速度向前推进
  • 结构特征:为了控制和管理进程,系统为每个进程设立一个进程控制块--PCB

进程与程序的区别:

  • 程序是进程的静态文本,进程是执行程序的动态过程
  • 进程与程序之间不是一一对应的,同一程序同时运行于若干不同的数据集合上,它将属于若干个不同的进程;
    一个进程可以执行多个程序
  • 程序可作为软件资源长期保存,进程只是一次执行过程,是暂时的
  • 进程是系统分配调度的独立单位,能与其他进程并发执行

进程状态及其演变

进程执行的间断性,决定了进程可能具有多种状态

基本状态

运行的进程可能具有就绪、执行、阻塞三种基本状态

进程已分配到除CPU以外的所有必要资源时,它便处于就绪状态一旦获得CPU,便立即执行,进入执行状态
正在执行的进程,由于发生某个事件而暂时无法执行时,便放弃处理机而进入阻塞状态
由于执行的进程变为阻塞状态后,调度程序立即把处理机分配给另一个就绪进程(因此,阻塞进程的事件消失后,进程不会立即恢复到执行状态,而转变为就绪状态,重新等待处理机)

创建和终止

为了管理的需要,还存在着两种比较常见的进程状态,即创建状态和终止状态

创建状态:

引起创建的事件:

  1. 用户登录
  2. 作业调度:为被调度的作业创建进程
  3. 提供服务:要求打印
  4. 应用请求

创建一个进程一般要通过两个步骤:

  • 首先,为一个新进程创建PCB,并填写必要的管理信息;
  • 其次,把该进程转入就绪状态并插入就绪队列之中。

当一个新进程被创建时,系统已为其分配了PCB,填写了进程标识等信息,但由于该进程所必需的资源或其它信息,如主存资源尚未分配等,一般而言,此时的进程已拥有了自己的PCB,但进程自身还未进入主存,即创建工作尚未完成,进程还不能被调度运行,其所处的状态就是创建状态。

引入创建状态,是为了保证进程的调度必须在创建工作完成后进行,以确保对进程控制块操作的完整性。同时,创建状态的引入,也增加了管理的灵活性,操作系统可以根据系统性能或主存容量的限制,推迟创建状态进程的提交。对于处于创建状态的进程,获得了其所必需的资源,以及对其PCB初始化工作完成后,进程状态便可由创建状态转入就绪状态。

终止状态:

引起终止的事件:

  1. 正常结束
  2. 异常结束
  3. 外界干预
    1. 系统管理员kill
    2. 父进程终止
    3. 父进程请求

进程的终止也要通过两个步骤:

  • 首先等待操作系统进行善后处理
  • 然后将其PCB清零,并将PCB 空间返还系统

一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态

进入终止态的进程以后不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。一旦其它进程完成了对终止状态进程的信息提取之后,操作系统将删除该进程。

阻塞和唤醒

阻塞是进程自身的一种主动行为

a. 调用block原语

b. 停止执行,修改PCB进入阻塞队列(一个或多个

唤醒由其他相关进程完成

a. wakeup原语

b. 修改PCB进入就绪队列

挂起

为了系统和用户观察分析的需要,还引入了挂起操作,与挂起对应的是激活操作

当进程被挂起,便会进入静止状态:正在执行,便会暂停执行,处于就绪状态则不接受调度

引入挂起状态的原因有:

  • 终端用户的请求:当终端用户在自己的程序运行期间发现有可疑问题时,希望暂时使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态称为挂起状态。
  • 父进程请求:有时父进程希望挂起自己的某个子进程,以便考查和修改该子进程,或者协调各子进程间的活动。
  • 负荷调节的需要:当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
  • 操作系统的需要:操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。

在引入挂起状态后:

  • 活动就绪→静止就绪:当进程处于未被挂起的就绪状态时,称此为活动就绪状态,表示为Readya
    当用挂起原语Suspend 将该进程挂起后,该进程便转变为静止就绪状态,表示为Readys,处于Readys状态的进程不再被调度执行
  • 活动阻塞→静止阻塞:当进程处于未被挂起的阻塞状态时,称它是处于活动阻塞状态,表示为Blockeda。当用Suspend原语将它挂起后,进程便转变为静止阻塞状态,表示为Blockeds。处于该状态的进程在其所期待的事件出现后,将从静止阻塞变为静止就绪
  • 静止就绪→活动就绪:处于Readys 状态的进程,若用激活原语Active 激活后,该进程将转变为Readya 状态
  • 静止阻塞→活动阻塞:处于Blockeds 状态的进程,若用激活原语Active 激活后,该进程将转变为Blockeda 状态

五个进程状态的转换

  • NULL→创建:一个新进程产生时,该进程处于创建状态
  • 创建→活动就绪:在当前系统的性能和内存的容量均允许的情况下,完成对进程创建的必要操作后,相应的系统进程将进程的状态转换为活动就绪状态
  • 创建→静止就绪:考虑到系统当前资源状况和性能要求,并不分配给新建进程所需资源,主要是主存资源,相应的系统进程将进程状态转为静止就绪状态,对换到外存,不再参与调度,此时进程创建工作尚未完成
  • 执行→终止:当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,进程即进终止状态

PCB

为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录型数据结构

PCB 的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程

  1. 作为独立运行基本单位的标志
  2. 能实现间断性的运行方式
  3. 提供进程管理所需要的信息
  4. 提供进程调度所需要的信息
  5. 实现与其他进程的同步与通信

PCB中的信息:

  1. 进程标识符:用于惟一地标识一个进程,一个进程通常有两种标识符:
    1. 内部标识符,在所有的操作系统中,都为每一个进程赋予了一个惟一的数字标识符,
      它通常是一个进程的序号。设置内部标识符主要是为了方便系统使用。
    2. 外部标识符,它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。为了描述进程的家族关系,还应设置父进程标识及子进程标识。此外,还可设置用户标识,以指示拥有该进程的用户。
  2. 处理机状态:主要是由处理机的各种寄存器中的内容组成的。处理机在运行时,许多信息都放在寄存器中。当处理机被中断时,所有这些信息都必须保存在PCB 中,以便在该进程重新执行时,能从断点继续执行。这些寄存器包括
    1. 通用寄存器,又称为用户可视寄存器,它们是用户程序可以访问的,用于暂存信息,在大多数处理机中,有 8~32 个通用寄存器,在RISC 结构的计算机中可超过100 个
    2. 指令计数器,其中存放了要访问的下一条指令的地址
    3. 程序状态字PSW,其中含有状态信息,如条件码、执行方式、中断屏蔽标志等
    4. 用户栈指针,指每个用户进程都有一个或若干个与之相关的系统栈,用于存放过程和系统调用参数及调用地址,栈指针指向该栈的栈顶。
  3. 进程调度信息:在 PCB中还存放一些与进程调度和进程对换有关的信息,包括
    1. 进程状态,指明进程的当前状态,作为进程调度和对换时的依据
    2. 进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级高的进程应优先获得处理机
    3. 进程调度所需的其它信息,它们与所采用的进程调度算法有关,比如,进程已等待CPU的时间总和、进程已执行的时间总和等
    4. 事件,指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
  4. 进程控制信息
    1. 程序和数据的地址,指进程的程序和数据所在的内存或外存地(首)址,以便再调度到该进程执行时,能从PCB中找到其程序和数据
    2. 进程同步和通信机制,指实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等,它们可能全部或部分地放在PCB 中
    3. 资源清单,即一张列出了除CPU 以外的、进程所需的全部资源及已经分配到该进程的资源的清单
    4. 链接指针,它给出了本进程(PCB)所在队列中的下一个进程的PCB的首地址。

PCB的组织方式

在一个系统中,通常可拥有数十个、数百个乃至数千个PCB。为了能对它们加以有效的管理,应该用适当的方式将这些PCB组织起来。目前常用的组织方式有以下两种。

  1. 线性方式:将系统种所有PCB都组织在一张线性表中,将该表首地址存在内存的一个专用区域
    实现简单,开销小,但是每次都需要扫描整张表,适合进程数目不多的系统
  2. 链接方式:把同一状态的PCB链接成一个队列,形成就绪队列、若干个阻塞队列和空白队列等
    对其中的就绪队列常按进程优先级的高低排列,优先级高排在队前,此外,也可根据阻塞原因的不同而把处于阻塞状态的进程的PCB排成等待I/O 操作完成的队列和等待分配内存的队列等
  3. 索引方式:系统根据所有进程的状态建立几张索引表,例如就绪索引表、阻塞索引表等,并把各索引表在内存的首地址记录在内存的一些专用单元中,在每个索引表的表目中,记录具有相应状态的某个PCB在PCB址


进程的控制

进程控制是进程管理最基本的功能,主要包括创建新进程,终止已完成的进程,将发生异常的进程置于阻塞状态,进程运行中的状态转换等

进程创建

参数:进程标识、优先级、进程起始地址、CPU初始状态、资源需求等等

创建进程的过程

  1. 创建一个空白PCB
  2. 为新进程分配所需资源
  3. 初始化PCB
    1. 标识信息,将系统分配的标识符和父进程标识符填入新PCB
    2. 处理机状态信息,使程序计数器指向程序入口地址,使栈指针指向栈顶
    3. 处理机控制信息,将进程设为就绪/静止状态,通常设为最低优先级
  4. 如果就绪队列能接纳,则插入

进程终止

进程终止的时机/时间:

  1. 正常结束
  2. 异常结束
    1. 越界错,访问的存储区越出该进程的区域
    2. 保护错,试图访问不允许访问的资源,或以不适当的方式访问(写只读)
    3. 非法指令,试图执行不存在的指令(可能是程序错误地转移到数据区,数据当成了指令)
    4. 特权指令出错,用户进程试图执行一条只允许OS执行的指令
    5. 运行超时,执行时间超过指定的最大值
    6. 等待超时,进程等待某件事超过指定的最大值
    7. 算数运算错,试图执行被禁止的运算(被0除)
    8. I/O故障
  3. 外界干预
    1. 操作员或OS干预(死锁)
    2. 父进程请求,子进程完成父进程指定的任务时
    3. 父进程终止,所有子进程都应该结束

终止过程

  1. 根据被终止进程的标识符,从PCB集合中检索出该PCB,读取进程状态
  2. 若处于执行状态:立即终止执行,置调度标志为true,指示该进程被终止后重新调度
  3. 若进程有子孙进程:将其所有子孙进程终止
  4. 全部资源还给父进程/OS
  5. PCB从所在队列/链表中移出

进程阻塞

阻塞的时机/事件

  1. 请求共享资源失败,系统无足够资源分配
  2. 等待某种操作完成
  3. 新数据尚未到达(相互合作的进程)
  4. 等待新任务

阻塞过程:进程通过block

进程唤醒

原语wakeup,和阻塞成对使用

唤醒过程:先把被阻塞的进程从该事件阻塞队列移出,将其PSB状态改为就绪,再插入就绪队列

进程同步

制约关系

  • 资源共享关系(间接制约
    • 需要互斥的访问临界资源
  • 相互合作关系(直接制约

临界资源:一次只允许一个进程访问的资源

  • 引起不可再现性是因为临界资源没有互斥的访问

临界区

while(1){
    entry;      //进入区
    critical;   //临界区
    exit;       //退出区
}

各进程应互斥进入相关临界区,所谓临界区是指一段代码,一段程序

同步机制应该遵循:

  • 空闲让进
  • 忙则等待
  • 有限等待
  • 让权等待:不能进入临界区的执行进程放弃cpu执行权

整形信号量

信号量机制是一种进程间的低级通信方式

s是一个整形量,除了初始化外,仅通过两个原子操作wait(s)和signal(s)访问(也叫P,V操作)

wait(s){
    while(s<=0);
    s--;
}
signal(s){
    s++;
}

互斥关系:A,B共享一个缓冲区,互斥

PA(){
    while(1){
        wait(s);
        // 临界区
        signal(s);
        // 剩余区
    }
}

PB(){
    while(1){
        wait(s);
        // 临界区
        signal(s);
        // 剩余区
    }
}

main(){
    s=1;    //init
    // begin
    PA();
    PB();
    // end
}

前驱关系:P1,P2同步,P2→P1

PA(){
    P(s);
    a1;
}

PB(){
    a2;
    V(s);
}

总结:

  • 互斥的模式:PV总是成对出现在同一进程中;
  • 同步的模式:PV总是成对出现在不同进程中;
  • 前驱关系:有多少前驱关系设置多少个信号量,初值为0;有多少前驱做多少P操作,有多少后继结点做多少V操作,无前驱不做P操作。

信号量表示的是临界资源数
初值为2,表示初始时有2个可用的资源,若现在为-1,说明这两个可用资源已经被占用了,而且有一个进程在等待资源
初值为3,表示初始时有3个可用的资源,若现在为1,表示有两个资源被进程访问了,可用资源变为1,没有进程会等待

为了使两个进程同步运行,至少2个信号量

例题

1.桌上有一空盘,允许存放一只水果,爸爸可向盘内放苹果或桔子,儿子专等吃桔子,女儿专等吃苹果

semaphore s,so,sa=1,0,0;
father(){
    while(1) {
        wait(s);
        // 将水果放入盘中;
        if(放入的是桔子)
        then signal(so);
        else signal(sa);
    } 
}
son(){
    while(1){
        wait(so);
        // 从盘中取出桔子
        signal(s);
        // 吃桔子
    }
}
daughter() {
    while(1){
        wait(sa);
        // 从盘中取出苹果
        signal(s);
        // 吃苹果
    }
}
main()
{
    // cobegin
    father();
    son();
    daughter();
    // coend
}

2.某寺庙,有小、老和尚若干,由小和尚提水入缸供老和尚饮用,水缸可容10桶水,水取自同一个井中,水井窄,每次只能容一个桶取水,水桶总数3个,每次入缸取水桶仅为1桶,且不可同时进行,试给出有关取水,入水的算法

mutexj = 1;
mutexg = 1;
empty = 10;
full = 0;
count = 3;

old(){
    while(1) {
        wait(full);     //缸中有无水
        wait(count);    //有无桶
        wait(mutexg);   //取水前
        Fetch from gang;
        signal(mutexg);
        signal(count);
        signal(empty);  //通知小
    }
}

little(){
    while(1) {
        wait(empty);    //缸中有无空
        wait(count);    //有无桶
        wait(mutexj);   //取水前
        Fetch from well;
        signal(mutexj);
        wait(mutexg);   //倒水前
        pour;
        signal(mutexg);
        signal(count);
        signal(full);   //通知老
    }
}

记录型信号量

整形信号量中S<=0就会不断地测试,未遵循让权等待,而是处于忙等

解决方案:建立一个进程链表list,连结所有等待该类资源的进程

typedef struct{
    int value;
    struct process_control_block *list
}semaphore;

wait(semaphore *S){
    S->value--;
    if(S->value < 0)
        block(S->list);
}

signal(semaphore *S){
    S->value--;
    if(S->value <= 0)
        wakeup(S->list);
}
  1. S->value>0时,表示系统中可用资源的数目
  2. 当S->value<0时,S->value的绝对值表示阻塞进程的数目
  3. 如果S->value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量

AND信号量

要么全分配,要么一个也不分配

不用时有可能发生死锁

Swait(s1,s2,…,sn){ 
    while(1){ 
        if (s1>=1& …&sn >=1){ 
            for(i=1;i<=n;i++) si--; 
            break;
        }
        else{
            //将进程放入与找到的第一个si<1的si相关的阻塞队列中
            //并将该进程的程序计数设置为swait操作的开始
        }
    } 
}

Ssignal(s1,s2,…,sn){ 
    while(1){
        for (i=1;i<=n;i++){ 
            si++;
            //将与si关联的队列中等待的所有进程都移动到就绪队列中
        } 
    } 
}

信号量集

为提高效率而对AND信号量的扩充

允许一次申请多种资源多个

ti为分配下限值,Si>=ti则不分配,di为该进程需求值

Swait(S1, t1, d1, …, Sn, tn, dn){
    while(1){
        if(Si>=ti& … &Sn>=tn)
            for (i=1;i<=n;i++) 
                Si=Si-di;
        else{
            //将进程放在Si<ti的第一个Si的阻塞队列中
            //并将该进程的程序计数设置为swait操作的开始   
        }
    } 
} 

Ssignal(S1, d1, …, Sn, dn){
    while(1){ 
        for (i=1;i<=n;i++) {
            Si =Si+di;
            //将与si关联的队列中等待的所有进程都移动到就绪队列中
        }
    }
}

Swait(S,d,d):允许每次申请d个资源,少于d不分配

Swait(S,1,1):S>1记录型信号量,S=1互斥形信号量

Swait(S,1,0):可控开关,S>=1时允许同时进入,S<1时不允许

经典进程同步问题

生产者-消费者*

定义数据结构

int n;
typedef item = xxx; //产品类型
item buffer [n];    //缓冲池
int in = 0,out = 0; 
int counter = 0;    //产品数

缓冲池满时,生产者等待;为空时,消费者等待

记录型信号量

semaphore mutex=1,empty=n,full=0
item buffer[n];     //缓冲池
int in = 0,out = 0; 

int main()
{
    cobegin:
    producer();
    consumer();
    coend
}

producer(){ 
    while(1){
        …
        produce an item in nextp; 
        …
        wait(empty);
        wait(mutex);
        buffer[in]=nextp;
        in=(in+1)%n;
        signal(mutex);
        signal(full);
    } 
}

consumer(){
    while(1) {
        wait(full);
        wait(mutex);
        nextc=buffer[out];
        out=(out+1)%n;
        signal(mutex);
        signal(empty);
        consumer the item in nextc;
    } 
}

P操作的顺序至关重要,顺序不当可能导致死锁(有缓冲池使用权,无缓冲)
V操作的顺序无关紧要
当缓冲区只有一个时,mutex可省略

AND信号量

semaphore mutex=1,empty=n,full=0;
item buffer[n];
int in=0,out=0;
main(){
    cobegin
    producer();
    consumer();
    coend
}

producer()
{
    while(1)
    {
        ...
        produce......;
        ...
        Swait(empty,mutex);
        buffer[int]=nextp;
        in = (in+1)mod n;
        Ssignal(mutex,full);
    }
}

consumer(){
    while(1){
        Swait(full,mutex);
        nextc=buffer[out];
        out=(out+1)%n;
        Ssignal(mutex,empty);
        consumer......;
    }
}

哲学家进餐

5个哲学家围坐,用5只筷子吃面,筷子交替摆放

记录型信号量

设5个信号量表示5只筷子

AND信号量

同上

读-写*

读进程可共享对象,写进程不可

设整形变量readcount读者数wmutex读写互斥,rmutex互斥访问

记录型信号量:

semaphore rmutex=1,wmutex=1;
int readcount=0;
int main(){
    cobegin:
    reader();
    writer();
    coend;
}

reader(){
    while(1){
        wait(rmutex);
        if(readcount==0) wait(wmutex);//无人读才能写
        readcount++;
        signal(rmutex);
        ...
        read......
        ...
        wait(rmutex);
        readcount--;
        if(readcount==0) signal(wmutex);
        signal(rmutex);
    }
}

writer(){
    while(1){
        wait(wmutex);
        ...
        write......
        ...
        signal(wmutex);
    }
}

写优先

semaphore rmutex=1,wmutex=1,s=1;
int readcount=0;
int main(){
    cobegin:
    reader();
    writer();
    coend;
}

reader(){
    while(1){
        wait(s);//!
        wait(rmutex);
        if(readcount==0) wait(wmutex);//无人读
        readcount++;
        signal(rmutex);
        signal(s);//!
        ...
        read......
        ...
        wait(rmutex);
        readcount--;
        if(readcount==0) signal(wmutex);
        signal(rmutex);
    }
}

writer(){
    while(1){
        wait(s);//!
        wait(wmutex);
        ...
        write......
        ...
        signal(wmutex);
        signal(s);//!
    }
}

信号量集

#define RN 20//最大读者数
semaphore L=RN,mx=1;

int main(){
    cobegin:
    reader();
    writer();
    coend;
}

reader(){
    while(1){
        swait(L,1,1);
        swait(mx,1,0);
        ...
        read......
        ...
        wait(rmutex);
        ssignal(L,1);
    }
}

writer(){
    while(1){
        swait(mx,1,1);
        swait(L,RN,0);
        ...
        write......
        ...
        ssignal(mx,1);
    }
}

管程

将同步操作的机制和临界资源结合到一起,避免了要使用临界资源的进程自备同步操作

管程:一个数据结构能为并发进程所执行的一组操作
包括:1. 局部对于管程的共享变量,2. 对该数据结构操作的一组过程,3. 对局部管程数据设初值

Monitor m_name{         //管程名
    variable declarations;  //共享变量说明
    cond declarations;      //条件变量说明
    public:                 //能被进程调用的过程
    void P1(…);             //对数据结构的操作过程
    {}                  
    void P2(…);             
    {}      
    ...
    void Pn(…);             
    {}
    ...
    {
        //管程主体
        //初始化代码
    }
}

生产者-消费者

建立管程PC,包括:

  • 二过程:put(item)过程;get(item)过程;
  • 一变量:count>=n时满,<=0时空
  • 初始值:in=out=count=0
Monitor PC{
    item buffer[N];
    int in out;
    condition notfull,notempty;
    int count;
    public:
    void put(item x){
        if(count>=N)cwait(notfull);
        buffer[in]=x;
        in=(in+1)%N;
        count++;
        csignal(notempty);
    }
    void get(item x){
        if(count<=0)cwait(notempty);
        x=buffer[out];
        out=(out+1)%N;
        count--;
        csignal(notfull);
    }
}

void producer(){
    item x;
    while(1){
        //produce
        PC.put(x);
    }
}

void consumer(){
    item x;
    while(1){
        PC.get(x);
        //consume
    }
}

猜你喜欢

转载自www.cnblogs.com/zhxmdefj/p/12059174.html