操作系统 进程的描述与控制

本系列是计算机操作系统的笔记 采用的书是《计算机操作系统》汤子瀛

第二章 进程的描述与控制

2.1前驱图和程序执行

2.1.1前驱图

1. 所谓前驱图,是指一个有向无循环图,可记为DAG(Directed Acyclic Graph),它用于描述进程之间执行的先后顺序。

2.1.2程序顺序执行

  1. 程序的顺序执行
  2. 程序顺序执行时的特征
    • 顺序性
    • 封闭性
    • 可再现性

2.1.3 程序并发执行

  1. 程序的并发执行
  2. 程序并发执行时的特征
    • 间断性
    • 失去封闭性

- 不可再现性


2.2进程的描述

2.1 进程的定义和特征

  1. 进程的定义
    • 为了使参与并发执行的每个程序都能够独立的运行,在操作系统中必须为之配置一个专门的数据结构,称之为进程控制块(Process Control Block,PCB)。
    • 由程序段、相关的数据段和PCB三部分便构成了进程实体,把进程实体简称为进程。
    • 创建进程,实质上是创建进程实体中的PCB,撤销进程,实际上是撤销进程中的PCB
    • 不同的定义
      1. 进程是程序的一次执行
      2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
      3. 进程是一个具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
  2. 进程的特征

    1. 动态性
    2. 并发性
    3. 独立性
    4. 异步性

    2.2.2进程的基本状态及其转换

  3. 进程的三种基本状态
    1. 就绪状态
      • 指进程已处于准备好运行的状态,即进程已分配到除CPU以外的所有必要资源后,只要获得CPU,便可立即执行。如果系统中有多个处于就绪状态,则按照优先级排成一个队列。
    2. 执行状态
      • 指进程已获得CPU,其程序正在执行的状态。
    3. 阻塞状态
      • 阻塞状态的进程也排成一个队列。
  4. 三种基本状态的转换
  5. 创建状态和终止状态
    1. 创建状态

2. 终止状态

2.2.3 挂起操作和进程状态的转换

  1. 挂起操作的引入
    1. 终端用户的需要
    2. 父进程请求
    3. 负荷调节的需要
    4. 操作系统的需要
  2. 引入挂起原语操作后三个进程状态的转换
    1. 活动就绪-静止就绪
    2. 活动阻塞-静止阻塞
    3. 静止就绪-活动就绪
    4. 静止阻塞-活动阻塞
  3. 引入挂起操作后五个进程状态的转换
    1. NULL-创建
    2. 创建-活动就绪
    3. 创建-静止就绪
    4. 执行-终止

2.2.4 进程管理中的数据结构

  1. 操作系统中用于管理控制的数据结构
    • 操作系统中,对每个资源和每个进程都设置一个数据结构,用于表征其实体,称之为资源信息表或进程控制表。
    • 其中包含了资源或进程的标识、描述、状态等信息以及一批指针
    • OS管理的这些数据结构一般分为以下四类:内存表、设备表、文件表和用于进程管理的进程表。
  2. 进程控制块PCB的作用
    1. 作为独立运行基本单位的标志
    2. 能实现间断性运行方式
    3. 提供进程管理所需要的信息
    4. 提供进程调度所需要的信息
    5. 实现与其他进程的同步与通信
  3. 进程控制块中的信息
    • 进程标志符
      1. 外部标识符
      2. 内部标识符
    • 处理机状态
    • 处理机状态信息也称为处理机的上下文,主要是由处理机的各种寄存器中的内容组成的。包括:通用寄存器、指令计数器、程序状态字、用户栈指针。
    • 进程调度信息
    • 包括:进程状态、进程优先级、进程调度所需要的其他信息、事件
    • 进程控制信息
    • 指用于进程控制所必须的信息,包括:程序和数据的地址、进程同步和通信机制、资源清单、链接指针。
  4. 进程控制块的组织方式
    • 线性方式
    • 链接方式

- 索引方式


2.3 进程控制

2.3.1 操作系统内核

  • 将一些与硬件紧密相关的模块、各种常用设备的驱动程序以及运行频率较高的模块,安排在紧靠硬件的软件层次中,将它们常驻内存,称为OS的内核
  • 目的在于两方面:便于对这些软件进行保护,防止遭受其他应用程序的破坏,而是可以提高OS的运行效率。
  • 为了防止OS本身及其关键数据遭受应用程序有意或无意的破坏,通常将处理机的执行状态分成系统态和用户态两种。
  • OS内核具备的功能:
    1. 支撑功能
      • 中断处理
      • 时钟管理
      • 原语操作
      • 所谓原语,就是由若干条指令组成的,用于完成一定功能的一个过程。原子操作:一个操作系统中的所有动作要么全做,要么全不做。
    2. 资源管理功能
      • 进程管理
      • 存储器管理

- 设备管理

2.3.2 进程的创建

  1. 进程的层次结构
    • 在OS中,允许一个进程创建另一个进程。
  2. 进程图
    • 所谓进程图就是用于描述进程关系的一棵有向树
  3. 引起创建进程的事件
    • 导致一个进程去创建另一个进程的典型事件有四类:
      1. 用户登录
      2. 作业调度
      3. 提供服务
      4. 应用请求
  4. 进程的创建
    1. 申请空白PCB,为新进程申请活得唯一的数字标识符,并从PCB集合中索取一个空白PCB
    2. 为新进程分配其运行所需要的资源
      • 对于批处理作业,其大小可在用户提出创建进程要求时提供
      • 若是为应用进程创建子进程,也应是在该进程提出创建进程的请求中给出所需内存的大小。
      • 对于交互型作业,用户可以不给出内存要求,而由系统分配一定的空间,如果新进程要共享某个已在内存的地址空间,则必须建立相应的链接。
    3. 初始化进程控制块
      • PCB的初始化包括:初始化标识信息,将系统分配的标识符和父进程标识填入新PCB中
      • 初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶。
      • 初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显示方式提出高优先级要求。

- 如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。

2.3.3 进程的终止

  1. 引起进程终止的事件
    • 正常结束,表示进程的任务已经完成。
    • 异常结束,是指进程在运行时发生了某种异常事件,使程序无法继续运行。
      • 越界错,指程序所访问的存储区,已越出该进程的区域
      • 保护错,指进程试图去写一个只读文件
      • 非法指令,指程序试图去执行一条不存在的指令
      • 特权指令错,指用户进程试图去执行一条只允许OS执行的指令
      • 运行超时,指进程的执行时间超过了指定的最大值
      • 等待超时,指进程等待某事件的事件超过了规定的最大值
      • 算术运算错,指进程试图去执行一个被禁止的运算
      • I/O故障,指I/O过程中发生了错误
    • 外界干预,指进程应外界的请求而终止运行
      • 操作员或操作系统干预,指如果系统中发生了某事件。
      • 父进程请求,指当紫禁城已完成了父进程所要求的任务时,父进程可以提出请求结束该子进程。
      • 因父进程终止,指当父进程终止时,它的所有子进程也都应当结束。
  2. 进程的终止过程
    • 如果系统中发生了要求终止进程的某事件,OS便调用进程终止原语,按下述过程去终止指定的进程。
      1. 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态。
      2. 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度。
      3. 若该进程还有子孙进程,还应将其所有子孙进程也都予以终止,以防它们称为不可控的进程。
      4. 将被终止进程所拥有的全部资源或者归还给父进程,或者归还给系统

5. 将被终止进程PCB从所在队列中移出,等待其他程序来收集信息

2.3.4 进程的阻塞与唤醒

  1. 引起进程阻塞和唤醒的事件
    • 向系统请求共享资源失败
    • 等待某种操作的完成
    • 新数据尚未到达
    • 等待新任务的到达
  2. 进程阻塞过程
    • 正在执行的进程,如果发上了上述事件,进程便通过调用阻塞原语block将自己阻塞。进入block过程后,应先立即停止执行,把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入到阻塞队列。最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换。
  3. 进程唤醒过程

- 首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。

2.3.5 进程的挂起与激活

  1. 进程的挂起
    • 出现进程挂起的事件后,OS将利用挂起原语suspend将指定进程或处于阻塞状态的进程挂起。suspend的执行过程是:首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪。对于活动阻塞状态的进程,将其改为静止阻塞。若被挂起的进程正在执行,则转向调度程序重新调度。
  2. 进程的激活过程

- 发生激活进程的事件,OS利用激活原语active,将制定进程激活,激活原语首先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪。若是静止阻塞,便将之改为活动阻塞。


2.4 进程同步

2.4.1 进程同步的基本概念

  • 进程同步机制的主要任务就是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按一定的规则(或时序)共享系统资源,并能很好的相互合作,从而使程序的执行具有可再现性。
    1. 两种形式的制约关系
      • 间接相互制约关系
      • 直接相互制约关系
    2. 临界资源(Critical Resource)
  • producer-consumer问题(生产者-消费者问题) : 如果要并行执行,其共有变量count会相互影响,出现不确定的值,需将其作为临界资源处理,令生产者进程和消费者进程互斥的访问变量count。
    1. 临界区(Critical section)
  • 把在每个进程中访问临界资源的那段代码称为临界区。
  • 若要保证诸进程互斥的进入自己的临界区,需要对欲访问的临界资源进行检查,看它是否正被访问。
  • 所以必须在临界区前面增加一段用于检查的代码,把这段代码称为进入区(Entry section)
  • 相应的,在临界区后面需要加上一段退出区的代码,用于将临界区正被访问标志恢复为未被访问的标志。
  • 进程中,除上述进入区、临界区及退出区之外的其他部分代码在这里都被称为剩余区。
    1. 同步机制应遵循的规则
    2. 空闲让进 当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,已有效的利用临界资源。
    3. 忙则等待 当已有进程进入临界区时,表明临界资源正在被访问,因而其他试图进入临界区的进程必须等待,已保证对临界资源的互斥访问。
    4. 有限等待 对要求访问的临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入‘死等’状态。

4. 让权状态 当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入‘忙等’的状态。

2.4.2 硬件同步机制

  • 目前计算机已提供了一些特俗的硬件指令,允许对一个字中的内容进行检测和修正,或两个字内容的交换。
    1. 关中断
      • 进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。
      • 缺点:滥用关中断权力可能导致严重后果、关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力、关中断方法不适用多CPU系统。
    2. 利用Test-and-Set指令实现互斥
  • TS指令的描述
    bool TS(boolean *lock)
    {
    boolean old; //true为正在使用,false表示空闲。
    old=*lock;
    *lock=TRUE;
    return old;
    }

    1. 利用Swap指令实现进程互斥
  • 该指令称为兑换指令
    void swap(boolean *a,boolean *b)
    {
    boolean temp;
    temp=*a;
    *a=*b;
    *b=temp;
    }
  • 为每个临界资源设置一个全局的布尔变量lock,初值为false,在每个进程中再利用一个局部布尔变量Key.
  • 利用Swap指令实现进程互斥的循环过程如下:
    do {
    key =True;
    do
    {
    swap(&lock,%key);
    }while(key!=false)
    };
    临界区操作;
    lock=False;
    }while(True);

- 上述指令的缺点:当临界资源忙碌时,其他访问进程必须不断的测试,处于一种忙等的状态,不符合‘让权等待’的原则,造成处理机的浪费。也难用于解决复杂的进程同步问题。

2.4.3 信号量机制

  1. 整型信号量
    • 最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问。
      wait(S){
      while(S<=0); //do no-op
      S--;
      }
      signal(S)
      {
      S++;
      }
    • 该机制还是不断测试,为遵循让权等待原则
  2. 记录型信号量

    • 记录型信号量机制则是一种不存在“忙等”现象的进程同步机制,在采取了让权等待的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程。它所包含的数据项描述如下:
          int value;       //代表资源数目
          struct process_control_block *list;        //进程链表指针
      }

    相应的,wait(S),signal(S)操作描述如下:

    {
        S->value--;                                    //s->value表示系统中某类资源数目,又称为资源信号量                       
        if(S->value<0) block(S->list);                //分配完毕,,进行自我阻塞
    }
    
        signal(semaphore *S)
        {
            S->value++;
            if(S->value<=0) wakeup(S->list);         //如果仍有等待进程被阻塞,调用wakeup,将链表中第一个等待进程唤醒。
        }

    上述进程互斥问题针对的是多个并发进程仅共享一个临界资源的情况。

  3. AND型信号量
    • 当进程同时要求的共享资源愈多,发生进程死锁的可能性也就愈大。
    • AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部分配给进程,待进程使用完毕后在一个释放。为此,在wait操作中增加一个AND条件
      Swait(S1,S2...Sn)
      {
      while(True)
      {
      if(Si>=1&&...&&Sn>=1)
      {
      for(i=1;i<=n;i++)
      Si--;
      break;
      }
      else
      {
      place the process in the waiting queue associated with the first Si found with Si<1,set the program count of this process to the beginning of Swait operation
      }
      }
      }
      Ssignal(S1,S2...Sn)
      {
      while(true)
      {
      for(i=1;i<=n;i++)
      { Si++;
      remove all the process waiting in the queue associated with Si into the ready queue
      }
      }
      }
  4. 信号量集
    • 对于上述的记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能对某类临界资源进行一个单位的申请或释放,当一次需要N个单位时,便要进行N次wait操作,显然低效,设置会增加死锁的概率。
    • 为了确保系统的安全性,当进程在申请某类临界资源时,在每次分配前,都必须测试资源的数量,判断是偶大于可分配的下限值,已决定是否可以分配。
    • 进程对信号量的测试值不在是1,而是该资源分配的下限值ti。进程对该资源的需求量是di,表示资源占用量。进行Si=Si-di操作。

- 其对应的Swait和Ssignal格式为:Swait(S1,t1,d1,...,Sn,tn,dn); Ssignal(S1,d1,...,Sn,dn);

2.4.4 信号量的应用

  1. 利用信号量实现进程互斥
    • 实现两个进程互斥的描述如下:
      1. 设mutex为互斥信号量,其初值为1,取值范围为(-1,0,1)。当mutex=1时,表示两个进程皆未进入需要互斥的临界区;当mutex=0时,表示有一个进程进入临界区运行,另外一个必须等待,挂入阻塞队列;当mutex=-1时,表示有一个进程正在临界区运行,另外一个进程因等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程退出时唤醒。
      2. 代码描述
        semaphore mutex=1;
        Pa() Pb()
        { {
        while(1) while(1)
        { {
        wait(mutex); wait(mutex);
        临界区; 临界区;
        signal(mutex); signal(mutex);
        剩余区; 剩余区;
        } }
        } }
  2. 利用信号量实现前趋关系

- 设有两个并发执行的进程P1和P2. p1中有语句s1,p2中有语句s2,希望在s1执行后在执行s2。为实现这种前驱关系,只需将进程P1和P2共享一个公用信号量S,并赋予其初值为0.在signal(S)操作放在语句s1后面,在S2语句前面插入wait(S)操作。

管程机制

  • 虽然信号量机制是一种即方便、又有效的进程同步机制,但每个要访问临界资源的进程都必须自备同步操作wait(S)和signal(S),这就使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。为了解决该问题,产生了管程(Monitors)
    1. 管程的定义
      • 代表共享资源的数据结构以及由该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,称之为管程。管程被请求和释放资源的进程所调用。
      • 管程由四部分组成:1.管程的名称 2.局部于管程的共享数据结构说明 3.对该数据结构进行操作的一组过程 4.对局部于管程的共享数据设置初始值的语句。
      • 管程语法描述如下:
        Monitor monitor_name{ //管程名
        share variable declarations; //共享变量说明
        cond declarations; //条件变量说明
        public: //能被管程调用的过程
        Void P1(...) //对数据结构操作的过程
        {...}
        void P2(...)
        {...}
        ...
        void(...)
        {...}
        ....
        { //管程主体
        initialization code; //初始化代码
        }
        }
      • 管程主要特性:1.模块化 2.抽象数据类型 3.信息掩蔽
      • 管程与进程不同:1.进程定义的是私有数据结构PCB,管程定义的是公共数据结构,如消息队列。 2.二者都存在对各自数据结构上的操作,但进程是由顺序程序执行有关操作,而管程主要是顺序同步操作和初始化操作。3.设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题。 4.进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程为主动工作方式。 5.进程之间能并发执行,管程不能与其调用者并发。6.进程具有动态性,管程只是操作系统中的一个资源管理模块。
    2. 条件变量
  • 为了解决一个进程调用了管程,在管程中时被阻塞或挂起,直到阻塞或挂起的原因解除,在此期间,如果该进程不释放管程,则其它进程无法进入管程,被迫长时间等待。引入了条件变量condition。
  • 一个进程被阻塞或挂起的条件有多个,因此在管程中设置了多个条件变量,对这些条件变量的访问只能在管程中进行。
  • 条件变量也是一种抽象数据类型,每个条件变量保存了一个链表,用于记录因该条件变量而阻塞的所有进程,同时提供两个操作即可表示为x.wait和x.signal,其含义为:
    • x.wait:正在调用管程的进程因X条件需要被阻塞或挂起,则调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件变化。

- x.signal:正在调用管程的进程因x条件发生了变化,则调用x.signal,重新启动一个因x条件而阻塞或挂起的进程,如果存在多个这样的进程,则选择其中的一个。如果没有,继续执行原进程,而不产生任何结果。


2.5 经典进程的同步问题

2.5.1生产者-消费者问题

  1. 利用记录型信号量解决生产者-消费者问题
  2. 利用AND信号量解决生产者-消费者问题
  3. 利用管程解决生产者-消费者问题
    • 首先建立一个管程,包含两个过程
      1. put(x)过程 生产者利用该过程将自己生产的产品投放到缓冲池中,并用整型变量count来表示缓冲池中已有的产品数目
      2. get(x)过程 消费者利用该过程从缓冲池中取出一个产品,当count<=0,表示缓冲池中已无可取用的产品,消费者应等待。
    • 对于条件变量notfull和notempty,分别有两个过程cwait和csignal对它们进行操作:
      1. cwait(condition):当管程被一个进程占用时,其它进程调用该过程时阻塞,并挂在条件condition的队列上。
      2. csignal(condition):唤醒在cwait执行后阻塞在条件condition队列上的进程,如果这样的进程不止一个,则选择其中一个实施唤醒操作;如果队列为空,则无其它操作而返回。
        -PC管程可描述如下:
        Monitor producerconsumer{
        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);
        }
        {
        in=0;out=0;count=0;
        }
        }PC;
    • 在利用管程解决生产者-消费者问题时,其中的生产者和消费者可描述为:
      “` void producer()
      {
      item x;
      while(True)
      {

      produce an item in nextp;
      PC.put(x);
      }
      }
      void consumer()
      {
      item x;
      while(True)
      {
      PC.get(x);
      comsume the item in nextc;
      ….
      }
      }
      void main()
      {
      cobegin;
      producer();
      consumer();
      coend
      }

“`

2.5.2哲学家进餐问题

  • 问题描述:有五个哲学家公用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,它们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐,进餐毕,放下筷子继续思考。
    1. 利用记录型信号量解决哲学家进餐问题
      • 放在桌子上的筷子是临界资源,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组,其描述如下semaphore chopstick[5]={1,1,1,1,1};所有信号量均被初始化为1,第i位哲学家的活动可描述为:
        do{
        wait(chopstick[i]);
        wait(chopstick[i+1]%5);
        ...
        //eat
        ...
        signal(chopstick[i]);
        signal(chopstick[(i+1)%]5);
        ...
        //think
        ...
        }while(True);
      • 以上描述中,可保证不会有两个想吐的哲学家同时进餐,但是可能会引起死锁。如果五位哲学家同时去拿左边的筷子,就会死锁。
      • 解决方法:
      • 至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而能使更多的哲学家进餐。
      • 仅当哲学家的左右两只筷子均可用时,才允许他拿起筷子进餐。
      • 规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子。而偶数好哲学家则相反。
    2. 利用AND信号量进制解决哲学家进餐为题
      • 要求每个哲学家先获得两个临界资源后才能进餐。
        “` semaphore chopstick[5]={1,1,1,1,1};
        do {

        //think

        Swait(chopstick[(i+1)%5],chopstick[i]);

        //eat

        Signal(chopstick[(i+1)%5],chopstick[i]);

}while(True);

2.5.3 读者-写者问题

  • 问题描述:一个数据文件或记录可被多个进程共享,把只需求读该文件的进程称为”Reader进程”,其它进程则称为“Writer进程”。允许多个进程同时读一个共享对象,因为读操作不会使数据文件混乱。但不允许一个Writer进程和其他Reader进程或Writer进程同时访问共享对象。所谓“读者-写者”问题,是指保证一个Writer进程必须与其他进程互斥地访问共享对象的同步问题。
    1. 利用记录型信号量解决读者-写者问题
      • 为实现Reader与Writer进程间在读或写时的互斥而设置了一个互斥信号量wmutex。另外,再设置一个整型变量readcount表示正在读的进程数目。
      • 读者-写者问题可描述如下:
        semaphore rmutex=1,wmutex=1;
        int readcount=0;
        void reader(){
        do{
        wait(rmutex);
        if(readcount==0) wait(Wmutex);
        readcount++;
        signal(rmutex);
        ...
        perform read operation;
        ...
        wait(rmutex);
        readcount--;
        if(readcount==0)
        signal(Wmutex);
        signal(rmutex);
        }while(True);
        }
        void Writer()
        {
        do{
        wait(wmutex);
        perform write opearion;
        signal(wmutex);
        }while(True);
        }
        void main()
        {
        cobegin
        Reader();
        Writer();
        coend
        }
    2. 利用信号量机制解决读者-写者问题
      • 与前面不同的是,增加了一个限制,即最多只允许RN个读者同时读。引入信号量L,赋予初值为RN,通过执行wait(L,1,1)操作来控制读者的数目。
      • 代码描述如下:
        “` int Rn;
        semaphore L=RN,mx=1;
        void Reader()
        {
        do{
        Swait(L,1,1);
        Swait(mx,1,0);

        perform read operation;

        Ssignal(L,1);
        }while(true);
        }
        void Writer()
        {
        do{
        Swait(mx,1,1;L,RN,0);
        perform write opeartion;
        Ssignal(mx,1);
        }while(True);
        }
        void main()
        {
        cobegin
        Reader();
        Writer();
        coend;
        }

“`


2.6 进程通信

  • 进程通信是指进程之间的信息交换。

2.6.1 进程通信的类型

  1. 共享存储器系统(Shared-Memory System)
    • 在共享存储器系统中,相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。分成以下两种类型:
      1. 基于共享数据结构的通信方式
      2. 基于共享存储区的通信方式
  2. 管道(pipe)通信系统
    • 所谓’管道‘,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。
    • 为了协调双方的通信,管道机制必须提供以下三方面的协调能力:
      1. 互斥,即当一个进程正在对pipe执行读/写操作时,其他(另一)进程必须等待。
      2. 同步,指当写(输入)进程把一定数量的数据写入pipe,便去睡眠等待。直到读(输出)进程取走数据后再把他唤醒。
      3. 确定对方是否存在,只有确定了对方已存在时才能进行通信。
  3. 消息传递系统(Message passing system)
    • 基于消息的通信方式属于高级通信方式,因其实现方式不同,可进一步分成两类:
      1. 直接通信方式,是指发送进程利用OS所提供的发送原语,直接把消息发给目标进程
      2. 间接通信方式,是指发送和接收进程,都通过共享中间实体(称为邮箱)的方式进行消息的发送和接收,完成进程间的通信。
  4. 客户机-服务器系统
    • 主要的实现方法分为三类:
      1. 套接字(Socket)
        • 一个套接字就是一个通信标识类型的数据结构,包含了通信目的地地址、通信使用的端口号、通信网络的传输层协议、进程所在的网络地址,以及针对客户或服务器提供的不同API等。
        • 套接字包括两类:
          1. 基于文件型
          2. 基于网络型
      2. 远程过程调用和远程方法调用
        • 远程过程(函数)调用RPC(Remote Procedure Call),是一个通信协议,用于通过网络连接的系统。该协议允许运行于一台主机系统上的进程调用另一台主机系统上的进程。
        • 远程过程的调用主要步骤:
          1. 本地过程调用者以一般方式调用远程过程在本地关联的客户存根,传递相应的参数,然后将控制权转移给客户存根。
          2. 客户存根执行,完成包括过程名和调用参数等信息的消息建立,将控制权转移给客户存根。
          3. 本地客户进程完成与服务器的消息传递,将消息发送到远程服务器进程。
          4. 远程服务器进程接收消息后转入执行,并根据其中的远程过程名找到对应的服务器存根,将消息转给该存根。
          5. 该服务器存根接收到消息后,由阻塞状态转入执行状态,拆开消息从中取出过程调用的参数,然后以一般方式调用服务器上关联的过程。
          6. 在服务器端的远程过程运行完毕后,将结果返回给与之关联的服务器存根。
          7. 该服务器存根获得控制权运行,将结果打包为消息,并将控制权转移给远程服务器进程。
          8. 远程服务器进程将消息发送回客户端。
          9. 本地客户进程接收到消息后,根据其中的过程名将消息存入关联的客户存根,再将控制权转移给客户存根。

10. 客户存根从消息中取出结果,返回给本地调用者进程,并完成控制权的转移。

2.6.2 消息传递通信的实现方式

  1. 直接消息传递系统
    • 直接通信原语
      1. 对称寻址方式
        • 通常,系统提供下述两条通信命令(原语)send(receiver,message);receive(sender,message)
      2. 非对称寻址方式
        • 在某些情况下,接收进程可能需要与多个发送进程通信,无法实现指定发送进程。对于这样的应用,接收进程不需要命名发送进程,只填写标识源进程的参数,即可完成通信后的返回值。而发送进程仍需要命名接收进程。原语表示为:send(P,message);receive(id,message)
    • 消息的格式
      • 单机系统环境,定长格式
    • 进程的同步方式
      • 发送进程阻塞,接收进程阻塞。
      • 发送进程不阻塞、接收进程阻塞
      • 发送进程和接收进程均不阻塞
    • 通信链路
      两种方式建立通信链路:
      1. 由发送进程在通信之前用显示的“建立连接”命令氢气系统为之建立一条通信链路,在链路使用完成后拆除链路。
      2. 发送进程无须明确提出建立链路的请求,只需利用系统提供的发送命令,系统会自动地为之建立一条链路。
        根据通信方式地不同,将链路分为两种:
      3. 单项通信链路,只允许发送进程向接收进程发送消息,或者相反
      4. 双向通信链路,既允许由进程A向进程B发送消息,也允许B向A。
  2. 信箱通信
    • 信箱的结构
      • 信箱头,用以存放有关信箱的描述信息。
      • 信箱体,由若干个可以存放消息的信箱格组成。
    • 信箱通信原语
      • 邮箱的创建和撤销
      • 消息的发送和接收
    • 信箱的类型
      • 私用邮箱
      • 公用邮箱
      • 共享邮箱
    • 在利用邮箱通信时,在发送进程和接收进程之间,存在以下四种关系:
    • 一对一关系
    • 多对一关系
    • 一对多关系

- 多对多关系

2.6.3 直接消息传递系统实例

  1. 消息缓冲队列通信机制中的数据结构
    • 消息缓冲区,在消息缓冲队列通信中,主要i用的数据结构时消息缓冲区。可描述如下
      typedef struct message_buffer{
      int sender; //发送者进程标识符
      int size; //消息长度
      char* text; //消息正文
      struct message_buffer *next; //指向下一个消息缓冲区的指针
      }</li>
      <li>PCB中有关通信的数据项,在PCB中应增加的数据项可描述如下;
      typedef struct processcontrol_block{

      struct message_buffer *mq; //消息队列队首指针
      semaphore mutex; //消息队列互斥信号量
      semaphore sm; //消息队列资源信号量

      }PCB;
  2. 发送原语

3. 接收原语

2.7 线程(Threads)的基本概念

2.7.1 线程的引入

  • 在操作系统中在引入线程,是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性
    1. 进程的两个基本属性
      • 进程是一个可拥有资源的独立单位,一个进程要能独立运行,它必须拥有一定的资源。
      • 进程同时又是一个可独立调度和分派的基本单位。
    2. 程序并发执行时所需付出的时空开销
  • 为使程序并发执行,系统必须进行以下操作:
    • 创建进程,系统创建进程时,必须为其分配所必须的、除处理机以外的所有资源
    • 撤销进程,系统在撤销进程时,必须对其所占有的资源执行回收操作,然后在撤销PCB
    • 进程切换,对进程进行上下文切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境。
      1. 线程————作为调度和分派的基本单位

- 为实现多处理环境下进程的创建、调度、分派,都需要花费较大的时间和空间开销。在OS中引入线程,以线程作为调度和分派的基本单位。,则可以有效地改善多处理机系统地性能。

2.7.2 线程与进程的比较

  1. 调度的基本单位
    • 在引入线程的OS中,把线程作为调度和分派的基本单位,因而线程时是独立运行的基本单位。
  2. 并发性
    • 在引入线程的OS中,进程可以并发执行,一个进程中的多个线程可以并发执行,一个进程中的所有线程都可以并发执行,不能进程中的线程也能并发执行。
  3. 拥有资源
    • 线程本身并不拥有资源,而是仅有一点必不可少的、能保证独立运行的资源。
    • 线程除拥有自己的少量资源外,还允许多个线程共享该进程所拥有的资源。
  4. 独立性
    • 在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低得多。
  5. 系统开销
    • OS在创建进程时得开销明显大于线程创建或撤销时所付出得开销。
  6. 支持多处理机系统

- 对于多线程进程,就可以将一个进程中得多个线程分配到多个处理机上,使它们并发执行。

2.7.3 线程的状态和线程控制块

  1. 线程运行的三个状态
    • 执行状态
    • 就绪状态
    • 阻塞状态
  2. 线程控制块TCB
    • 线程控制块通常有这样几项:
    • 线程标识符
    • 一组寄存器
    • 线程运行状态
    • 优先级
    • 线程专有存储区
    • 信号屏蔽
    • 堆栈指针
    • TCB中,需设置两个指向堆栈的指针,指向用户自己堆栈的指针和指向核心栈的指针。
  3. 多线程OS中的进程属性
    • 进程是一个可拥有资源的基本单位
    • 多个线程可并发执行

- 进程已不是可执行的实体


2.8 线程的实现

2.8.1 线程的实现方式

  1. 内核支持线程KST(Kernel Supported Threads)
    • 内核支持线程KST在内核的支持下运行,其创建、阻塞、撤销和切换也在内核空间实现,在内核空间设置了一个线程控制块,内核根据该控制块而感知某线程是否存在。
    • 这种线程实现方式的四个主要优点:
    • 在多处理器系统中,内核能够同时调度同一进程中的多个线程并行执行。
    • 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其他线程占有处理器运行,也可以运行其他进程中的线程。
    • 内核支持线程具有最小的数据结构和堆栈,线程的切换比较快,切换开销小。
    • 内核本省也可以采用多线程技术,可以提高系统的执行速度和效率
    • 主要缺点:
    • 对于用户的线程切换而言,其模式切换的开销较大。
  2. 用户级线程
    • 用户级线程是在用户空间中实现的,用户级线程是与内核无关的。
    • 用户级线程方式的优点:
    • 线程切换不需要转换到内核空间
    • 调度算法可以是进程专用的
    • 用户级进程的实现与OS平台无关
    • 主要缺点:
    • 系统调用的阻塞问题
    • 在单纯的用户级线程是实现方式中,多线程应用不能利用多处理机进行多重处理的优点.
  3. 组合方式
    • 三种模型:
    • 多对一模型
    • 一对一模型

- 多对多模型

2.8.2 线程的实现

  1. 内核支持线程的实现
    • 系统在创建一个新进程时,便为它分配一个任务数据区,其中包括若干个线程控制块TCB空间。
    • 内核支持线程的调度和切换分为抢占式方式和非抢占式方式两种
    • 在线程的调度算法上,可采用时间片轮转法,优先权算法
  2. 用户级线程的实现
    • 用户级线程是在用户空间实现的,它们都在一个中间系统上。
    • 当前有两种方式实现中间系统:
    • 运行时系统,所谓运行时系统,实质上是用于管理和控制线程的函数(过程)的集合。

- 内核控制线程

2.8.3 线程的创建和终止

  1. 线程的创建
    • 应用程序在启动时,通常仅有一个线程在执行,称为“初始化线程”,主要功能用于创建新线程。
  2. 线程的终止
    • 由终止线程通过调用相应的函数(或系统调用)对它执行终止操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值