[Study]操作系统

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Study]操作系统相关的知识,希望对你有一定的参考价值。

文章目录

一、概述

1.1 操作系统概述

  1. 操作系统的概念:操作系统(Operating System)控制和管理整个计算机系统的硬件和软件资源,合理地调度计算机的工作和资源,提供给用户和其他软件方便的接口和环境,它是计算机系统中最基本的系统软件

  2. 操作系统应具有的功能:

    • 操作系统作为系统资源的管理者:处理机(进程)管理、存储器(内存)管理、文件管理、设备管理

    • 操作系统作为用户与计算机硬件之间的接口:命令接口(允许用户直接使用)、程序接口(由一组系统调用组成,程序接口 = 系统调用 = 系统调用命令 = 广义指令)、图形用户界面

    • 操作系统作为最接近硬件的层次:实现对硬件机器的扩展(裸机上安装操作系统便可以提供资源管理功能以方便用户使用)

  3. 操作系统的特征:

    • 并发(并发与并行)
    • 共享(互斥与同时)
    • 虚拟(空分虚拟和时分虚拟)
    • 异步

    其中 并发和共享 是两个最基本的特征,二者互为存在条件

  4. 操作系统的发展和分类:

    • 手工操作阶段

    • 批处理阶段(单、多道批处理)

    • 分时操作系统:计算机以时间片为单位轮流为各个用户 / 作业服务,各个用户可通过终端与计算机进行交互

    • 实时操作系统(硬、软实时系统)

    • 网络操作系统

    • 分布式操作系统

    • 个人计算机操作系统

    其中 多道批处理系统 的出现标志着操作系统开始出现

1.2 运行机制

  1. 两种指令

    • 特权指令:运行在核心态
    • 非特权指令:运行在核心态或用户态
  2. 两种处理器状态:用程序状态字寄存器(PSW)的某个标志位标识当前处理器状态

    • 用户态(目态)
    • 核心态(管态)
  3. 两种程序

    • 内核程序:需要使用特权指令的程序
    • 应用程序:无需使用特权指令的程序
  4. 操作系统内核:细分为大内核与微内核。内核是计算机上配置的底层软件,是操作系统最基本、最核心的部分,需要实现时钟管理、中断管理、原语等核心功能。对系统资源进行管理时还需要实现进程管理、内存管理、设备管理、文件管理等功能

    • 原语是一种特殊的程序,处于操作系统最底层,是最接近硬件的部分,这种程序运行时间较短、调用频繁且具有原子性(一气呵成,不可中断

1.3 中断与异常

  1. 中断机制的诞生:为了实现多道应用程序并发执行而引入的一种技术。中断可以使 CPU 从 用户态 -> 核心态,使操作系统获得计算机的控制权,从而开展一系列的管理工作

  2. 中断的分类:

    • 内中断:也称 异常、陷入、例外。信号来源于 CPU 内部,与当前执行的指令有关
    • 外中断:简称中断。信号来源于 CPU 外部,与当前执行的指令无关
  3. 用户态与核心态的切换:

    • 用户态 -> 核心态:中断 是唯一途径
    • 核心态 -> 用户态:执行一个特权指令将程序状态字(PSW)标志位设置为 “用户态”

1.4 系统调用

  1. 系统调用概念:系统调用是操作系统提供给应用程序(编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以发出系统调用请求来获得操作系统的服务。系统调用发生在用户态,对系统调用的处理发生在核心态

  2. 系统调用按功能分类:设备管理、文件管理、进程控制、进程通信、内存管理

  3. 系统调用与库函数的区别:

  1. 系统调用过程:传递系统调用参数 -> 执行陷入指令(用户态)-> 执行系统调用(核心态)-> 返回用户程序
    • 陷入指令在用户态下执行,执行后立即引发一个 内中断 从而使 CPU 进入核心态
    • 陷入指令是 唯一 一个只能在 用户态 下执行而不能在核心态下执行的指令

二、进程管理

2.1 进程定义及特征

  1. PCB:系统为每个运行的程序配置一个数据结构称为进程控制块(PCB),用来描述进程的各种信息(指令和数据)。其中包括了进程描述信息、进程控制和管理信息、资源分配清单、处理机相关信息等

  2. 进程与进程实体:

    • PCB、程序段、数据段三部分构成了进程实体(进程映像)
    • 进程(动态)是进程实体(静态)的运行过程,是系统进行资源分配和调度的一个独立单位
  3. 进程的组织方式:

    • 链接方式:按照进程状态将 PCB 分为多个队列,操作系统持有指向各个队列的指针
    • 索引方式:按照进程状态的不同,建立几张索引表,操作系统持有指向各个索引表的指针
  4. 进程的特征:动态性、并发性、独立性、异步性、结构性

2.2 进程状态与转换

  1. 进程的三种基本状态:运行态(Running)、就绪态(Ready)、阻塞态(Waiting/Blocking

  2. 进程的另外两种状态:新建(New)、终止态(Terminated

  3. 进程状态间转换过程:

    • 运行态 -> 阻塞态:进程自身做出的主动行为
    • 阻塞态 -> 就绪态:不是进程自身能够控制的,是一种被动行为

2.3 进程控制

  1. 进程控制概念:主要目的是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能
  2. 进程控制实现:操作系统中使用原语来实现进程控制。原语的特点是执行期间不允许中断、只能一气呵成,这种不能被中断的操作称为原子操作,具体采用 “关中断指令”“开中断指令” 实现
  3. 原语实现进程控制的职责:
    • 更新 PCB 中的信息:修改进程状态标志、将运行环境保存到 PCB、从 PCB 恢复运行环境
    • 将 PCB 插入到合适的队列
    • 分配 / 回收资源

2.4 进程通信的三种方式

  1. 共享存储:两个进程对数据结构或存储区的访问必须是互斥的
  2. 消息传递:进程间的数据交换以格式化的消息为单位。进程通过操作系统提供的 “发送消息 / 接收消息” 两个原语进行数据交换
  3. 管道通信:管道只能采用 半双工通信 即某一时间段内只能实现单向数据传输
    • 各个进程需要 互斥 地访问管道
    • 数据以字符流的形式写入管道,当管道写满时写进程的 write() 系统调用将被阻塞,等待中的读进程将数据读走。当读进程将数据全部取走后,管道变空,此时读进程的 read() 系统调用将被阻塞
    • 如果管道未写满则不允许读,同理未读空则不允许写
    • 数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会导致数据读取错误

2.5 线程概念

  1. 线程概念:线程是一个基本的 CPU 执行单元,也是程序执行流的最小单位。引入线程之后,进程只作为除 CPU 之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。进程是 资源分配 的基本单位,而线程是 处理机调度 的基本单位

  2. 线程的属性:

    • 线程是处理机调度的基本单位
    • 线程也有就绪、运行、阻塞三种基本状态
    • 每个线程都有一个线程 ID、线程控制块(TCB
    • 多 CPU 计算机中,各个线程可占用不同的 CPU
    • 同一进程的不同线程间共享进程资源,线程间通信甚至无需系统干预
    • 同一进程中的线程切换,不会引起进程切换,系统开销小;不同进程中的线程切换,会引起进程切换,系统开销大
  3. 线程的实现方式:

    • 用户级线程(User-Level Thread,ULT):“从用户视角能看到的线程”,由应用程序通过线程库实现,所有的线程管理工作都由应用程序负责,在用户态下即可完成,无需操作系统的干预

    • 内核级线程(Kernel-Level Thread,KLT):“从操作系统内核视角能看到的线程”,又称内核支持的线程。内核级线程的管理工作由操作系统内核完成,因而内核级线程的切换必然需要在核心态下才能完成

2.6 三种多线程模型

  1. 多对一模型:多个用户级线程映射到一个内核级线程,即每个用户进程只对应一个内核级线程

    • 优点:用户级线程的切换在用户态即可完成,无需切换到核心态,线程管理开销小,效率高
    • 缺点:当一个用户级线程被阻塞后,整个用户进程都会被阻塞,并发度不高,多个用户线程不可在多核处理机上并行执行

  2. 一对一模型:一个用户级线程映射到一个内核级线程,即一个用户进程对应多个内核级线程

    • 优点:当一个线程被阻塞后,别的线程可以继续执行,并发能力强,可在多核处理机上并行执行
    • 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到内核态下进行管理,成本高,开销大

  3. 多对多模型:n 个用户级线程映射到 m 个内核级线程(n >= m),即每个用户进程对应多个内核级线程

    • 克服了多对一模型并发度不高的缺点
    • 克服了一对一模型中一个用户进程占用过多内核级线程的缺点

2.7 处理机调度的三个层次

  1. 高级调度(作业调度):外存与内存之间的调度

    • 每个作业只调入一次,调出一次
    • 作业调入时建立相应的 PCB,调出时撤销 PCB
  2. 中级调度(内存调度):决定将哪个处于 挂起状态 的进程重新调入内存

    • 一个进程可能会被多次调入、调出内存,因而中级调度发生的频率要比高级调度更高
  • 引入虚拟存储技术之后,可以将暂时不能运行的进程调至外存等待,等它重新具备了运行条件且内存空闲时重新调入内存,这么做的目的是为了提高内存利用率和系统吞吐量。值得注意的是,进程的 PCB 并不会一起调到外存,而是会常驻内存。PCB 中会记录进程数据在外存中的存放位置,进程状态等信息,操作系统通过内存中的 PCB 来保持对各个进程的监控和管理

  • 挂起状态:暂时调到外存等待的进程状态称为 挂起状态(suspend)“挂起”“阻塞” 的区别:挂起态将进程映像调到外存,阻塞态下进程映像仍在内存中

  1. 低级调度(进程调度):主要任务是按照某种方法或策略从就绪队列中选取一个进程,并将处理器分配给它。进程调度是操作系统中最基本的一种调度,频率很高,一般几十毫秒调度一次

三种处理机调度层次的联系和对比:

主要任务发生时机频率状态影响
高级调度(作业调度)从后备队列中选择作业将其调入内存并创建进程外存->内存最低无->创建态->就绪态
中级调度(内存调度)从挂起队列中选择合适的进程将其调回内存外存->内存中等挂起态->就绪态
低级调度(进程调度)从就绪队列中选择一个进程为其分配处理机内存->CPU最高就绪态->运行态

2.8 进程调度概述

  1. 进程调度的方式:非剥夺调度方式(非抢占)和剥夺调度方式(抢占)

  2. 不能进行进程调度与切换的情况:

    • 在处理中断的过程中:中断处理过程复杂,与硬件密切相关,很难做到在中断处理过程中进行进程切换
    • 进程在操作系统 内核程序临界区 中:
      • 内核程序临界区:一般是用来访问某种内核数据结构的,比如进程的就绪队列(由各就绪进程的 PCB 组成)
      • 临界区:访问临界资源的那段代码
      • 临界资源:一段时间内只允许一个进程使用的资源,各个进程需要互斥地访问临界资源
    • 在原子操作过程中(原语操作应一气呵成,不可中断)

2.9 调度算法的评价指标

  1. CPU 利用率:指 CPU “忙碌” 时间占总时间的比例

  2. 系统吞吐量:单位时间内完成的作业数量

  3. 周转时间:指从作业被提交给系统开始,到作业完成为止的这段时间间隔,由以下各部分时间组成:高级调度时间 + 低级调度时间 + CPU 上执行时间 + 等待 I/O 完成的时间

    • 平均周转时间 = 各作业周转时间之和 / 作业数
    • 带权周转时间 = 作业周转时间 / 作业实际运行的时间 = (作业完成时间 - 作业提交时间)/ 作业实际运行的时间
    • 平均带权周转时间:各作业带权周转时间之和 / 作业数

2.10 六种进程调度算法

  1. 先来先服务(FCFS,First Come First Service):非抢占式调度算法,不会导致饥饿现象(某进程/作业长期得不到服务)

    • 优点:公平、算法实现简单
    • 缺点:对长作业有利,短作业不利(等待很长时间而执行时间很短)
  2. 短作业优先(SJF,Shortest Job First):存在抢占式和非抢占式会,产生饥饿现象

    • 非抢占式短作业优先
      • 优点:“最短的” 平均等待时间、平均周转时间
      • 缺点:对短作业有利,长作业不利,可能产生饥饿现象(短作业源源不断地到来)
    • 抢占式短作业优先(最短剩余时间优先):每当有 进程加入 就绪队列或当前 进程已完成 时就需要调度,如果新到达的进程的剩余时间比当前运行的进程的剩余时间更短,则由新进程抢占处理器,当前进程重新回到就绪队列
  3. 高响应比优先(HRRN,Highest Response Ratio Next):非抢占式的调度算法,会产生饥饿现象

    • 只有当前运行的进程主动放弃 CPU 时才需要进行调度,调度时计算所有就绪进程的响应比,选响应比最高的进程运行
    • 响应比 = (等待时间 + 要求服务时间)/ 要求服务时间
  4. 时间片轮转(Round-Robin):抢占式调度算法,不会产生饥饿问题

    • 时间片选择:如时间片过大,使得每个进程都可以在一个时间片内就完成,则时间片轮转调度算法退化为先来先服务算法,并且会增大进程被处理机响应的时间(等待较长时间),因而时间片的选择不能太大。时间片的选择也不应过小,因为频繁地切换进程会耗费过多的处理时间。一般来说,时间片的选择要让切换进程的开销占比不超过 1%
  5. 优先级:抢占式调度算法,会导致饥饿问题(高优先级的进程/作业源源不断到来)

    • 系统进程优先级高于用户进程
    • 前台进程优先级高于后台进程(给用户以友好体验)
    • 操作系统更偏好 I/O 型进程(I/O 型进程可与 CPU 并行执行)
  6. 多级反馈队列:抢占式调度算法,会导致饥饿问题

    • 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
    • 新进程到达时先进入 第 1 级 队列,按 FCFS 原则排队等待被分配时间片,若用完时间片但进程还未结束,则进程进入下一级队列队尾(若已经是最下级队列,则重新放入该队列队尾)
    • 只有 第 k 级 队列为空时,才会为 k+1 级队头的进程分配时间片
    • 在第 k 级队列中的进程运行时,若更高级的 (1 ~ k-1)队列中进入了新进程,则原来运行的进程放回 k 级队列队尾,转而执行优先级更高的进程

2.11 进程同步与互斥

  1. 同步:亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调他们的 工作次序 而产生的制约关系
  2. 实现进程互斥需要遵循的原则:
    • 空闲让进
    • 忙则等待
    • 有限等待
    • 让权等待:当进程不能进入临界区时,应立即释放处理机

2.12 进程互斥的三种软件实现方法

  1. 单标志法:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予
    • 缺点:不遵循空闲让进原则
  2. 双标志检查法:设置一个布尔数组 boolean flag[];,数组中各个元素用来标记各进程想进入临界区的意愿,比如 “flag[0] = true” 意味着 0 号进程想要进入临界区。细分为双标志先检查法和双标志后检查法:区别在于前者 “检查后上锁”,后者 “上锁后检查”
    • 缺点:双标志先检查法不遵循忙则等待原则;双标志后检查法不遵循空闲让进、有限等待原则并可能导致饥饿问题
  3. Peterson 算法:双标志后检查法中,两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区。Gary L. Peterson 想到了一种方法,如果双方都争着想进入临界区,那可以让进程尝试 “孔融让梨”,主动让对方先使用临界区
    • 缺点:不遵循让权等待原则并可能会导致处理机 “忙等”

2.13 进程互斥的三种硬件实现方法

  1. 中断屏蔽方法:利用 “开/关中断指令” 实现,即在某进程开始访问临界区到结束访问为止都不允许被中断,也即不能发生进程切换

    • 优点:简单、高效
    • 缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程
  2. TestAndSet(TS)或 TestAndSetLock(TSL)指令:使用硬件实现,执行的过程不允许被中断,只能一气呵成(原子操作)

    • 优点:实现简单,无需像软件实现方法那样严格检查是否存在逻辑漏洞;适用于多处理机环境
    • 缺点:不满足 “让权等待” 原则,暂时无法进入临界区的进程会占用 CPU 并循环执行 TSL 指令从而导致 “忙等”
  3. Swap 指令:也称 Exchange(XCHG) 指令。使用硬件的方式实现,执行过程不允许被中断,只能一气呵成。优缺点同 TSL 方式一致

2.14 信号量机制

  1. 定义:信号量其实就是一个变量(可以是一个整数或是更复杂的记录型变量),可以用一个信号量来标识系统中某种资源的数量

  2. 一对原语:wait(W)signal(S) 常简称为 P、V 操作

    • P:proberen,荷兰语,有 test 的意思,即尝试消费一个信号量
    • V:verhogen,荷兰语,有 increase 的意思,即增加、释放一个信号量
  3. 记录型信号量:

    // 信号量定义
    typedef struct 
        int value;			// 剩余资源数
        struct process *L;	// 等待队列
     semaphore;
    
    // wite 原语申请资源,P 操作
    void wait (semaphore S) 
        S.value--;
        if (S.value < 0) 
            // 当前线程阻塞并挂到 S 等待队列队尾
            block(S.L);
        
    
    
    // singal 原语释放资源,V 操作
    void signal(semaphore S) 
        S.value++;
        // singal 释放资源后 S.value 仍小于 0,说明仍然存在等待资源的进程在阻塞,则唤醒它
        if (S.value <= 0) 
            // 存在可用资源且等待队列中存在阻塞线程,唤醒队头线程
            wakeup(S.L);
        
    
    
  4. 信号量机制实现互斥:

    • 分析并发进程的关键活动,划定临界区
    • 设置互斥信号量 mutex,初值为 1
    • 在临界区之前执行 P(mutex)
    • 在临界区之后执行 V(mutext)
  5. 信号量实现进程同步:

    • 分析什么地方需要实现 “同步关系”,即需要保证 “一前一后” 执行的操作
    • 设置同步信号量 S,初值为 0
    • 在 “前操作” 之后执行 V(S)
    • 在 “后操作” 之前执行 P(S)
  6. 信号量机制实现前驱关系:

    • 分析问题,画出前驱图,把每一对前驱关系都看作一个同步问题
    • 为每一对前驱关系设置同步信号量,初值为 0
    • 在 “前操作” 之后执行 V(S)
    • 在 “后操作” 之前执行 P(S)

2.15 信号量机制之生产者 - 消费者问题

生产者、消费者共享一个初始为空、大小为 n 的缓冲区

只有缓冲区未满时,生产者才能将产品放入缓冲区,否则阻塞等待

只有缓冲区非空时,消费者才能从缓冲区消费产品,否则阻塞等待

缓冲区是临界资源,各进程必须互斥地访问

信号量定义:

semaphore mutex = 1;	// 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n;	// 同步信号量,表示空闲缓冲区的数量
semaphore full = 0;		// 同步信号量,表示非空缓冲区的数量

生产者:

producer() 
    while (1) 
        生产一个产品;
        P(empty);	// 消耗一个空闲缓冲区
        P(mutex);	// 互斥访问缓冲区
        将产品放入缓冲区;
        V(mutex);	// 释放缓冲区
        V(full);	// 增加一个产品
    

消费者:

consumer() 
    while (1) 
        P(full);	// 消耗一个非空缓冲区
        P(mutext);	// 互斥访问缓冲区
        从缓冲区取走一个产品;
        V(mutex);	// 释放缓冲区
        V(empty);	// 增加一个空闲缓冲区
        使用产品;
    

注:实现互斥的 P 操作一定要放在实现同步的 P 操作之后,否则会产生死锁

2.16 信号量机制之多生产者 - 多消费者问题

桌子上有一个盘子,每次只能向其中放入一个水果

爸爸专向盘子中放入苹果,女儿专等着吃盘子中的苹果

妈妈专向盘子中放入橘子,儿子专等着吃盘子中的橘子

只有盘子为空时,爸爸或妈妈才能向盘子中放入一个水果

只有盘子中存在自己需要的水果时,儿子或女儿才能从盘子中取走自己需要的水果

信号量定义:

semaphore mutext = 1;	// 互斥信号量,互斥地访问盘子
semaphore plate = 1;	// 同步信号量,盘子可放的水果个数
semaphore apple = 0;	// 同步信号量,盘子中的苹果个数
semaphore orange = 0;	// 同步信号量,盘子中的橘子个数

爸爸:

dad() 
    while (1) 
        准备一个苹果;
        P(plate);
        P(mutex);
        将苹果放入盘子;
        V(mutex);
        V(apple);
    

妈妈:

mom() 
    while (1) 
        准备一个橘子;
        P(plate);
        P(mutex);
        将橘子放入盘子;
        V(mutex);
        V(orange);
    

女儿:

daughter() 
    while (1) 
        P(apple);
        P(mutex);
        从盘子中取出苹果;
        V(mutex);
        V(plate);
        吃掉苹果;
    

儿子:

son() 
    while (1) 
        P(orange);
        P(mutex);
        从盘子中取走橘子;
        V(mutex);
        V(plate);
        吃掉橘子;
    

注:实现互斥的 P 操作一定要放在实现同步的 P 操作之后,否则会产生死锁

2.17 信号量机制之吸烟者问题

假设一个系统有三个抽烟者进程和一个供应者进程

香烟由三种材料组成:烟草、纸、胶水,每个抽烟者不停地卷烟并抽掉

三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水

供应者每次将两种材料放在桌上,拥有剩余那种材料的抽烟者拿走桌上的材料后卷起香烟抽掉,并通知供应者完成了

供应者接着便会在桌上放入另外两种材料,如此往复从而实现让三个抽烟者轮流抽烟

信号量定义:

semaphore offer1 = 0;	// 同步信号量,桌上材料组合一的数量
semaphore offer2 = 0;	// 同步信号量,桌上材料组合二的数量
semaphore offer3 = 0;	// 同步信号量,桌上材料组合三的数量
semaphore finish = 0;	// 抽烟是否完成
int i = 0;				// 用于实现抽烟者轮流抽烟

供应者:

provider() 
    while (1) 
        if (i == 0) 
            将组合一放在桌上;
            V(offer1);
         else if (i == 1) 
            将组合二放在桌上;
            V(offer2);
         else if (i == 2) 
            将组合三放在桌上;
            V(offer3);
        
        i = (i + 1) % 3;
        P(finish);	// 等待来自吸烟者的完成信号
    

吸烟者:

smoker1() 
    while (1) 
        P(offer1);
        从桌上拿走组合一;
        卷烟;
        抽掉;
        V(finish);
    


smoker2() 
    while (1) 
        P(offer2);
        从桌上拿走组合二;
        卷烟;
        抽掉;
        V(finish);
    


smoker3() 
    while (1) 
        P(offer3);
        从桌上拿走组合三;
        卷烟;
        抽掉;
        V(finish);
    

注:缓冲区大小为 1,同一时刻 4 个同步信号量中至多有一个值为 1,不会被 P 操作阻塞,因而无需设置专门的互斥信号量实现对缓冲区的互斥访问

2.18 信号量机制之读者 - 写者问题

有读者和写者两组并发进程共享一个文件

当两个及以上读进程同时访问共享数据时不会导致数据不一致的问题

当某个写进程和其它进程同时访问共享数据则可能导致数据不一致的错误。因此要求:

  1. 允许多个读者同时对文件执行读操作
  2. 同一时刻只允许一个写者往文件中写入信息
  3. 任一写者在完成写操作之前不允许其他读者或写者工作
  4. 写者执行写操作前,应让已有的读者和写者全部退出

信号量定义:

semaphore rw = 1;		// 用于实现对文件的互斥访问,表示当前是否有进程在访问共享文件
int count = 0;			// 用于记录当前有几个进程在访问共享文件
semaphore mutext = 1;	// 用于保证对 count 变量的互斥访问
semaphore w = 1; 		// 用于实现 “写优先”,保证写进程不会饿死

写者:

writer() 
    while (1) 
        P(w);
        P(rw);		// 写之前加锁
        写文件;
        V(rw);		// 写之后解锁
        V(w);
    

读者:

reader() 
    while (1) 
        P(w);
        P(mutex);		// 各都进程间互斥访问 count
        if (count == 0) 
            P(rw);		// 第一个读进程负责 “加锁”
        
        count++;
        V(mutex);
        V(w);
        
        读文件;
        
        P(mutex);
        count--;
        if (count == 0) 
            V(rw);	// 最后一个读进程负责 “解锁”
        
        V(mutex);
    

2.19 信号量机制之哲学家进餐问题

一张圆桌上坐着 5 位哲学家,每两个哲学家之间摆一根筷子,桌子中间是一碗米饭

哲学家们倾注毕生的经历用于思考和进餐,哲学家在思考时,并不影响其他人

只有当哲学家饥饿时,才试图拿起左、右两个筷子(一根一根地拿起),如果筷子已经在他人手中则等待

饥饿的哲学家只有成功拿到两根筷子时才可以开始进餐,进餐完毕后继续思考

如何避免死锁现象的发生呢?

  1. 最多允许四个哲学家拿筷子,这样就可以保证至少有一个哲学家可以拿到两根筷子从而进餐
  2. 要求奇数号哲学家先拿左边,再拿右边筷子,偶数号哲学家则相反
  3. 使用互斥信号量
// 信号量定义
semaphore chopsticks[5] = 1, 1, 1, 1, 1;
semaphore mutex = 1;

Pi() 
    while (1) 
        P(mutex);
        P(chopsticks[i]);				// 拿左
        P(chopsticks[(i + 1) % 5]);		// 拿右
        V(mutex);
        
        吃饭;
        
        V(chopsticks[i]);				// 放左
        V(chopsticks[(i + 1) % 5]);		// 放右
        思考;
    

2.20 管程

  1. 为什么要引入管程:解决信号量机制编程麻烦、易出错的问题

  2. 管程是一种特殊的软件模块(类比 ),由以下部分组成:

    • 局部于管程的共享数据结构说明(类字段
    • 对该数据结构进行操作的一组过程(类方法
    • 对局部于管程的共享数据设置初始值的语句(构造器、代码块
    • 管程有一个名字(类名
  3. 管程的基本特征:

    • 局部于管程的数据只能被局部于管程的过程所访问(封装)
    • 一个进程只有通过调用管程内的过程才能进入管程访问共享数据(通过类提供的方法访问类字段)
    • 每次仅允许一个进程在管程内执行某个内部过程(互斥)
  4. 管程的补充说明: