Operating_System_Chapter-4_Fall_2011

Operating_System_Chapter-4_Fall_2011 - 第4章 进程管理...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 第4章 进程管理 4.1 进程的概念 4.2 进程的结构 4.3 进程控制 4.4 进程的同步与互斥 4.5 进程间通信 4.6 死锁 4.7 线程 IE310, 1-1, Feb. 18th, 2008, IE310.2008.sjtu@gmail.com Shanghai Jiao Tong University 4.1 进程的概念 现代操作系统最主要的特点在于实现多道 程序并发执行,并由此引发资源共享。 为了对并发执行的程序进行动态描述而引 入了进程的概念。 进程是操作系统最核心的概念。 操作系统对于资源的分配和管理都是围绕 进程进行的。 在多道程序并发执行的环境下,操作系统 面临哪些与单道程序执行环境不同、且又 必须解决的问题呢? IE310, 4-2, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1 进程的概念 4.1.1 顺序程序与并发程序 顺序程序 是指严格按照写入的顺序执行的程序。 单道系统中顺序程序执行时具有以下特征: 顺序性-?流畅性? 顺序执行各指令,直至程序结束;期间遇跳转或循环指令, 则相应跳转或循环 封闭性-程序运行环境相对封闭,不受其它程序和外界因素 的干扰 可再现性-只要程序执行的环境和初始条件相同,对于同一 程序的多次执行在机器内部的动作系列完全相同,最后获得 的结果也相同。 原因:期间独占系统资源。 IE310, 4-3, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.1 进程的概念(顺序程序与并发程序) 4.1.1 顺序程序与并发程序 并发程序 并发程序是指两道或两道以上程序同时被装入内存中 运行,这些程序的执行在时间上互相有重叠,即在一 个程序执行结束之前,另一个程序已经开始执行。 但是,在任一时刻只有一个程序在CPU上运行,而这 些程序运行的次序不是事先确定的。 并发程序具有以下与顺序程序不同的特征: 间断性-程序在执行期间走走停停(由于进程调度) 失去封闭性-与其它程序一起共享有关软硬件资源(比如多进 程抢占打印机),环境受到其它程序的影响 不可再现性-程序的执行过程难以完全再现 IE310, 4-4, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.1 进程的概念(顺序程序与并发程序) 程序并发的本质思想* 当一个程序需要等待输入/输出完成时,就让 该程序退出使用CPU,而将另一个程序投入 CPU上运行。 当等待的I/O操作完成时,硬件将向系统发出 一个中断请求信号,系统在响应该中断信号 后,把等待中的程序重新调度运行。 这样,在一个程序等待I/O完成期间,CPU并 不会发生空闲,从而提高了处理机的利用率。 IE310, 4-5, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.1 进程的概念(顺序程序与并发程序) 引入进程的原因总结 在多道程序工作环境下,一个程序活动不再能独占系 统资源,因此也就不再能单独决定这些资源的状态。 程序和计算机执行程序的活动(过程)之间也不再有一 一对应关系。 总之,程序活动不再处于一个封闭的系统中,而是和 其它程序活动之间存在着相互依赖和制约的关系,因 而呈现出并发、动态以及相互制约这些新的特征。 在这种情况下,程序这个静态的概念已经不能如实地 反映多道系统中程序的并发活动,故引入了进程的概 念来描述系统和用户的程序活动。 IE310, 4-6, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.1 进程的概念(顺序程序与并发程序) 程序并发执行的描述语句 cobegin P1; P2; P3; …; Pi; …; Pn; coend 被cobegin和coend括起来的多个程序是并发 执行的。 这n个并发程序的执行是非顺序的、由操作系 统进行调度的、具有随机性的执行过程,可 以是其中任意一个进程Pi先执行。 IE310, 4-7, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1 进程的概念 4.1.2 进程的定义与特性 从理论角度看,进程是对正在运行的程序过 程的抽象 从实现角度看,进程则是一种数据结构(进程 控制块等),目的在于清晰地刻画动态系统内 在的规律,有效管理和调度进入计算机主存 储器运行的程序 IE310, 4-8, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.2 进程的概念(进程的定义与特性) 定义1: 进程是指可并发执行的程序在一个数据 集合上的一次运行过程。 定义2: 进程是一个具有独立功能的程序关于某 个数据集合的一次运行活动,是操作系 统进行调度和资源分配的基本单位。 进程描述了程序动态执行的过程,当程 序投入运行时创建进程,当程序中止时 进程消亡,因而进程具有生命周期。 IE310, 4-9, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.2 进程的概念(进程的定义与特性) 进程的特征: 动态性:进程是程序执行的过程。 并发性:进程使程序能并发执行。 制约性:进程之间既相互影响又相互制约,涉及资源 的共享和互斥使用,以及进程间的同步和通信。 独立性:进程是独立运行的基本单位,也是系统资源 分配与调度的基本单位。 结构性:进程包括可执行的程序代码、程序的数据和 堆栈、程序计数器、堆栈指针、有关寄存器以及运行 程序所必须的所有其它信息。 异步性:进程以各自独立的、不可预知的速度向前推 进。 IE310, 4-10, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.2 进程的概念(进程的定义与特性) 进程与程序的关系—课程vs教材: 动态性和静态性。进程是动态的(动态实体、执行 过程),程序是静态的(静态实体、指令集合) 。 临时性和永久性。进程是临时的,程序是永久的; 进程是一个状态变化的过程,而程序可作为文件 长久保存。 组成上的不同。进程的组成包括程序、数据和进 程控制块(PCB)。 多对一或者一对多的关系。通过多次执行,一个 程序可对应多个进程(一本教材被多个班级所使 用);一个进程也可能由多个程序构成(一门课程 使用了多本教材) 。 IE310, 4-11, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.2 进程的概念(进程的定义与特性) 引入进程的代价: 空间上的开销:为了管理进程,需要建立相 应的数据结构(如进程控制块)及控制机制, 而这些数据结构和控制机制需要占用一定的 内存空间。 时间上的开销:为了完成进程的调度与切换, 需要不断跟踪进程的运行过程和进行工作切 换,而这些需要花费一定的CPU时间。 IE310, 4-12, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1 进程的概念 4.1.3 进程的状态及其转换 进程的基本状态 就绪态(Ready) 进程已经具备了运行 (除处理机之外)的所有条件,一旦分配 到CPU就立即可以执行并转入运行态。 当前,CPU正被其他进程占用,因此暂时不能执行。 系统中处于就绪状态的进程可能会有多个,通常将这些进程 (按照优先级顺序)组织成一个队列,称为就绪队列。 一旦CPU空闲或者时间片到,从就绪队列中选择一个进程执 行。 进程的映像可能在内存,也可能(几乎全部/比如Unix下除了 必须常驻内存的Proc结构)不在内存而在交换区。 Unix:进程映像在内存的就绪状态称为内存就绪,不在内存 则称为就绪且换出。 IE310, 4-13, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 运行态(Running) 进程已获得CPU,正在处理机上运行的状态。 在单处理机系统中,处于运行状态的进程只有一个。 在多处理机系统中,可能会有多个进程处于运行状态。 根据处理机当前能否执行特权指令,运行态还可以被进一步 分为核心态(运行)和用户态(运行)。 在没有其它进程可以执行时(如所有进程都在阻塞状态),通 常会自动执行系统的Idle进程(相当于空操作并能降低CPU功 耗)。 运行态进程的映像已被载入内存(较大部分/或者仅当前需要 执行的代码),CPU当前的地址映射机构正指向该进程。 IE310, 4-14, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 阻塞态(Blocked) 也称为等待态(Wait)或睡眠态(Sleep)。 指原来正在执行的进程由于等待某些事件(如等待I/O操作完 成、等待缓冲区的释放等)的发生而处于暂停执行的状态。 进程这时已经放弃了CPU。 处于等待状态的进程,只有当所等待的事件发生后,才有可 能被继续调度并执行。(同步调用方式,比如张三同学非要 等拿到了父母给买的电脑之后才继续学习) 系统通常根据进程阻塞等待的不同原因,把进程组织成多个 队列,称为阻塞队列。 睡眠态进程的映像可能在内存,也可能不在内存。 Unix:在内存则称为内存睡眠,不在内存则称为睡眠且换出。 Unix:处于睡眠且换出的进程映像是不能换入内存的,只有 处于就绪且换出的进程映像才能在内存空闲时换入内存。即, 睡眠态的进程一旦换出,则只有唤醒后才能换入。 IE310, 4-15, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 其它状态-创建态 操作系统正在为该新进程分配必要的资源; 操作系统正在为该新进程创建必要的管理信息; 前述工作尚未完成。 IE310, 4-16, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 其它状态-终止态 一个进程正常执行完毕之后进入该状态。 一个进程出现了无法克服的错误而被操作系统所 终结时,也进入该状态。 进程进入该状态之后,首先等待操作系统进行善 后处理,比如等待其父进程读取该进程的退出状 态 然后等待操作系统释放该进程所占用的各项资源, 更新进程管理信息使用的数据结构 IE310, 4-17, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 其它状态-挂起态 终端用户的请求,比如程序调试。 父进程的请求,以便考查、修改或协调子进程的 活动。 负荷调节的需要。当实时系统中的工作负荷较重, 或者可能影响到对实时任务的控制时,系统可能 把一些不重要的进程挂起,以保证系统的正常运 行。 操作系统的需要。操作系统有时希望挂起某些进 程,以便检查运行中的资源使用情况或者进行记 账。 IE310, 4-18, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的基本状态 Unix中的几种进程状态 创建态 就绪态 运行态 核心运行态-系统调用尚未返回且正在运行 用户运行态-正在执行用户程序提供的代码 睡眠态 内存中就绪态 就绪且换出态-进程映像当前不在物理内存 (内存中)睡眠态-正在进行系统调用且等待其完成 睡眠且换出态-进程映像当前不在物理内存 僵死态-子进程执行了终止执行的系统调用exit()后,操作系 统对其进行善后处理,等待父进程读取该进程的退出状态。 在僵死状态下,进程不能进行任何状态的转换,其资源除了 proc结构以外已全部释放。当proc结构也释放后,该子进程 才算完全撤销。 IE310, 4-19, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的状态转换 就绪态->运行态 运行态->就绪态 时间片到 运行态->阻塞态 所有就绪态的进程竞争CPU,当进程获得了CPU,其就绪 态就转换为运行态。 由操作系统的进程调度程序来决定哪一个就绪态进程能够 获得CPU,用户进程自己无权实施进程调度。 提出I/O请求 等待其它进程发来消息但是还未到来 所请求资源尚未获得满足 阻塞态->就绪态 所等待的条件获得满足 IE310, 4-20, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的状态转换 IE310, 4-21, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) 进程的状态转换 IE310, 4-22, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.1.3 进程的概念(进程的状态及其转换) Unix进程状态转换 IE310, 4-23, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.2 进程的结构 4.2.1 进程的实体 进程的物理实体又称为进程的静态描述。 进程的静态描述由三部分组成: 程序:描述了进程所要 完成的功能; 数据集合:进程运行所 需要的私有数据和堆栈 工作区(系统堆栈和用 户堆栈); 进程控制块(PCB):进 程描述块。 进程描述信息 进程控制信息 资源管理信息 进程控制块 (PCB) 现场保护信息 堆栈 私有数据 私有程序 共享程序空间 数据集合 程序 (可执行代码) IE310, 4-24, xqfang.sjtu@gmail.com 4.2.1 进程的结构(进程的实体) Shanghai Jiao Tong University 进程控制块(PCB)的内容 为了对进程在执行过程中的动态信息进行控制和管理, 操作系统为每个进程建立一个数据结构——进程控制 块,简称PCB(Process Control Block)。 进程控制块是进程的动态部分,对程序员来说是透明、 不可见的,由操作系统建立和管理。 它包含了进程的描述信息、控制信息和资源信息,是 进程动态特性的集中反映。 PCB是进程存在的唯一标志,有生命周期,在创建进 程时产生,撤销进程时消亡。 PCB主要包括如下信息: 进程描述信息 进程控制信息 资源管理信息 处理机现场保护信息 IE310, 4-25, xqfang.sjtu@gmail.com 4.2.1 进程的结构(进程的实体) Shanghai Jiao Tong University 进程控制块(PCB)的内容 进程描述信息 进程名-进程的外部标识符,通常基于可执行文件 名(不唯一/多个进程可能有一样的进程名) 进程标识符(Process ID,PID)-进程的内部标识符, 在操作系统范围内唯一,通常是一个正整数 用户标识符(User ID,UID) 进程组关系 IE310, 4-26, xqfang.sjtu@gmail.com 4.2.1 进程的结构(进程的实体) Shanghai Jiao Tong University 进程控制块(PCB)的内容 进程控制信息 进程状态,指明进程的当前状态。 进程优先级,用于描述进程使用处理机的优先级别的一个整 数,优先级高的进程应优先获得处理机。 程序起始地址,规定该进程的程序以此地址开始执行。 各种计时信息,给出进程占有和利用资源的有关情况。 进程同步和通信机制,指实现进程同步和进程通信时必需的 机制,如消息队列指针、信号量等与别的进程所发生的信息 交换情况。 链接指针,它给出了本进程(PCB)所在队列中的下一个进程 的PCB的首地址。 IE310, 4-27, xqfang.sjtu@gmail.com 4.2.1 进程的结构(进程的实体) Shanghai Jiao Tong University 进程控制块(PCB)的内容 资源管理信息 占用内存大小及其管理用数据结构指针,如进程页表指针等。 在某些复杂系统中,还有交换或覆盖用的有关信息,如交换 程序段长度、交换外存地址等。这些信息在进程申请、释放 内存时使用。 共享程序段大小及起始地址。 输入输出设备的设备号,所要传送的数据长度、缓冲区地址、 缓冲区长度及所用设备的有关数据结构指针等。进程在申请、 释放设备进行数据传输时将使用这些信息。 指向文件系统的指针及有关标识等。进程可使用这些信息对 文件系统进行操作。 IE310, 4-28, xqfang.sjtu@gmail.com 4.2.1 进程的结构(进程的实体) Shanghai Jiao Tong University 进程控制块(PCB)的内容 处理机现场保护信息 通用寄存器 程序计数器PC-指向下一条指令的地址 程序状态字PSW-条件码、中断屏蔽标志等 栈指针寄存器 IE310, 4-29, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.2 进程的结构 *4.2.1.a 进程上下文* 进程上下文的引入 进程上下文实际上是进程执行过程中顺序关 联的静态描述。 进程上下文是一个与进程切换和处理机状态 发生交换有关的概念。 在进程执行过程中,由于中断、等待或程序 出错等原因造成进程调度,这时操作系统需 要知道和记忆进程已经执行到什么地方或新 的进程将从何处执行。 IE310, 4-30, xqfang.sjtu@gmail.com 4.2.1.a 进程的结构(*进程上下文*) Shanghai Jiao Tong University *4.2.1.a 进程上下文* 进程上下文的一种含义 进程上下文是一个抽象的概念,它包含了每个进程执 行过的、正在执行的以及待执行的指令和数据,在指 令寄存器、堆栈(存放各调用子程序的返回点和参数 等)、状态字寄存器等中的内容。 把已执行过的进程指令和数据在相关寄存器与堆栈中 的内容称为上文。 把正在执行的指令和数据在寄存器与堆栈中的内容称 为正文。 把待执行的指令和数据在寄存器与堆栈中的内容称为 下文。 也有资料称切换前的运行进程为上文,切换后 的运行进程为下文。 IE310, 4-31, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.2 进程的结构 4.2.2 进程队列 PCB的组织 操作系统对于进程的管理,很大程度上基于 它们的进程控制块。为了方便对进程控制块 (PCB)的查找,操作系统通常采用以下方式来 组织PCB: 链接 索引表 IE310, 4-32, xqfang.sjtu@gmail.com 4.2.2 进程的结构(进程队列) Shanghai Jiao Tong University PCB的组织 链接方式管理的PCB(地下工作中的单线联系): IE310, 4-33, xqfang.sjtu@gmail.com 4.2.2 进程的结构(进程队列) Shanghai Jiao Tong University PCB的组织之链接方式 把处于同一状态的进程PCB用链接的方式连成一个进程队列。 多种状态对应多个进程队列:就绪队列、阻塞队列、运行队列 和空闲队列。 处于就绪队列中的进程往往按照先来先到、优先级高低或其它 原则排列。 对于阻塞状态的进程,往往根据其阻塞原因的不同,形成若干 队列,如等待打印机队列、等待网络数据到达队列、等待磁盘 写队列等。 运行队列仅适用于多处理机系统,因为在单处理机系统中,处 于运行态的进程总是只有一个。 空闲队列中存放的是系统中的所有未被使用的PCB,在创建新 进程时,就需要为之分配一个空闲的PCB。 struct sPCB { … struct sPCB * pNext; }; struct sPCB * pReadyQueueFirst = NULL; struct sPCB * pBlockingQueueFirst = NULL; IE310, 4-34, xqfang.sjtu@gmail.com 4.2.2 进程的结构(进程队列) Shanghai Jiao Tong University PCB的组织之索引表方式 索引表方式管理的PCB(党团员花名册): 索引表 PCB表 就绪队列 等待队列 1 等待队列 2 PCB 1 PCB 2 PCB 3 PCB 4 PCB 5 PCB 6 PCB 7 … PCB n IE310, 4-35, xqfang.sjtu@gmail.com 4.2.2 进程的结构(进程队列) Shanghai Jiao Tong University PCB的组织之索引表方式 为相同状态的进程建立一张索引表,如就绪索引表、阻塞索引 表等。 各索引表项存放 相应PCB项在PCB表中的索引号。 或者相应PCB项的地址。 对于阻塞状态的进程,同样可以根据其阻塞原因的不同,形成 若干个阻塞索引表,如等待打印机进程索引表、等待网络数据 到达进程索引表、等待磁盘写进程索引表等。 struct sPCB { bool bUsed; … }; struct sPCB PCB_Table[NPROC]; int nReady_Num = 0, nBlocking_Num = 0; struct sPCB * Ready_Table[NPROC], * Blocking_Table[NPROC]; IE310, 4-36, xqfang.sjtu@gmail.com 4.2 进程的结构 Shanghai Jiao Tong University 4.2.5 Unix进程映像 在Unix中,进程映像由3个部分组成:proc结构、正文 段和数据段: U nix进 程 映 像 PR O C结 构 正 文 段 用户栈 数 据 段 用户数据区 U 区 核心栈 user结 构 IE310, 4-37, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University Unix的正文段 Unix的数据段 正文段中存放程序代码中可以共享的部分 这样,被多个进程共享的正文段在系统中将只需要一个副本 正文段中存放程序代码中(相对于进程而言)的私有部分 Unix的PCB PCB中包含了大量的信息,一个PCB数据结构需要占用较大的 存储空间,一般为几百到几千字节。 Unix将PCB分为2个部分:proc结构和user结构。 proc结构是进程控制块中常驻内存部分,存放进程最基本的 信息,也是进程的唯一标识; user结构中主要包含那些只有进程执行时才会被使用的信息, 因此作为非常驻内存部分,存放在该进程的数据段中。 Unix这样处理PCB是为了节省内存空间的开销。当进程被操 作系统调度执行时,其正文段和数据段会载入内存,进程的 user结构也随之进入内存。 IE310, 4-38, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University Unix进程映像的组成 proc结构——长驻内存 正文段——存放在磁盘上,执行时调到内存 数据段——存放在磁盘上,执行时调到内存 Unix进程控制块PCB的组成 proc结构——长驻内存 user结构——在数据段内,执行时调到内存 IE310, 4-39, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University 进程基本控制块——proc结构 struct proc { char p_stat; /*进程状态*/ char p_flag; /*进程特征*/ char p_pri; /*进程优先数*/ char p_sig; /*软中断号*/ char p_uid; /*用户号*/ char p_time; /*驻留时间*/ char p_cpu; /*占用CPU时间*/ char p_nice; /*计算优先数时用*/ int p_ttyp; /*控制终端tty结构的地址*/ int p_pid; /*进程号*/ int p_ppid; /*父进程号*/ int p_addr; /*数据段地址,由此可以找到user结构*/ int p_size; /*数据段大小*/ int p_wchan; /*等待原因*/ int p_textp; /*正文段所在的text表项的地址*/ } proc[NPROC]; NPROC为系统允许的最多进程个数。 IE310, 4-40, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University 进程基本控制块——user结构 struct user { int u_rsav[2]; char u_segflag; char u_error; char u_uid; char u_gid; int u_procp; char *u_base; char u_count; char u_offset[2]; int *u_cdir; char *u_dirp; struct { int u_ino; char u_name[DIRSIZ]; } u_dent; int u_ofile[NOFILE]; int u_arg[5]; int u_tsize; int u_dsize; int u_ssize; int u_utime; int u_stime; int u_cutime; int u_cstime; int *u_ar0; …… } u; /*保留现场保护区指针*/ /*用户/核心空间标志*/ /*返回出错代码*/ /*有效用户号*/ /*有效组号*/ /*proc结构地址,与proc结构链接*/ /*内存地址*/ /*传送字节数*/ /*文件读写移*/ /*当前目录i节点地址*/ /*i节点当前指针*/ /*当前目录项*/ /*用户打开文件表,NOFILE默认为15*/ /*保存系统调用的自变量*/ /*正文段大小*/ /*用户数据区大小*/ /*用户栈大小*/ /*用户态执行时间*/ /*核心态执行时间*/ /*子进程用户态执行时间*/ /*子进程核心态执行时间*/ /*当前中断保护区内r0的地址*/ IE310, 4-41, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University 共享正文段和正文表text 为了实现正文段的共享,操作系统建立了一个正文表text, 以对所有进程的正文段进行集中管理. 正文表中存放所有进程的所有正文段的内存地址和外存地址 以及其它信息。 正文表中的每一项描述一个正文段。 一个正文段可能被两个以上的进程所引用。 其结构定义为: struct text { int x_daddr; int x_caddr; int x_size; int x_iptr; int x_count; int x_ccount; } text[NTEXT]; /*磁盘地址/加载源*/ /*内存地址/已在内存的标志*/ /*内存块数*/ /*文件内存i节点地址*/ /*共享进程数*/ /*内存副本的共享进程数*/ IE310, 4-42, xqfang.sjtu@gmail.com 4.2.5 进程的结构(Unix进程映像) Shanghai Jiao Tong University Unix进程映像各个部分之间的链接关系 p roc表 … … … … p _ te x tp p_addr 内 存 te x t表 x_ caddr x_daddr … … … … 共 享 的 正 文 段 复 制 到 内 存 用 户 栈 外 存 用 户 数 据 区 共 享 的 正 文 段 核 心 栈 u ser u _ p rocp IE310, 4-43, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.1 进程控制原语 进程控制是操作系统对进程进行管理所提供的控制操 作。 进程控制至少应该包括进程创建、进程撤销、进程睡 眠、进程唤醒等操作,?它们都使用原语实现?。 所谓原语是指在执行过程中?不允许中断? , ?不允许 并发执行? ,它?属于操作系统内核的一部分? ,以? 系统调用的形式?提供给用户和操作系统使用。 原语具有操作的原子性,整个操作过程不可分割,要 么全做,要么全不做。 区别:服务原语(Service Primitive),如网络数据发送。 IE310, 4-44, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.2 进程的创建 进程创建的时机 由于执行任务的需要,比如启动运行一个新的程序, 一个进程往往通过创建一个新的进程去完成该新的任 务。 用户登录,系统启动一个shell程序 作业调度,为批处理的特定作业创建进程 提供服务,系统为当前进程启动一个独立进程为其提供打印 机对话等服务 应用请求,应用进程自己创建新进程,以便新进程能以并发 运行方式完成特定任务。如,启动键盘输入进程处理键盘终 端的数据输入,启动表格输出进程将处理结果以表格形式在 屏幕上显示等。 IE310, 4-45, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University 进程关系 被创建的进程被称为子进程,负责创建的那 个进程被称为父进程。 子进程可以进而创建它自己的子进程,从而 可以形成进程的层次(家族)体系,即进程树。 因此,系统中的所有进程都来自同一个”祖先” 进程。 IE310, 4-46, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University 进程树示意 IE310, 4-47, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University 进程的创建过程如下: 申请空白PCB 创建一个进程实际上是创建一个PCB。创建一个新进程时,首先 要查找PCB表,从中分配一个空闲的PCB,并为新进程分配一个 唯一的进程标识符PID。 为新进程分配资源 为新进程的程序段、数据段和用户堆栈分配必要的内存空间 以及其他各种资源。然后,从外存中把程序段和数据段装入到 分配给该进程的内存地址空间中。 初始化PCB 根据系统或父进程提供的参数以及分配到的资源情况,初始化 申请到的PCB表项,即给PCB中的各项信息赋予初值。 初始化标识信息-将系统分配的标识符和父进程标识符填入新PCB中 初始化处理机状态信息-使程序计数器指向程序的入口地址,使栈 指针指向栈顶 初始化处理机控制信息-将进程的状态设置为就绪状态,对于优先 级,通常是将它设置为最低优先级,除非用户以显式方式提出高优 先级要求 将新进程放入就绪队列,将已初始化的PCB加入到就绪队列中。 IE310, 4-48, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University Unix进程的创建 Unix父进程创建一个子进程后,系统为该子进程分配一个独立 的proc结构; 然后将父进程的正文段指针复制给子进程proc结构中的正文段 指针; 为子进程分配一个数据段,将父进程数据段复制到子进程的数 据段中。 父 进 程 p r o c p _ p id p _ a d d r p _ te x tp … 子 数 据 段 复 制 … 数 据 段 进 程 p r o c p _ p id p _ a d d r p _ te x tp … … te x t表 … … p _ d a d d r 共 享 的 正 文 段 IE310, 4-49, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University Unix进程的创建 子进程将被赋予一个新的唯一的进程标识符pid,父进程标志 符、数据段地址、执行时间等proc结构和user结构中的部分字 段将被重新设置。 子进程将继承父进程创建时刻所拥有的全部资源,包括所有已 经打开的文件、程序(可执行代码)、组代码、环境变量、工作 目录以及资源限制等。 当子进程创建成功后,系统中就会增加一个进程参与竞争CPU。 父子进程中的哪一个先被调度执行,是随机的。 当操作系统调度到子进程时,会执行与父进程一样的程序。 对于所执行的程序,父子进程之间的!!!唯一区别!!!在于” fork() 系统调用”的返回值。 程序代码仅能根据” fork()系统调用” 返回值的不同来唯一确定 当前究竟是运行在父进程环境还是子进程环境。 变量存在于父子映像各自的数据段存储空间,父子进程对变量 进行修改这样的行为,不会传递到对方进程,任何一方都无法 直接感知对方的行为。 IE310, 4-50, xqfang.sjtu@gmail.com 4.3.2 进程控制(进程的创建) Shanghai Jiao Tong University Linux中终端用户进程的创建与调度执行 Linux启动时首先创建0#进程init_task。 所有其它进程都是通过调用fork()创建。 0#进程调用fork()创建1#进程。 1#进程使用fork()为每个可用于用户登录的通信端口创建一个 进程为用户服务,如等待用户登录、执行shell命令解释程序等。 当用户开始使用某端口时,系统进程创建一个login进程来接收 用户标识和口令,并通过系统文件/etc/passwd中的信息核对 该用户的身份。 如果登录成功,login进程将改变当前目录至用户主目录,并执 行指定的shell程序,使得用户能够通过shell界面直接与login系 统进程交互。 当用户在键盘上键入一个用户可执行文件名时,操作系统就为 该文件创建一个相应的用户进程并投入运行。 IE310, 4-51, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.3 进程的撤销 当一个进程完成了特定的工作,或者由于某种错误非正常终止时, 都需要撤销该进程。 正常结束-进程正常运行完毕 异常结束-运行期间出现某些错误和故障而迫使进程终止 越界错误-程序所访问的存储区已越出该进程的区 保护错-进程试图访问一个不允许访问的资源或文件 非法指令-程序试图去执行一条不存在的指令,比如错误地转移到数据区执 行,把数据当成了指令 特权指令错-用户进程试图直接执行只允许操作系统代码执行的特权指令 算术运算错-试图执行一个被禁止的运算,比如被0除 I/O故障-指在I/O过程中发生了错误等 运行超时-进程的执行时间超过了指定的最大值 等待超时-进程等待某事件的时间,超过了规定的最大值 外界干预-进程应外界的请求而终止运行 操作员或操作系统干预,如发现死锁后的人为解除 父进程请求-父进程具有终止自己的任何子孙进程的权力 父进程终止-父进程终止时,操作系统可能将它的所有子孙进程终止;当然, 现代操作系统一般将它们的父进程置为1(init系统进程) IE310, 4-52, xqfang.sjtu@gmail.com 4.3.3 进程控制(进程的撤销) Shanghai Jiao Tong University 进程的撤销过程 撤销过程需要根据被撤销进程的标识符PID,从PCB 集合中找到该进程对应的PCB。 从PCB中读出该进程的状态。 若被终止进程正处于执行状态,应立即终止该进程的 执行,并置调度标志为真,用于指示该进程被终止后 应重新进行调度。 若该进程还有子孙进程,还应将其所有子孙进程予以 终止,以防它们成为不可控的进程;或者,设置它们 的父进程为1(init系统进程)。 将被终止进程所拥有的全部资源释放,并回收对应的 PCB结构。 IE310, 4-53, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.4 进程的阻塞 处于运行状态的进程,如果要等待某事件的发生, 如等待资源、等待I/O完成等,无法继续执行, 这时进程的相关系统调用将阻塞进程自身,从而 进入阻塞状态。比如: 请求系统资源而又尚未被满足,比如请求使用打印机, 但是正被其它进程所占用。 启动某种I/O操作(而又尚未完成),进程需要等待该 I/O操作的完成,届时由中断处理程序或进程唤醒。 同步或互斥条件尚未满足,比如新数据尚未到达,来 自另一个合作进程的需要处理的数据尚未到达。 请求延时,暂时没有新工作可做,某些守护进程当前 尚未获得工作任务。 IE310, 4-54, xqfang.sjtu@gmail.com 4.3.4 进程控制(进程的阻塞) Shanghai Jiao Tong University 进入阻塞状态的具体操作过程如下: 停止进程在处理机上的执行,把CPU现场信息 保存到该进程的PCB的相应表目中。 修改PCB的有关表目内容,如把进程状态由运 行状态改为阻塞状态。 根据阻塞原因,把修改后的PCB放入相应的阻 塞队列中。 转入进程调度程序,从就绪队列中调度一个 新的进程投入运行。 IE310, 4-55, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.5 进程的唤醒 唤醒进程的操作一般不会单独提供给用户使用, 而是包含在操作系统所提供的可供用户调用的同 步机制中,比如mutex_unlock()。 每当特定的事件发生之后,操作系统将检查该事 件的等待队列上是否有等待进程。 如果该事件的等待队列上有等待进程,系统将唤 醒相应的阻塞进程,将其转换为就绪状态: 把PCB从相应的等待队列中移出。 修改PCB的有关表目内容,如把进程状态由阻塞状态 改为就绪状态。 将修改后的PCB放回到就绪队列中,等待调度。 IE310, 4-56, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3 进程控制 4.3.6 Unix下的进程控制 Unix下的进程创建 类Unix操作系统下,进程的创建一般通过调 用fork()系统调用完成。 如果执行出错失败,返回值将是-1。 否则,在父进程中返回子进程的PID,也就 必定大于0;在子进程中返回0。 子进程一般通过调用getpid()系统调用取得自 己的PID。 进程通过调用getppid()系统调用来取得自己 的父进程的PID。 IE310, 4-57, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的进程创建 格式:int fork(); 返回值: =0: 创建成功,从子进程返回; >0: 创建成功,从父进程返回,其值为子进程的PID号; =-1:创建失败。 子进程继承父进程打开的所有文件及资源, 对父进程的当前目录和所有已打开系统文件 表项中的引用记数加1。 IE310, 4-58, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的进程创建(孙悟空) nChildPID = fork(); if (-1 == nChildPID ) { /* 创建失败 */ printf("进程创建失败.\n"); } else if (nChildPID > 0) { /* 是父进程中的返回 */ printf("F: 这是父进程.\n"); } } else { /* 是子进程中的返回 */ printf(" C: 这是子进程.\n"); } nChildPID,… nChildPID,… int main(int argc, char ** argv) { pid_t nChildPID; pid_t nChildPID; IE310, 4-59, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的进程退出 进程一般通过调用exit(int nStatus)系统调用实现进程 的主动退出。 格式:void exit(int nStatus); 对该函数的调用不会返回。 向父进程发送子进程退出的软中断信号SIGCHLD,终 止信息 (nStatus & 0xFF)将被返回给父进程,作为子 进程的退出状态。 父进程一般通过调用waitpid(pid_t nPID,int * pnStatus, int nOptions)系统调用获得特定子进程的 退出状态(((* pnStatus) >> 8) & 0xFF)。 IE310, 4-60, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的进程睡眠 父进程等待子进程终止的系统调用wait()。 进程主动睡眠(延时)系统调用sleep()。 格式:sleep(n); 其中:n表示延时的秒数。 功能:进程睡眠n秒。 进程主动睡眠(延时)系统调用usleep(),参数 为需要睡眠的微秒数。 IE310, 4-61, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的连续2个fork()的进程家族树 #include <stdio.h> int main(int argc, char ** argv) { fork( );//父进程1创建子进程2 fork( );//父进程1再次创建子进程3 //子进程2创建其子进程4 putchar(’A’);//每个进程都要输出’ A’ } 查看该程序被执行了几次,共 有多少个“A”输出,说明有几 个进程产生。 运行后输出了4个‘A’字符, 这表示程序运行后一共有4个 进程处于并发之中。 1 2 3 4 IE310, 4-62, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) 对本页暂不要求:Unix下进程的创建与退 出调用示意 #include<stdio.h> int main(int argc, char ** argv) { int p1; putchar('x'); //父子共享部分,都要输出'x' while ((p1=fork())==-1); if (p1==0) putchar('b'); else putchar('a'); putchar('y'); //子进程输出'b' //父进程输出'a' //父子共享部分,都要输出'y' } 运行后输出的结果可能是xayxby或xbyxay之中的任意一个 。 IE310, 4-63, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char ** argv) { pid_t nFatherPID = -1, nChildPID; int nToReturnStatus = -1, nReturnedStatus = -1; Unix下进程的创建与 退出调用使用示意 (源程序代码forkproc.c) } nChildPID = fork(); if (-1 == nChildPID ) { /* 创建失败 */ printf("进程创建失败.\n"); } else if (nChildPID > 0) { printf("F: 父进程:nFatherPID(%p)=%d;nChildPID(%p)=%d.\n",&nFatherPID,nFatherPID,&nChildPID,nChildPID); printf("F: fork()返子进程的PID: nChildPID(%p) = %d.\n", & nChildPID, nChildPID); nFatherPID = getpid();printf("F: 父进程的PID: nFatherPID(%p) = %d.\n", & nFatherPID, nFatherPID); printf("F: 父进程即将调用waitpid().\n"); waitpid(nChildPID, & nReturnedStatus, 0); printf("F: nReturnedStatus(%p)=%08XH;(nReturnedStatus>>8)&0xFF=%d;但nToReturnStatus(%p)=%08XH.\n", & nReturnedStatus, nReturnedStatus, (nReturnedStatus>>8)&0xFF, & nToReturnStatus, nToReturnStatus); } else { printf(" C: 子进程:nFatherPID(%p)=%d;nChildPID(%p)=%d.\n",&nFatherPID,nFatherPID,&nChildPID,nChildPID); nChildPID = getpid(); printf(" C: 子进程getpid()发现自己的PID: nChildPID(%p) = %d.\n", & nChildPID, nChildPID); nFatherPID = getppid(); printf(" C: 子进程发现自己的父进程的PID: nFatherPID(%p) = %d.\n", & nFatherPID, nFatherPID); printf(" C: 子进程即将开始睡眠5秒钟.\n"); sleep(5); nToReturnStatus = 6; printf(" C: 子进程的5秒钟睡眠已经结束; nToReturnStatus(%p)=%08XH; 但nReturnedStatus(%p)=%08XH.\n", & nToReturnStatus, nToReturnStatus, & nReturnedStatus, nReturnedStatus); exit(nToReturnStatus & 0xFF); printf(" C: 警告!!! 子进程的本打印语句一般不应该被执行到!!!\n"); } IE310, 4-64, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下进程的创建与退出调用使用示意 编译连接forkproc.c之后的运行结果示意 或者使用make命令根据makefile进行编译连接 $ ps -ef命令,可用于查询系统范围内的进程清单 [fang@aloha mini_prog]$ gcc -o a forkproc.c [fang@aloha mini_prog]$ a C: 子进程:nFatherPID(0xbfffe5a4)=-1;nChildPID(0xbfffe5a0)=0. C: 子进程getpid()发现自己的PID: nChildPID(0xbfffe5a0) = 12818. C: 子进程发现自己的父进程的PID: nFatherPID(0xbfffe5a4) = 12817. C: 子进程即将开始睡眠5秒钟. F: 父进程:nFatherPID(0xbfffe5a4)=-1;nChildPID(0xbfffe5a0)=12818. F: fork()返回子进程的PID: nChildPID(0xbfffe5a0) = 12818. F: 父进程的PID: nFatherPID(0xbfffe5a4) = 12817. F: 父进程即将调用waitpid(). C: 子进程的5秒钟睡眠已经结束; nToReturnStatus(0xbfffe59c)=00000006H; 但 nReturnedStatus(0xbfffe598)=FFFFFFFFH. F: nReturnedStatus(0xbfffe598)=00000600H;(nReturnedStatus>>8)&0xFF=6;但 nToReturnStatus(0xbfffe59c)=FFFFFFFFH. [fang@aloha mini_prog]$ IE310, 4-65, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入 子进程刚被创建时,其程序内容与其父进程的是相同 的。 子进程如果需要与其父进程完全不同的程序代码,可 以调用exec系统调用。 根据格式的不同,可以有以下几种: execl()、execle()、execlp()、execv()、execve()、 execvp()。 其功能都是将指定的程序装入所调用进程的映像中, 用这个可执行文件的副本去覆盖该进程的程序空间, 从而改变调用进程的执行代码,使调用进程执行新引 入的可执行程序(二进制代码文件)。 IE310, 4-66, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入 格式1:给出指向参数表的指针 int execv(const char *path, char *const argv); char * path指向文件全名(路径名/文件名)的指针; char *argv 指向命令及参数的指针,分别为 argv[0]、…、argv[i]、…、argv[N],且NULL == argv[N]。 功能:执行参数指定的命令或文件,用该命令或可执 行文件的副本覆盖调用它的进程的映像。 返回值:-1表示错误返回,否则不返回。 IE310, 4-67, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入 格式2:给出指向参数表的指针 int execl(const char *path, const char *arg0, ...); char * path指向文件全名(路径名/文件名) 的指针; 参照前页: char * arg0指向argv[0],以’\0’结尾; char * argi指向argv[i],以’\0’结尾; char * argN指向argv[N],且NULL == argv[N]。 IE310, 4-68, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入execls.c gcc –o execls execls.c #include<unistd.h> #include<stdio.h> int main(int argc, char ** argv) { int p = -1; char * path="/bin/ls"; //定义文件路径参数 //定义命令参数 char * pExecArgv[4]={"ls", "-l", NULL}; printf("%s的p(%p)=%d, 将调用execv\n", argv[0], & p, p); execv(path, pExecArgv); //执行命令 printf("错误!!!本语句不该被执行!\n"); } IE310, 4-69, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入execls.c 编译execls.c之后的运行结果演示 [fang@aloha mini_prog]$ gcc -o execls execls.c [fang@aloha mini_prog]$ execls execls的p(0xbfffe00c)=-1, 马上将要调用execv 总用量 152 -rwxrwxr-x 1 fang fang 12267 8月 17 21:27 chldexec -rw-r--r-1 fang fang 550 2011-09-20 chldexec.c -rwxrwxr-x 1 fang fang 11904 8月 17 21:29 execls -rw-r--r-1 fang fang 335 2011-09-20 execls.c … [fang@aloha mini_prog]$ ./execls ./execls的p(0xbffff98c)=-1, 马上将要调用execv 总用量 152 -rwxrwxr-x 1 fang fang 12267 8月 17 21:27 chldexec -rw-r--r-1 fang fang 550 2011-09-20 chldexec.c -rwxrwxr-x 1 fang fang 11904 8月 17 21:29 execls -rw-r--r-1 fang fang 335 2011-09-20 execls.c … [fang@aloha mini_prog]$ IE310, 4-70, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的复杂的子进程映像的重新装入chldexec.c #include<unistd.h> #include<stdio.h> int main(int argc, char ** argv) { int p = 999; printf("%s的p(%p)=%d, 马上将要调用fork\n", argv[0], & p, p); while (-1 == (p=fork())); //创建子进程 if (p==0) { //子进程返回 printf("子进程开始睡眠3秒钟!\n"); sleep(3); printf("子进程已经醒来!\n"); printf("子进程即将开始执行execls!\n"); execl("./execls", "execls", NULL); //加载子进程的程序 printf("错误!!!本语句不该被执行!\n"); } else { //父进程返回 printf("父进程开始等待\n"); wait(0); //等待子进程终止 printf("父进程开始退出\n"); } } IE310, 4-71, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入chldexec.c 编译chldexec.c之后的运行结果演示之一 [fang@aloha mini_prog]$ gcc -o chldexec chldexec.c [fang@aloha mini_prog]$ chldexec chldexec的p(0xbffff294)=999, 马上将要调用fork 子进程开始睡眠3秒钟! 父进程开始等待 子进程已经醒来! 子进程即将开始执行execls! execls的p(0xbfffdb8c)=-1, 马上将要调用execv 总用量 152 -rwxrwxr-x 1 fang fang 12267 8月 17 21:35 chldexec -rw-r--r-- 1 fang fang 550 2011-09-20 chldexec.c -rwxrwxr-x 1 fang fang 11904 8月 17 21:29 execls -rw-r--r-- 1 fang fang 335 2011-09-20 execls.c … 父进程开始退出 [fang@aloha mini_prog]$ IE310, 4-72, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.3.6 进程控制(Unix下的进程控制) Unix下的子进程映像的重新装入chldexec.c 编译chldexec.c之后的运行结果演示之二 [fang@aloha mini_prog]$ ./chldexec ./chldexec的p(0xbffff294)=999, 马上将要调用fork 子进程开始睡眠3秒钟! 父进程开始等待 子进程已经醒来! 子进程即将开始执行execls! execls的p(0xbfffdb8c)=-1, 马上将要调用execv 总用量 152 -rwxrwxr-x 1 fang fang 12267 8月 17 21:35 chldexec -rw-r--r-- 1 fang fang 550 2011-09-20 chldexec.c -rwxrwxr-x 1 fang fang 11904 8月 17 21:29 execls -rw-r--r-- 1 fang fang 335 2011-09-20 execls.c … 父进程开始退出 [fang@aloha mini_prog]$ IE310, 4-73, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4 进程的同步与互斥 进程的同步与互斥,是指进程在运行期间相 互依赖和相互影响的关系。 进程间的合作,也以说是进程间的相互制 约。 为了保证进程的正确运行以及相互合作的进 程之间交换信息,需要进程之间的通信。 进程之间的制约关系体现为进程的同步和互 斥。 IE310, 4-74, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4 进程的同步与互斥 4.4.1 基本概念 进程同步:主要源于进程合作,是进程间共 同完成一项任务时形成的相互协作的关系。 进程间的同步要求这些进程在执行次序上是 相互协调的,即在执行时按一定的时序进行。 进程互斥:主要源于对资源(硬件资源和软件 资源)的竞争,是进程之间排它性使用资源的 关系。 IE310, 4-75, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.1 进程的同步与互斥(基本概念之资源竞争例子) P2() { int R1; P1() { int R1; { { } } 按订票要求找到X; R1 = X; if (R1 >= 1) { R1 = R1 – 1; X = R1; 打印输出一张票子; } else { 提示票已售完; } X } } 按订票要求找到X; R1 = X; if (R1 >= 1) { R1 = R1 – 1; X = R1; 打印输出一张票子; } else { 提示票已售完; } IE310, 4-76, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.1 进程的同步与互斥(基本概念) 4.4.1 基本概念 临界资源:一次只允许一个进程访问的资源, 如打印机、公共变量等。 临界区(段):包含访问临界资源的程序段称 为临界段或临界区。 IE310, 4-77, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.1 进程的同步与互斥(基本概念) (1965 Dijkstra)临界区应遵循的准则 1. 空闲则入。当没有进程在临界区时,允许一个 进程立即进入临界区。 2. 忙则等待。若已经有进程处于临界区,则其余 要求进入临界区的进程必须等待。 3. 有限等待。进程在临界区中的时间必须是有限 的。若有多个等待进程,则应能够在有限的时 间内让其中一个进入临界区,而不能让所有的 进程”死等”。 4. 让权等待。不能进入临界区的进程,应主动释 放CPU资源,如转换到阻塞状态。 IE310, 4-78, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4 进程的同步与互斥 4.4.2 简单的同步机制 同步机制是指用于保证多个进程在执行次 序上的协调关系的相应机制。 IE310, 4-79, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 单标志算法(轮流标志法) Pi() { while (1) { Pj() { while (1) { while (i != turn) usleep(1000); 临界区代码; while (j != turn) usleep(1000); turn 临界区代码; turn = j;/* 轮到j进程访问 */ turn = i;/* 轮到i进程访问 */ 其它代码; } } 其它代码; } } Turn:当前被允许进入临界区的进程号 IE310, 4-80, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.2 进程的同步与互斥(简单的同步机制) 单标志算法(轮流标志法)的特点 强制两个进程轮流进入临界区 会出现长耗时进程阻塞短耗时进程的现象 (若Pi进程刚从临界区出来,而此时Pj进程尚 未进入临界区,尽管此时临界区为空,当Pi想 再次进入时,却因turn标志为j而无法进入) 对于标志turn的判断与等待是一个轮询过程, 效率低下 IE310, 4-81, xqfang.sjtu@gmail.com Shanghai Jiao Tong University Pi() { while (1) { 双标志算法 Ti Pj() { while (1) { while (0 != Tj) usleep(1000); (XXXX) Ti = 1; 临界区代码; Ti = 0; 其它代码; } } while (0 != Ti) usleep(1000); (XXXX) Tj = 1; 临界区代码; Tj = 0; Tj 其它代码; } } Tx:进程x当前在临界区内的标志 IE310, 4-82, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.2 进程的同步与互斥(简单的同步机制) 双标志算法的特点 本方法根本就不可以被使用 仅考虑了两个进程之间的互斥问题 各进程先检测对方进程状态标志后再设置自己的标志, 在检测和设置期间可能插入另外一个进程的检测操作, 会造成两个进程在分别检测后同时进入临界区。 如果先设置自己的标志,然后再检测对方的状态标志, 也可能出现两个进程先后同时设置自己的标志再分别 检测对方状态标志,造成双方都不能进入临界区的情 况。 对于标志turn的判断与等待也是一个轮询过程,效率 低下 IE310, 4-83, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 双标志算法(先设置自己的标志) Pi() { while (1) { Ti Pj() { while (1) { Ti = 1; (XXXX) while (0 != Tj) usleep(1000); Tj = 1; (XXXX) while (0 != Ti) usleep(1000); 临界区代码; Ti = 0; 临界区代码; Tj = 0; 其它代码; } } Tj 其它代码; } } IE310, 4-84, xqfang.sjtu@gmail.com Shanghai Jiao Tong University Pi() { while (1) { Ti = 1; T = i; while (i == T && 1 == Tj) { usleep(1000); } 临界区代码; Ti = 0; 其它代码; 三标志算法 Ti Pj() { while (1) { Tj = 1; T = j; while (j == T && 1 == Ti) { usleep(1000); } T Tj } } 临界区代码; Tj = 0; 其它代码; } } Tx:进程x期望进入临界区的标志; T:如Ti和Tj均期望进入临界区,其晚者之进程号。 IE310, 4-85, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.2 进程的同步与互斥(简单的同步机制) 三标志算法的特点 本方法仍然缺乏实用性 比单标志法复杂而又没有任何优点? 强制两个进程轮流进入临界区 对于标志turn的判断与等待也是一个轮询过程, 效率低下 IE310, 4-86, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.2 进程的同步与互斥(简单的同步机制) 硬件同步机制 软件方法的一个基本思想是通过判断标志的状态,实 现进程互斥进入临界区。 往往涉及两条指令,然而在这两条指令的执行期间可 能被(进程调度所)中断。 硬件同步机制的思想: 把标志看成是一把锁,锁的初始状态是打开着的。 在一个进程临进入临界区之前,完成上锁操作,以封锁其它 进程进入临界区。 在该进程离开临界区之后,完成解锁操作,以允许其它进程 进入。 上锁操作首先内含对锁状态的测试操作:若已经上锁,则阻 塞进程等待锁被释放,进而届时再次尝试上锁操作;若没有 上锁,则予以上锁。 测试与上锁由一条机器硬件指令完成,因此是原子性 (atomic)的,期间不会被中断。 IE310, 4-87, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 硬件同步机制 上锁开始 enter_region: TSL REGISTER,LOCK CMP REGISTER,#0 JNE enter_region RET 取锁状态 上锁 原已上锁? 否 是 leave_region: MOVE LOCK,#0 RET 上锁结束 IE310, 4-88, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 硬件同步机制的工作原理 上锁(一条机器指令) 将一个存储器字读到寄存器中 在该内存地址上存入一个表示已经上锁的值 判断现已保存在寄存器中的原值 如果原值表示尚未上锁,则已经完成上锁,继续 如果原值表示已经上锁,则重复前述上锁尝试 解锁(一条机器指令) 将一个表示已经解锁的值存入内存地址的存储器字 中 IE310, 4-89, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.2 进程的同步与互斥(简单的同步机制) 硬件同步机制的特点 适用于任意数目的进程 实现方法简单,正确性容易验证 对于锁状态的检测,可能循环进行,没有做 到”让权等待” 从多个等待进程中随机选择一个进程进入临 界区,可能导致有的进程出现”饥饿”现象 IE310, 4-90, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 硬件同步机制的”让权等待” 上锁开始 取锁状态 上锁 原已上锁? 解锁 是 阻塞 否 被唤醒 跳转指令 上锁结束 IE310, 4-91, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4 进程的同步与互斥 4.4.3 信号量同步机制 信号量 信号量有多种类型,常用的记录型信 号量,可定义如下: struct semaphore { int value; /* value是初值为非负的整型变量 */ int *Q; /* Q是初始状态为空的等待队列 */ } S; IE310, 4-92, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 信号量 value是信号量的值; Q指向等待信号量的队列。 信号量的物理含义: 当S.value>0,表示可用资源个数; 当S.value<0,|S.value|表示等待该信号量的进程 个数。 IE310, 4-93, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 信号量的PV操作 P操作(测试-荷兰语Proberen)-请求资源 尝试对信号量减1,请求系统分配一个单位资源; 若没有可分配的资源,则当前进程变为阻塞状态, 并进入等待队列; 一旦有其它进程调用V操作,本进程将可能被唤醒。 对信号量S进行P操作,记为P(S)。 IE310, 4-94, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 信号量的PV操作 V操作(增量-荷兰语Verhogen)-释放资源 对信号量加1,释放一个单位资源; 若新的信号量小于等于0,则从等待队列中唤醒一 个进程,使其转入就绪状态,执行V操作的进程继 续执行。 对信号量S进行V操作,记为V(S)。 IE310, 4-95, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 信号量PV操作算法示意 P(struct semaphore * pS) { V(struct semaphore * pS) 原子性-- { pS pS->value - -; if (pS->value < 0) value 原子性-- pS->value + +; Q if (pS->value <= 0) { 本进程放入等待队列pS->Q; { 从等待队列pS->Q取出一个P; 阻塞当前进程; } 将进程P放入就绪队列; --原子性 } } --原子性 } IE310, 4-96, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 用信号量实现互斥模型 为临界资源设置一个互斥信号量S,其初值为1;在每个进程中将 临界区代码置于P(S)和V(S)操作之间。互斥模型如下: 其它正常代码段1; P(S) 临界段; V(S) 其它正常代码段2; 必须成对使用P和V操作 遗漏P操作则不能保证互斥访问 遗漏V操作则不能在使用临界资源之后将其释放(给其它等待的 进程) P、V原语不能次序错误、重复或遗漏 IE310, 4-97, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 用信号量实现同步模型 为进程设置一个同步信号量S,其初值为0;在 进程需要同步的地方分别插入P(S)和V(S)操作。 一个进程使用P操作时,则另一进程往往使用 V操作与之对应 使用P操作的进程可能发生阻塞调用,(阻塞 状态可能往往就是其常态) ,等待其它进程V 操作的唤醒动作 使用V操作的进程往往触发了先前使用P操作 的进程的继续运行 V操作不可能导致调用进程自身的阻塞 IE310, 4-98, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 用信号量实现同步模型 例如, 有两个进程P1、P2, P1的功能是计算x=a+b的值,a和b是常量,在 P1的前面代码中能得到; P2的功能是计算y=x+1的值。 IE310, 4-99, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 两个进程P1、P2的同步示意 P1(struct semaphore * pS) { ……; x = a + b; V(pS); /* 可能唤醒 */ pS value P2(struct semaphore * pS) { ……; Q P(pS); /* 可能阻塞 */ y = x + 1; ……; } X ……; } 在P1完成对V(pS)调用的返回之前,P2 将阻塞在对P(pS)的调用之中,其P(pS) 调用将暂时不会返回。 IE310, 4-100, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.3 进程的同步与互斥(信号量同步机制) 使用信号量及其PV操作注意事项 据进程间的制约关系确定信号量的种类。在保持进 程间有正确的同步关系的情况下,哪个进程先执行, 哪些进程后执行,彼此间通过什么资源(信号量)进行 协调,从而明确要设置哪些信号量。 信号量的初值与相应资源的数量有关,也与P、V操作 在程序代码中出现的位置有关。 同一信号量的P、V操作要成对出现 P、V操作可能分别出现在不同的进程代码中 P操作宜尽可能的晚出现 V操作宜尽可能地早出现 IE310, 4-101, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4 进程的同步与互斥 4.4.4 经典同步互斥问题 生产者与消费者问题(Producer-Consumer) 生产者-消费者问题是最著名的同步问题。 描述一组生产者进程向一组消费者进程共享 一个有界缓冲池(Bounded Buffer Pool)。 假定缓冲池中有n个缓冲区,每个缓冲区可以 存放有且仅有一个消息。 生产者尝试向缓冲池提供与放入消息。 消费者从缓冲池等待并取走消息。 IE310, 4-102, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 生产者与消费者问题(Producer-Consumer) Producer 1 Producer 2 ... Producer M 生产指针 满 空 消费指针 共享缓冲区 Consumer 1 Consumer 2 ... Consumer N 指针移动方向 IE310, 4-103, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 生产者与消费者问题(Producer-Consumer) 无论是生产者,还是消费者,均可能对缓冲池(往往是具有较复 杂数据结构的数据对象)进行操作,因此,缓冲池是临界资源。 生产者之间、生产者与消费者之间、消费者之间都必须互斥地 使用该缓冲池。为此,设置互斥信号量Smutex(初值为1)。 只有在缓冲池中至少有一个缓冲区已经存入消息之后,消费者 才能从中提取消息,否则必须等待。为此,设置一个同步信号 量Sfull,初值为0(没有消息)。 只有在缓冲池中至少有一个缓冲区为空之后,生产者才能把消 息放入其中,否则必须等待。为此,设置一个同步信号量 Sempty,初值为n(全部为空)。 指向首个空闲缓冲区的指针in的初值为0,指向首个保存有消息 的缓冲区的指针out的初值为0。 IE310, 4-104, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 生产者与消费者问题(Producer-Consumer)示意 struct sBufType buffer[n]; P生产者() P消费者() { { while (1) { while (1) { P(& Sfull); 生产一个产品; P(& Smutex); P(& Sempty); 从buffer[out]取出一个产品; P(& Smutex); out = (out + 1) mod n; 产品放入buffer[in]; V(& Smutex); in = (in + 1) mod n; V(& Sempty); V(& Smutex); V(& Sfull); } } 消费该产品; } }/*尝试将代码由下往上书写*/ IE310, 4-105, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 生产者与消费者问题(Producer-Consumer)示意 struct sBufType buffer[n]; P生产者() /*已将代码由下往上书写*/ { { { while (1) { 生产一个产品; 消费该产品; P(& Sempty); V(& Sempty); P(& Smutex); V(& Smutex); 产品放入buffer[in]; out = (out + 1) mod n; in = (in + 1) mod n; 从buffer[out]取出一个产品; V(& Smutex); P(& Smutex); V(& Sfull); P(& Sfull); while (1) } } } } P消费者() IE310, 4-106, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 读者与写者问题(Reader-Writer) 读者-写者问题也是经典的进程同步问题。 有两组并发执行的进程共享同一个数据文件。 一组进程只要求读数据文件的内容,被称为读者。 另一组进程则要求修改数据文件的内容,被称为写 者。 允许多个读者同时执行读操作,即读者可以同时读 数据文件,而不需要互斥。 一个写者不能和其它进程(无论是写者还是读者)同 时访问数据文件,它们之间必须互斥。 IE310, 4-107, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 读者与写者问题(Reader-Writer) 对于写者与其它写者或者读者对数据文件进 行的互斥访问操作,设置互斥信号量 Smutex(初值为1)。 设置读者的个数为Nreadcount变量,初值为0 (没有读者)。 所有读者均可能访问与修改Nreadcount变量, 为此,设置一个互斥信号量 Sreadcount_mutex(初值为1)。 IE310, 4-108, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 读者与写者问题(Reader-Writer)示意 P写者() { 其它工作; P读者() { 其它工作; P(& Sreadcount_mutex); Nreadcount ++; if (1 == Nreadcount) P(& Smutex); V(& Sreadcount_mutex); P(& Smutex); 写数据文件; V(& Smutex); 其它工作; 读数据文件; } P(& Sreadcount_mutex); Nreadcount - -; if (0 == Nreadcount) V(& Smutex); V(& Sreadcount_mutex); int main(int argc, char ** argv) { cobegin 若干P写者;若干P读者; coend } } 其它工作; IE310, 4-109, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 读者与写者问题(Reader-Writer) 如果进一步限制同时可以读的读者数最多为 10个,那么就需要额外设置一个资源信号量 Ssum,初值为10。 要求读者在尝试对数据文件进行的所有操作 之前,首先对Ssum进行P操作,最后对Ssum 进行V操作。 也就是先行限制同时可能访问数据文件的读 者数为10个,令后续读者阻塞。 最后,唤醒一个可能存在的正处于阻塞状态 的读者。 IE310, 4-110, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 读者与写者问题(限10个读者)示意 P写者() P读者() { { 其它工作; P(& Ssum);/*Added*/ P(& Sreadcount_mutex); Nreadcount ++; if (1 == Nreadcount) P(& Smutex); V(& Sreadcount_mutex); P(& Smutex); 写数据文件; V(& Smutex); 其它工作; 读数据文件; } P(& Sreadcount_mutex); Nreadcount - -; if (0 == Nreadcount) V(& Smutex); V(& Sreadcount_mutex); V(& Ssum );/*Added*/ int main(int argc, char ** argv) { cobegin 若干P写者;若干P读者; coend 其它工作; } 其它工作; } IE310, 4-111, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 睡眠的理发师问题(Barber-Customers) 一个有趣的进程同步问题。 有一个理发师、一张理发椅和n张给顾客坐的顾客椅。 如果没有顾客来,理发师就会睡觉。 第一个来的顾客必须唤醒睡着了的理发师。 如果理发师正在理发,新来的顾客须选择空的顾客椅 坐下等待。 如果所有的顾客椅都被坐满,新来的顾客就离开理发 店。 顾客理发完成之后,必须等待理发师收费后才能离开 理发店。 IE310, 4-112, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 睡眠的理发师问题(Barber-Customers) 理发师和顾客的活动被看做两类不同的进程。 理发师只有在理发椅上有顾客时才能进行理发,否则睡觉阻塞等待, 直至第一个进来的顾客将其唤醒并开始理发。该同步关系相当于单缓 冲区的生产者-消费者问题。设置同步信号量Sused(初值为0)。 只有在理发椅空闲时,顾客才能坐上并等待理发师理发。刚刚理好头 发的顾客或者理发师需要借助资源信号量Snext(初值为1)才能唤醒后述 可能坐在顾客椅上阻塞等待的顾客。 理发师为顾客理发时,顾客必须等待理发的完成,可以用同步信号量 Sfinish_cut(初值为0)表示。 在理发完成之后,顾客必须向理发师付费,并等理发师收费后才能离 开,使用同步信号量Spayment(初值0)表示。 理发店中的顾客椅是顾客进程竞争的资源,应设置资源信号量 Schairs(值为n)。 顾客来到理发店之后,首先考察理发师是否在理发,若理发师忙,则 考察是否有顾客椅,若有的话,则坐在顾客椅上等候理发,若没有则 离开。 用一个整型变量Ncustomers (初值为0)来对顾客进行计数。各顾客进程 必须借助互斥信号量Smutex(初值为1)来互斥地访问和修改该变量。 IE310, 4-113, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 睡眠的理发师问题 Pbarber() { Pcustomer() { int Ntemp; P(& Smutex); if (Ncustomers > n) { V(& Smutex); 离开理发店; } Ncustomers ++; Ntemp = Ncustomers; V(& Smutex); if (Ntemp > 1) { P(& Schairs);/*等顾客椅*/ 坐顾客椅; P(& Snext);/*等待理发椅为空*/ 离开顾客椅; V(& Schairs);/*顾客椅+1*/ } else { P(& Sfree);/*占用理发椅*/ } while (1) { P(& Sused);/*等顾客*/ 理发; V(& Sfinish_cut); /*唤醒顾客*/ 坐上理发椅; V(& Sused);/*唤醒理发师理发*/ 接受理发; P(& Sfinish_cut);/*等理发结束*/ P(& Spayment);/*等付费*/ 接收顾客的付费; } 离开理发椅; V(& Snext);/*唤醒顾客椅上的一个顾客*/ 准备钞票付费; V(& Spayment);/*唤醒理发师收费*/ } } P(& Smutex); Ncustomers --; V(& Smutex); 离开理发店; IE310, 4-114, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 由理发师唤醒顾客椅 Pbarber() { Pcustomer() { int Ntemp; P(& Smutex); if (Ncustomers > n) { V(& Smutex); 离开理发店; } Ncustomers ++; Ntemp = Ncustomers; V(& Smutex); if (Ntemp > 1) { P(& Schairs);/*等顾客椅*/ 坐顾客椅; P(& Snext);/*等待理发椅为空*/ 离开顾客椅; V(& Schairs);/*顾客椅+1*/ } else { P(& Sfree);/*占用理发椅*/ } while (1) { P(& Sused);/*等顾客*/ 理发; V(& Sfinish_cut); /*唤醒顾客*/ 坐上理发椅; V(& Sused);/*唤醒理发师理发*/ 接受理发; P(& Sfinish_cut);/*等理发结束*/ 离开理发椅; P(& Spayment);/*等付费*/ 接收顾客的付费; V(& Snext); /*唤醒顾客椅上的一个顾客*/ 准备钞票付费; V(& Spayment);/*唤醒理发师收费*/ } } } P(& Smutex); Ncustomers --; V(& Smutex); 离开理发店; IE310, 4-115, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 前驱后继同步问题 多个合作进程在执行时有先后次序的要求,前后形成 前驱后继关系,可以用进程流图描述。 在前驱与后继同步问题中,每个进程是否能够执行, 取决于它的所有前驱是否执行结束。每个前驱的结束 都是它得以执行的必要条件之一,因此,需要针对该 进程的每个前驱设置一个信号量,并在该进程开始处 执行相应的P操作。 同样,如果进程有后继,则当它结束时,需要执行其 后继所等待的信号量的V操作,释放其后继所等待的 执行条件。 IE310, 4-116, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 前驱后继同步问题的解决方法 分析每个进程j,看它有无前驱,如果有则针 对每个前驱进程i设置一个信号量Sij,初值皆 为0; 在每个进程j开始时,针对其每个前驱进程, 对信号量Sij执行P操作,有几个前驱执行几个 P操作; 在每个进程i执行完毕后,针对其每个后继进 程,对信号量Sij执行V操作,有几个后继执行 几个V操作。 以上, i表示前驱进程号,j表示后继进程号。 IE310, 4-117, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 前驱后继同步问题的示例 设有4个进程,其执行的先后流图如下图所示。用P、 V操作实现其同步。 P1 P2 P3 P4 IE310, 4-118, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 前驱后继同步问题的示例之同步分析 进程Pj 有无前驱 请求信号量 信号量初值 有无后继 释放信号量 P1 无 无 无 P2、P3 S12、S13 P2 P1 S12 0 P4 S24 P3 P1 S13 0 P4 S34 P4 P2、P3 S24、S34 0 无 无 IE310, 4-119, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.4.4 进程的同步与互斥(经典同步互斥问题) 前驱后继同步问题的示例之解决算 法描述 int s12 = s13 = s24 = s34 = 0; p1() { //无前驱,所以无P操作 执行P1自己的程序; V(s12);//有后继P2,释放条件s12 V(s13);//有后继P3,释放条件s13 } p2() { P(s12);//有前驱P1,申请条件s12 执行P2自己的程序; } V(s24);//有后继P4,释放条件s24 p3() { P(s13);//有前驱P1,申请条件s13 执行P3自己的程序; V(s34);//有后继P4,释放条件s34 } p4() { P(s24);//有前驱P2,申请条件s24 P(s34);//有前驱P3,申请条件s34 执行P4自己的程序; //无后继,所以无V操作 } int main(int argc, char ** argv) { cobegin p1(); p2(); p3(); p4(); coend } IE310, 4-120, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信 4.5.1 概述 进程间的通信(IPC-Inter-Process Communication),指进程之间的信息交换。 根据进程间通信规模和方式的不同,可以将 进程通信分为低级通信和高级通信。 IE310, 4-121, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.1 进程通信(概述) 低级通信 低级通信是指进程通信时仅传递状态和控制 信息。 如信号量及其P/V操作原语、互斥锁、读写锁、 软中断信号signal等。 每次交换的信息量少,其传递的信息往往只 是一个信号或一个键或组合键,不适合传送 批量、大量的信息。 主要用于进程之间的同步、互斥、终止、挂 起等控制信息的传递。 IE310, 4-122, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信(概述) 高级通信 高级通信是指进程通信时能够高效地传送批 量的数据。 如共享内存、管道、消息队列等。 每次交换的信息可能往往以报文为单位,信 息量较大。 IE310, 4-123, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信 4.5.2 消息(Message)队列 多进程共享同一个消息队列。 由操作系统统一管理一组用于通信的消息缓冲区,每个消息缓 冲区可以存放一个消息。 消息由接收进程名、消息长度、消息正文等内容组成。 发送消息的进程可以在任意时刻发送任意个消息到指定的消息 队列上,同时,该发送进程还要检查是否有接收进程在等待它 所发送的消息,若有则唤醒它。 接收消息的进程可以在需要消息时到该消息队列上获取消息; 如果消息还没有到来,则转入睡眠状态。 消息队列一旦创建后即可由多进程共享;作为一个临界资源, 针对同一消息队列的发送和接收必须互斥进入,但由操作系统 所提供的发送和接收系统调用自动实现。 IE310, 4-124, xqfang.sjtu@gmail.com 4.5.2进程通信(消息队列) Shanghai Jiao Tong University IE310, 4-125, xqfang.sjtu@gmail.com 4.5.2进程通信(消息队列) Shanghai Jiao Tong University 单个消息的数据结构示意 struct msg_buf { char szSender[20]; struct msg_buf * pNext; int nMsgSize; char msgText[200]; }; /* /* /* /* 发送者 */ 下一个 */ 消息长度 */ 消息正文 */ IE310, 4-126, xqfang.sjtu@gmail.com 4.5.2进程通信(消息队列) Shanghai Jiao Tong University 某个消息队列在操作系统内部的结构示意 struct PCB { struct semaphore Smutex = 1; /* 互斥锁 */ struct semaphore Snum = 0; /* 消息个数 */ struct msg_buf * pFirst;/* 指向第一个消息的指针 */ … }; IE310, 4-127, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.2进程通信(消息队列) 消息发送/接收原语示意 procedure msgsnd(reciever, sender, pText, nSize) { if (nSize > 200) procedure msgrcv() { pPCB = getOwnPCB(); 出错; pMsg = getMsgBuf(nSize); P(pPCB->Snum); strcpy(pMsg.szSender, sender); pMsg.nMsgSize = nSize; P(pPCB->Smutex); memcpy(pMsg.msgText, pText, nSize); pMsg = get_and_remove(pPCB->pFirst); pPCB = getProcessPCB(reciever); V(pPCB->Smutex); P(pPCB->Smutex); insert(pPCB->pFirst, pMsg); V(pPCB->Smutex); } return pMsg; V(pPCB->Snum); } IE310, 4-128, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信 4.5.3 共享内存 若干个进程通过一块共享内存区域来交换数据。 共享内存区域实际上是一块被同时映射到多个进程虚 拟地址空间的内存区域,共享内存所对应的虚页面出 现在多个进程的页表之中,从而成为多个进程虚拟地 址空间的一部分。 对于各进程来说,经过必要的创建和挂接准备,可以 象普通内存一样地访问该块内存区域。 共享内存出现于各进程中的虚拟地址当然往往是不尽 相同的。 这是进程间通信中最为高效的手段,而且支持大容量 通信。 缺少保护和触发机制。 IE310, 4-129, xqfang.sjtu@gmail.com 4.5.3 进程通信(共享内存) Shanghai Jiao Tong University 4.5.3 共享内存 A进程虚空间 内存空间 A正文 A数据 A栈 B进程虚空间 B正文 共享区 B数据 B栈 IE310, 4-130, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信 4.5.4 管道(Pipeline)通信 管道是指用于连接一个读进程和一个写进程,以实现它 们之间通信的共享文件,又称为管道文件。 发送进程(即写进程)向管道(共享文件)提供输入,以字 符形式将大量的数据送入管道,而接收进程(即读进程) 从管道中读取、接收数据。 管道通信是基于文件系统形式的一种通信方式。 管道中的信息是单向传送的。 管道传送的是无格式字节流(比如,多次写入的信息经 由一次读取调用所全部返回)。 进程间需要一定的同步(如果没有等待读取的进程,对 管道的写入可能失败)。 共享管道的进程必须是同一家族的进程,并由其祖先进 程创建。一般在父子进程之间进行管道通信。 IE310, 4-131, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5 进程通信 4.5.5 Linux的进程通信 低级通信 处理随机事件:软中断通信Signal 进程间的同步和互斥 信号量 互斥锁 条件变量 高级通信 父子进程间:(无名)管道 任意进程间,且自带同步,但信息复制耗时:消息队 列 利用内存缓冲区直接交换信息,快捷、信息量大,但 不带同步工具,同步和互斥问题需各进程利用前述同 步和互斥手段自行解决:共享内存 IE310, 4-132, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 1. 软中断信号signal 是一种简单且最基本的进程间通信机制 最大的特点是提供了一种简单的处理异步事件的方法, 是操作系统用来通知进程有事件发生的一种机制。 例如,我们常见的用户从键盘输入组合键Ctrl+C来中断 一个程序的运行,或者在两个进程之间通过某个信号来 通知发生了异步事件,或者向系统或进程报告突发的硬 件故障如非法指令、运算溢出等等。 类似于防空警报、烽火台等效果;每次上班,领导总是 先行检查、处理秘书放在办公桌上的紧急报告。 用户进程还可以给自己发送信号以中断程序的执行,并 自动转入指定的软中断处理函数中去执行用户自行安排 的处理内容。 由于这种信号总是在进程处于运行状态时才会去响应, 故称之为软中断信号。 IE310, 4-133, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) Linux常用的软中断信号 10 序号 名称 含义 SIGUSR1 用户定义信号1 11 SIGSEGV 段违例(段越界) 12 SIGUSR2 用户定义信号2 13 SIGPIPE 向非法管道中写 数据(没有读) 14 SIGALARM 钟报警 15 SIGTERM 软件中止 1 SIGHUP 挂起 2 SIGINT 按Ctrl+C 3 SIGQUIT 按Ctrl+\ 4 SIGILL 非法指令 5 SIGTRAP 自陷,跟踪代码的 执行 6 SIGIOT IOT指令 16 …… …… 7 SIGBUS 总线错(超时) 17 SIGCHLD 子进程死亡 8 SIGFPE 浮点数例外 18 …… …… 9 SIGKILL 终止进程 …… …… …… IE310, 4-134, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 对于需要使用的软中断信号,进程在接收之前必须先使用 signal()系统调用函数进行预置。预置的目的是将某个软中断信 号与某个可执行的处理函数进行关联。当信号发出并被指定的 进程接收后,系统就中断接收该软中断信号进程的常规执行, 转而执行与该软中断信号相关联的函数。该函数执行完毕后再 返回被中断的位置继续执行。 事实上,除了用户定义信号SIGUSR1和SIGUSR2外,其它软 中断信号都已经由操作系统预置了相应的处理函数。用户进程 中如果对这些软中断信号进行预置,就可以使该信号与新的处 理函数进行关联。当该软中断信号被接收时,转而执行的不再 是操作系统原来所预置的处理函数,而是用户对该软中断信号 重新预置的处理函数。 对于同一个软中断信号,可以通过多次调用signal()系统调用, 分别与不同的处理函数进行关联。系统在响应该软中断信号时, 执行的是当前(最近)预置的处理函数,从而实现同一软中断信号 在不同的阶段执行不同的处理函数。 IE310, 4-135, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 软中断的信号预置signal()系统调用 预置软中断信号signum由function()函数处理。 signal(signum, function) void (*signal(int signum, void (*function) (int))) (int); typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t function); 参数signum是由系统给定的软中断信号中的序号或名称。 参数function是与该软中断信号相关联的函数名,当进 程在运行过程中捕捉到该软中断信号后,将暂时中断程 序的当前执行,临时转而执行该函数。 返回软中断信号signum原来的处理函数。 软中断信号必须提前预置,然后才可以在程序运行中被 用户捕获。 IE310, 4-136, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 软中断的信号预置signal()系统调用 signal(signum, function) void (*signal(int signum, void (*function) (int))) (int); typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t function); 返回软中断信号signum原来的处理函数。 #define SIG_ERR (void (*)())-1 #define SIG_DFL (void (*)())0 #define SIG_IGN (void (*)())1 IE310, 4-137, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 软中断信号的发送kill()系统调用 向指定进程标识符pid的进程发软中断信号 signum。 int kill(pid_t pid, int signum); 返回成功与否。 pid表示一个或一组进程的标识符。 当pid>0时,发送给指定pid的进程; 当pid=0时,发送给同组的所有进程; 当pid=-1时,发送给所有满足以下条件的进程:那些 进程的用户标识符等于发送进程的有效用户标识符; signum软中断信号的序号或名称。 命令行方式为$ kill –nSigNum nPID IE310, 4-138, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 软中断的其它系统调用 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigpending(sigset_t *set); int sigsuspend(const sigset_t *mask); struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); } # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) typedef struct { unsigned long int __val[_SIGSET_NWORDS]; } sigset_t; IE310, 4-139, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 软中断应用例子softint.c #include<stdio.h> #include<unistd.h> #include<signal.h> #include <sys/types.h> int k = 1; //循环变量及其初值 void int_func(int sig) //软中断处理函数 { printf("接收到软中断信号%d, 初始k值为%d\n", sig, k); k=0; //修改循环变量的值为0 } int main(int argc, char ** argv) { pid_t nOwnPID = -1; nOwnPID = getpid(); printf("我的PID为%d\n", nOwnPID); signal(SIGINT,int_func);//预置软中断信号的处理函数 /*循环显示,等待键入Ctrl+c,获取该软中断信号后转软中断处理函数执行*/ while (1 == k) { printf("k=%d! 请通过其它终端输入kill -SIGINT %d命令发送信号\n", k, nOwnPID); sleep(1); printf("k=%d! 请通过其它终端输入kill -SIGINT %d命令发送信号\n", k, nOwnPID); } printf("End, k = %d!\n", k); //软中断处理函数致使循环结束 } IE310, 4-140, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之软中断) 系统调用可能被软中断信号的处理函数所打断 返回后,该系统调用可被自动重启 但需要使用sigaction()才能设置,read_con_sigtest.c int bIntTriggered = 0; char command[1024]; void int_handler(int nSigNum) { bIntTriggered = nSigNum; printf("int_handler(%d) has been called...\n",nSigNum); } int main(int argc, char ** argv) { struct sigaction sigAct; signal(SIGINT, int_handler); //sigAct.sa_handler = int_handler; //sigemptyset(& sigAct.sa_mask); //sigAct.sa_flags = SA_RESTART;//0;SA_RESTART; //sigaction(SIGINT, & sigAct, NULL); while(0 == bIntTriggered) { printf("Enter some text:"); //提示键入消息内容 fgets(command, 1024, stdin); //标准输入送buffer printf("fgets() return %s\n", command); if (bIntTriggered) { printf("bIntTriggered = %d\n", bIntTriggered); break; } } } IE310, 4-141, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) 2. 信号量 AND型信号量 在有些场合,一个进程需要先获得两个或者 更多的共享资源方能执行其任务. 假定进程P1和P2均需要访问共享资源D和E, 为此设置两个互斥信号量Sd_mutex和 Se_mutex,初值均为1。 如果P1和P2两个进程的临界区代码如后所示: IE310, 4-142, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) AND型信号量 P1() { 其它代码1; P(& Sd_empty); <-x中断死锁可能x-> P(& Se_empty); 其它临界区代码D&E; V(& Se_empty); V(& Sd_empty); 其它代码2; } P2() { 其它代码3; P(& Se_empty); <- x中断死锁可能x-> P(& Sd_empty); 其它临界区代码E&D; V(& Sd_empty); V(& Se_empty); 其它代码4; } IE310, 4-143, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) AND型信号量 AND型信号量的基本思想是:将临界区操作所需的所有资源, 一次性全部分配给一个进程,待进程从临界区退出时再一起释 放。 也就是,对于临界资源的分配,采用原子操作方式,要么全部 分配给一个进程,要么一个也不分配给它。 AND型信号量的个数实际上可以扩展为n。 不妨将AND型信号量的P操作记为PA(S1, S2 , …, Sn)。 不妨将AND型信号量的V操作记为VA(S1, S2 , …, Sn)。 这样,原先的例子可以如后所示: IE310, 4-144, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) AND型信号量 P1() P2() { { 其它代码1; PA(Sd_empty, Se_empty); PA(Sd_empty, Se_empty); 其它临界区代码D&E; 其它临界区代码E&D; VA(Sd_empty, Se_empty); VA(Sd_empty, Se_empty); 其它代码2; } 其它代码3; 其它代码4; } IE310, 4-145, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) 信号量集合 对于信号量S所代表的资源,如果一次性需要其d单位个资源, 那么就要多次调用P(S)操作。 另外,如果其个数低于某一个下限值t,可能要求不予分配。从 而,在每次分配之前,均需要测试该资源的数量是否大于安全 的下限值。 这样,也可以有这样的原子性操作,PS(S, t, d)。 也可以有这样的原子性操作,VS(S, d)。 参照AND型信号量,那么也可以有这样的原子性操作: PS(S1, t1, d1, S2, t2, d2, …, Sn, tn, dn) VS(S1, d1, S2,, d2, …, Sn, dn) IE310, 4-146, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 内部实现用数据结构 semid_ds进程的用户空间对信号量集合的描述. 信号量结构 供用户操作的系统调用 创建semget()系统调用。 操作semop()系统调用。 控制/删除semctl()系统调用。 IE310, 4-147, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 struct semid_ds { struct ipc_perm sem_perm; /*操作权限 */ __time_t sem_otime; /* 前次semop()时间 */ __time_t sem_ctime; /* 前次semctl()时间 */ unsigned long int sem_nsems; /* 集合中的信号量个数 */ …; }; IE310, 4-148, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 单个信号量结构的若干重要成员 unsigned short semval; /* 信号量的值 */ unsigned short semzcnt; /* 等待信号量值为0的进程数 */ unsigned short semncnt; /*等待信号量值增长的进程数 */ pid_t sempid; /* 前次调用semop()的进程 */ IE310, 4-149, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的创建semget()系统调用 创建一个新的信号量集合或者打开一个已经存在的信号量集合。 int semget(key_t key, int nsems, int semflg); 返回信号量集合描述符,semid。 key用于不同进程/线程间对同一个消息队列的约定、匹配,相当于 指定联络地点。 (key_t ftok(const char *pathname, int proj_id); 将一个路径名与项目标识号转换为System V进程间通信的key值; 类似于passwd。) semflg是用户设置的标志和访问方式(权限)。如果 semflg&IPC_CREAT为真,表示如果系统中没有以key命名的信号量 集合,则创建该信号量集合,否则,就返回该信号量集合的描述符。 IE310, 4-150, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 IE310, 4-151, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的操作semop()系统调用 请求向指定的信号量集合(中的多个信号量)执行指定的操作序 列。 int semop(int semid, struct sembuf *sops, unsigned nsops); semid是由semget()返回的信号量集合描述符。 struct sembuf { unsigned short sem_num; /* 指定信号量序号 */ short sem_op; /* 指定操作类型 */ short sem_flg; /* 操作的附加标志 */ }; sops指定操作序列。 nsops指出操作序列中的操作个数(数组大小)。 sem_flg规定相应的动作要求,可以根据需要选取设置 IPC_NOWAIT和SEM_UNDO标志值。 IE310, 4-152, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的操作semop()系调用 对sops指定操作序列的执行是原子性的,也 就是对该操作序列中的操作,要不全部被执 行,要不就一个也没被执行。 信号量集合中的首个信号量为第0个。 sops操作序列中的每一个操作仅对信号量集 合中的第sem_num个信号量进行操作。 操作分为如下三个类型: IE310, 4-153, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量操作的三个类型之一 ----如果sem_op>0,追加至该信号量的值 semval(+=sem_op)。 如果SEM_UNDO标志被设置,一旦进程退出, 该操作将被取消(回退)。 如果权限合适,本操作总是能够正常进行, 总是不会引起调用进程的阻塞。 IE310, 4-154, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量操作的三个类型之二 ----如果sem_op为0,等待该信号量的值semval为0。 如果semval为0则继续; 否则: 如果设置了IPC_NOWAIT标志,则出错返回且错误码errno为 EAGAIN (且sops中的所有操作均没有被执行) ; 否则,设置该信号量的semzcnt加1,进程阻塞等待。 semval成为0, 那么semzcnt减1后继续; 信号量集合被删除,则出错返回且错误码errno为EIDRM; 调用进程捕捉到一个信号signal,那么semzcnt减1,出错返回且错误 码errno为EINTR. IE310, 4-155, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量操作的三个类型之三 ----如果sem_op<0,申请使用该信号量所代表的资源。 如果semval>=|sem_op|,则semval-=|sem_op|,继续(如果 SEM_UNDO标志被设置,追加登记取消(回退)操作至系统) ; 否则(semval<|sem_op|),: 如果设置了IPC_NOWAIT标志,则出错返回且错误码errno为 EAGAIN(且sops中的所有操作均没有被执行); 否则,设置该信号量的semncnt加1,进程阻塞等待。 semval>=|sem_op|,那么semncnt减1,semval-=|sem_op|后继续 (如果SEM_UNDO标志被设置,追加登记取消(回退)操作至系统) ; 信号量集合被删除,则出错返回且错误码errno为EIDRM; 调用进程捕捉到一个信号signal,那么semncnt减1,出错返回且错误 码errno为EINTR. IE310, 4-156, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的控制/删除semctl()系统调用 控制、查询指定的信号量集合或其中的单个信号量。 int semctl(int semid, int semnum, int cmd, ...); semid是由semget()返回的信号量集合描述符。 有三至四个参数,如果是四个参数,那么第四个参数 arg采用如下格式 union semun { int val; struct semid_ds *buf; unsigned short *array; }; struct seminfo *__buf; /*SETVAL命令希望设置的信号量值 */ /* IPC_STAT, IPC_SET命令使用缓冲 */ /* GETALL, SETALL命令使用数组 */ /* Linux特定部分: */ /* IPC_INFO命令使用的缓冲 */ cmd给出命令的类型: IE310, 4-157, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的控制/删除semctl()系统调用 cmd给出命令的类型: IPC_STAT要求将由semid指定信号量集合的数据结构信息拷贝到由 arg->buf给出的缓冲区中,semnum参数被忽略。 IPC_SET要求按照arg-> buf指向的内容设置由semid指定信号量集 合的数据结构的若干值sem_perm.uid、sem_perm.gid、 sem_perm.mode(之低9位),更新sem_ctime,semnum参数被忽 略。 IPC_RMID要求立即删除由semid指定的信号量集合,各可能相关的 阻塞等待进程均被唤醒并出错返回,错误码errno为EIDRM。 GETALL要求返回所有信量的值至arg->array数组中,semnum参 数被忽略。 GETNCNT要求返回第semnum个信号量的semncnt值(等待其 semval增加的进程个数) 。 IE310, 4-158, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量的控制/删除semctl()系统调用 cmd给出命令的类型: GETPID要求返回第semnum个信号量的sempid值(最近对之调用了 semop()操作的进程号) 。 GETVAL要求返回第semnum个信号量的semval值。 GETZCNT要求返回第semnum个信号量的semzcnt值(等待其semval 为0的进程个数) 。 SETALL要求将所有信号量的值设置为如arg->array数组所示,清除 所有历史取消(回退)队列,唤醒有关阻塞等待的进程(等0或增加) , semnum参数被忽略。 SETVAL要求将第semnum个信号量的值semval值设置为如arg->val 所示,清其历史取消(回退)队列,唤醒有关阻塞等待的进程(等0或 增加)。 IE310, 4-159, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之信号量) Linux的信号量集合 信号量及其P、V操作的一般流程: 定义信号量集合标识符:int semid; 定义信号量集合数据结构 定义P、V操作所用的数据结构:struct sembuf P,V; 定义给信号量赋初值的参数数据结构:union semun arg; 申请信号量集合semget ,根据需要申请创建 分别对每个信号量semid赋初值: arg.val = 初值; semctl(semid, 0, SETVAL, & arg); 定义信号量集合的P操作: P.sem_num = 0; P.sem_op = -1; P.sem_flg = SEM_UNDO; 定义信号量集合的V操作: V.sem_num = 0; V.sem_op = 1; V.sem_flg = SEM_UNDO; 对信号量集合semid执行P操作:semop(semid, & P, 1); 对信号量集合semid执行V操作:semop(semid, & V, 1); 撤消信号量集合:semctl(semid, IPC_RMID, 0); IE310, 4-160, xqfang.sjtu@gmail.com Shanghai Jiao Tong University #include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<linux/sem.h> int main(int argc, char ** argv) { int chld, i=1, j=1, mutexid; struct sembuf P,V; union semun arg; mutexid=semget(IPC_PRIVATE,1,0666|IPC_CREAT); /*创建只含有一个互斥信号量元素的信号量集*/ arg.val=1; /*为信号量赋初值*/if (-1 == semctl(mutexid,0,SETVAL,arg)) perror("semctl setval error"); P.sem_num=0; P.sem_op=-1; P.sem_flg=SEM_UNDO; /*定义P操作*/ V.sem_num=0; V.sem_op=1; V.sem_flg=SEM_UNDO; /*定义V操作*/ while (-1 == (chld=fork())) ; //创建子进程 if (chld > 0) { //父进程返回 while (i<=9) { //循环9次 usleep(3); semop(mutexid,&P,1); //进入临界区前执行P操作 semset.c printf("parent in\n"); semop(mutexid,&V,1); i++; } else { } } } wait(NULL); semctl(mutexid,IPC_RMID,0); while(j<=9) { usleep(3); semop(mutexid,&P,1); } exit(0); printf(" child in\n"); semop(mutexid,&V,1); j++; sleep(1); printf("parent out\n"); //出临界区执行V操作 //等待子进程终止 //撤消信号量 //子进程返回 //循环9次 //进入临界区前执行P操作 sleep(3); printf(" //出临界区执行V操作 child out\n"); //子进程终止 IE310, 4-161, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之互斥锁) 3. 互斥锁pthread_mutex 进程或线程间的简单互斥 编译连接时须输入-lpthread参数,详细例子合并至共享内存部分 必须先进行初始化 pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr); 在进入临界区之前,使用pthread_mutex_lock(),从临界区出来之 后,使用pthread_mutex_unlock() pthread_mutex_lock()相当于信号量用于互斥时的P操作 pthread_mutex_unlock()相当于信号量用于互斥时的V操作 int pthread_mutex_lock(pthread_mutex_t * mutex); int pthread_mutex_unlock(pthread_mutex_t * mutex); int pthread_mutex_trylock(pthread_mutex_t * mutex); int pthread_mutex_destroy(pthread_mutex_t * mutex); IE310, 4-162, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之互斥锁) P1() { int X; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int R; Pn() { pthread_mutex_lock(& mutex ); 按订票要求找到X; R = X; if (X >= 1) { X = X – 1; } pthread_mutex_unlock(& mutex ); } int R; pthread_mutex_lock(& mutex ); 按订票要求找到X; R = X; if (X >= 1) { X = X – 1; } pthread_mutex_unlock(& mutex ); if (R >= 1) { 打印输出一张票子; } else { 提示票已售完; } } if (R >= 1) { 打印输出一张票子; } else { 提示票已售完; } IE310, 4-163, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之条件变量) 4. 条件变量pthread_cond 进程或线程间的简单同步 编译连接时须输入-lpthread参数,详细例子合并至共享内存部分 必须先进行初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t * cond, pthread_condattr_t * cond_attr); 有一方等待 int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); int pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, const struct timespec * abstime); 另一方的以下调用将唤醒正在等待的那一方 int pthread_cond_signal(pthread_cond_t * cond); int pthread_cond_broadcast(pthread_cond_t * cond); int pthread_cond_destroy(pthread_cond_t * cond); IE310, 4-164, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之条件变量) int x; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; P1() P2() { { ……; pthread_mutex_lock(& mutex ); pthread_mutex_lock(& mutex ); x = a + b; /* 往往阻塞 */ /* 可能唤醒 */ pthread_cond_wait(& cond, & mutex); pthread_cond_signal(& cond); y = x + 1; pthread_mutex_unlock(& mutex ); pthread_mutex_unlock(& mutex ); ……; } ……; ……; } IE310, 4-165, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之管道) 5. 管道 所谓管道,是指用于连接一个读进程和一个写进程,以实现它们之 间通信的共享文件,又称pipe文件。 一般使用的是无名管道,是一个临时文件,没有文件路径名,不占 用文件目录项 通过pipe()系统调用创建。 int pipe( int fd[2] ); 返回值表示成功与否。 通过fd返回两个文件描述符: 写文件描述符fd[1] 读文件描述符fd[0]。 写进程通过系统调用write()向fd[1]写入数据;读进程通过系统调用 read()从管道fd[0]中读出内容。 共享管道的进程必须是同一家族的进程,并由其祖先创建。一般在 父子进程之间进行管道通信。 IE310, 4-166, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之管道) pipecom.c: #include #include #include #include #include if ((nPID = fork()) < 0) { perror("创建进程失败!"); exit(1); } if (nPID > 0) { /* 父进程 */ close(fd[0]); /* 关闭读管道 */ write(fd[1], szMsg, strlen(szMsg)); wait(NULL); } else { /* 子进程 */ close(fd[1]); /* 关闭写管道 */ nRetSize = read(fd[0], pBuf, nMsgSize); pBuf[nRetSize] = '\0'; printf("读取管道的返回值是%d\n", nRetSize); printf("%s\n", pBuf); } <stdio.h> <string.h> <sys/types.h> <sys/wait.h> <unistd.h> #define nMsgSize 25 char * szMsg = "Hello, China!"; int main(int argc, char ** argv) { char pBuf[nMsgSize]; int fd[2], nRetSize; pid_t nPID; if (pipe(fd) < 0) { perror("创建管道失败!"); exit(1); } } IE310, 4-167, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 6. 消息队列 内部实现用数据结构 struct msqid_ds进程的用户空间对消息队列的描述. struct msg_queue操作系统内核空间对进程消息队列 的描述. struct msg消息队列中消息的存放形式. 供用户操作的系统调用 创建msgget()系统调用。 发送msgsnd()系统调用。 接收msgrcv()系统调用。 控制/删除msgctl()系统调用。 IE310, 4-168, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 消息队列的创建msgget()系统调用 创建一个新的消息队列或者打开一个已经存在的消息队 列。 int msgget(key_t key, int msgflg); 返回消息队列描述符,msqid。 key用于不同进程/线程间对同一个消息队列的约定、匹 配,相当于指定联络地点。 (key_t ftok(const char *pathname, int proj_id);将一个 路径名与项目标识号转换为System V进程间通信的key 值;类似于passwd。) msgflg是用户设置的标志和访问方式(权限)。如果 msgflg&IPC_CREAT为真,表示如果系统中没有以key命 名的消息队列,则创建该消息队列,否则,就返回该消 息队列的描述符。 IE310, 4-169, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 消息队列的消息发送msgsnd()系统调用 向指定的消息队列发送一个消息(将该消息链接到该消息队列的尾 部)。 int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); struct msgbuf { long mtype; /* 消息类型,必须大于0 */ char mtext[1]; /* 消息正文 */ }; msqid是由msgget()返回的消息队列描述符。 msgp是指向由消息类型和字符数组所组成结构对象的指针,指定需 要发送的消息类型和消息正文。 msgsz给出消息正文mtext数组的实际字节大小,往往大于1。 msgflg规定相应的动作,比如IPC_NOWAIT动作标志要求当无内存 空间存储消息或者消息队列的长度已达限定值时,进程是等待还是 立即返回。 IE310, 4-170, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 消息队列的消息接收msgrcv()系统调用 从指定的消息队列中接收指定类型的消息。 ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg); struct msgbuf { long mtype; /* 消息类型,必须大于0 */ char mtext[1]; /* 消息正文 */ }; msqid是由msgget()返回的消息队列描述符。 msgp是指向缓冲区,指向由消息类型和字符数组所组成结构对象的指针,将用于存 放接收到消息的类型和消息正文。 msgsz给出上述接收缓冲区中为消息正文mtext数组所实际预留的字节大小。 msgtyp指定希望接收的消息类型,0表示消息队列中的第一个消息,大于0而且 msgflg&MSG_EXCEPT不为真表示消息队列中的第一个具有该类型的消息,大于0而 且msgflg&MSG_EXCEPT为真表示消息队列中的第一个非该类型的消息,小于0表示 消息队列中的第一个类型小于或等于其绝对值的消息。 msgflg规定相应的动作,比如IPC_NOWAIT动作标志要求当消息队列中尚没有指定 类型的消息时,进程是等待还是立即返回。 正常情况下返回实际拷贝的消息正文至mtext数组中的字节数。 IE310, 4-171, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 消息队列的控制/删除msgctl()系统调用 控制、查询指定的消息队列。 int msgctl(int msqid, int cmd, struct msqid_ds *buf); msqid是由msgget()返回的消息队列描述符。 buf是含有控制参数和查询结果的用于缓冲区地址。 cmd给出命令的类型: IPC_STAT要求将由msqid指定消息队列的数据结构信息拷贝到 由buf给出的缓冲区中。 IPC_SET要求按照buf指向的内容设置由msqid指定消息队列的数 据结构的若干值。 IPC_RMID要求立即删除由msqid指定的消息队列,各可能相关 的读阻塞等待、写阻塞等待进程均被唤醒并错返回。 IE310, 4-172, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 简单的消息发送示意sendmesg.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <linux/msg.h> #define MAXMSG 512 //定义消息缓冲区数据结构的消息正文部分的字节长度 struct my_msg { //定义消息缓冲区数据结构 long my_msg_type; char some_text[MAXMSG]; } msg; int main(int argc, char ** argv) { int msgid; //定义消息缓冲区内部标识 msgid = msgget(1234, 0666|IPC_CREAT); //创建消息队列 while (1) { puts("Enter some text(\"end\" to exit):");//提示键入内容 memset(msg.some_text, 0, MAXMSG); fgets(msg.some_text, MAXMSG, stdin); //读取标准输入 msg.my_msg_type=1; //设置消息类型 msgsnd(msgid, (struct msgbuf *) & msg, strlen(msg.some_text) + 1, 0); if (0 == strncmp(msg.some_text, "end", 3)) //”end”为结束标志 break; } } IE310, 4-173, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 简单的消息接收示意recvmesg.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <linux/msg.h> #define MAXMSG 512 //定义消息缓冲区数据结构的消息正文部分的字节长度 struct my_msg { //定义消息缓冲区数据结构 long my_msg_type; char some_text[MAXMSG]; } msg; int main(int argc, char ** argv) { int msgid; //定义消息缓冲区内部标识 msgid = msgget(1234, 0666|IPC_CREAT);//获取消息队列,必须与发送方的相匹配 while (1) { msgrcv(msgid, (struct msgbuf *) & msg, MAXMSG, 0, 0);//接收消息 printf("You wrote: %s", msg.some_text); //显示消息 if (0 == strncmp(msg.some_text, "end",3)) //”end”为结束标志 break; } msgctl(msgid, IPC_RMID, 0); //撤消消息队列 } IE310, 4-174, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 复杂的消息发送示意sendmesg_pro.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <linux/msg.h> struct my_msg { //定义消息缓冲区数据结构 long my_msg_type; float fVal; //数据 int bEnd; //结束标志 int nOrgSenderID;//可以继续有更多的其它数据 } msg; int main(int argc, char ** argv) { int msgid_req, msgid_resp, nCounter = 0;//定义消息缓冲区内部标识与计数器 msgid_req = msgget(4321, 0666|IPC_CREAT); msgid_resp = msgget(4322, 0666|IPC_CREAT); msg.my_msg_type=2; //设置消息类型 msg.bEnd = 0; while (0 == msg.bEnd) { msg.fVal = nCounter * 1.0;printf("客户请求计算%f的平方值\n", msg.fVal); msg.nOrgSenderID = nCounter; if (++ nCounter > 8) msg.bEnd = 1; //通知接收方这是最后一个请求 msgsnd(msgid_req, (struct msgbuf *) & msg, sizeof(msg) - sizeof(long), 0); msgrcv(msgid_resp, (struct msgbuf *) & msg, sizeof(msg) - sizeof(long), 0, 0); printf("客户得到所请求的平方值结果%f\n", msg.fVal); } msgctl(msgid_resp, IPC_RMID, 0); //撤消应答的消息队列 printf("平方计算客户进程退出运行\n"); } IE310, 4-175, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之消息队列) 复杂的消息接收示意recvmesg_pro.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <linux/msg.h> struct my_msg { //定义消息缓冲区数据结构 long my_msg_type; float fVal; //数据 int bEnd; //结束标志 int nOrgSenderID;//可以继续有更多的其它数据 } msg; int main(int argc, char ** argv) { int msgid_req, msgid_resp, nCounter; //定义消息缓冲区内部标识 msgid_req = msgget(4321, 0666|IPC_CREAT);//获取请求消息队列 msgid_resp = msgget(4322, 0666|IPC_CREAT);//获取应答消息队列 while (1) { msgrcv(msgid_req, (struct msgbuf *) & msg, sizeof(msg) - sizeof(long), 0, 0); msg.fVal *= msg.fVal; msgsnd(msgid_resp, (struct msgbuf *) & msg, sizeof(msg) - sizeof(long), 0); printf(" 发 回 nOrgSenderID 为 %d 的 平 方 值 请 求 结 果 %f\n", msg.nOrgSenderID, msg.fVal); if (0 != msg.bEnd) break; } msgctl(msgid_req, IPC_RMID, 0); //撤消请求的消息队列 printf("平方计算服务进程退出运行\n"); } IE310, 4-176, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 7. 共享内存 无名管道特点:简单方便,但工作在单向通信方式,且只能在创建 它的进程及其子孙进程之间共享。 消息缓冲特点:可以在任意进程之间通信,而且自带同步工具,使 用方便,但是信息复制消耗CPU的时间,不适宜于信息量大或操作 频繁的场合。 针对消息缓冲的缺点,推出共享内存机制,其特点是:改而利用内 存缓冲区直接交换信息,快捷、信息量大是其优点,但是不带同步 工具,同步和互斥问题需要各进程利用其他同步工具解决。 多个进程共享同一块物理内存空间,直接将共享的内存页面通过附 接,映射到相互通信的进程各自的虚拟地址空间中,从而使多个进 程可以直接访问同一个物理内存页面,没有中间环节,如同访问自 己的私有空间一样(但实质上不是私有的而是共享的)。 进程A地址空间 附接 进程B地址空间 共享内存 附接 IE310, 4-177, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 内部实现用数据结构 struct shmid_ds进程的用户空间对共享内存 区域的描述. 供用户操作的系统调用 创建shmget()系统调用。 挂接shmat()系统调用。 分离shmdt()系统调用。 控制/删除shmctl()系统调用。 IE310, 4-178, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) struct shmid_ds { struct ipc_perm shm_perm; int shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; unsigned short shm_cpid; unsigned short shm_lpid; short shm_nattch; }; /* /* /* /* /* /* /* /* 操作权限 */ 段大小字节数 */ 前次挂接时间 */ 前次分离时间 */ 前次修改时间 */ 创建进程之pid */ 最近操作进程pid */ 当前挂接数 */ IE310, 4-179, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) struct ipc_perm { key_t key; ushort uid; /* ushort gid; ushort cuid; /* ushort cgid; ushort mode; /* ushort seq; /* }; 所有者euid和egid */ 创建者euid和egid */ shmflg的低9位 */ 序号 */ IE310, 4-180, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 共享内存的创建shmget()系统调用 创建一个新的共享内存区域或者打开一个已经存在的共享内存区域。 int shmget(key_t key, int size, int shmflg); 返回共享内存区域的描述符, shmid。 key用于不同进程/线程间对同一个共享内存区域的约定、匹配,相 当于接头地点。 key_t ftok(const char *pathname, int proj_id); 将一个路径名与项目标识号转换为System V进程间通信的key值。 size指出所需共享内存区域的字节大小,实际的大小将上升至 PAGE_SIZE的边界。 shmflg是用户设置的标志和访问方式(权限)。如果 shmflg&IPC_CREAT为真,表示如果系统中没有以key命名的共享内 存区域,则创建该共享内存区域,否则,就返回该共享内存区域的 描述符。 fork()创建的子进程继承其父进程的共享内存区域。 exec()和exit()将自动分离所调用进程的所有共享内存。 IE310, 4-181, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 共享内存的挂接shmat()系统调用 向指定的消息队列发送一个消息(将该消息链接 到该消息队列的尾部)。 void *shmat(int shmid, const void *shmaddr, int shmflg); shmid是由shmget()返回的共享内存区域描述符。 shmaddr是用户希望挂接到的虚拟内存空间地址, NULL表示由系统选择一个合适的。 shmflg指定共享内存区域的访问权限。 返回实际所挂接到的虚拟内存空间中的地址。 IE310, 4-182, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 共享内存的分离shmdt()系统调用 把指定的共享内存从进程的虚拟内存空间 中分离。 int shmdt(const void *shmaddr); shmaddr指向先前由shmat()所成功挂接的 共享内存在虚拟内存空间中的地址。 IE310, 4-183, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 共享内存的控制/删除shmctl()系统调用 控制、查询指定的共享内存。 int shmctl(int shmid, int cmd, struct shmid_ds *buf); shmid是由shmget()返回的共享内存描述符。 buf是含有控制参数和查询结果的用于缓冲区地址。 cmd给出命令的类型: IPC_STAT要求将由shmid指定共享内存的数据结构信息拷贝到 由buf给出的缓冲区中。 IPC_SET要求按照buf指向的内容设置由shmid指定共享内存的数 据结构的若干值。 IPC_RMID要求立即删除由shmid指定的共享内存,实际在最后 一个进程的分离shmdt()之后进行。 SHM_LOCK/SHM_UNLOCK超级用户可以借此指定该共享内存是 否允许被交换至内存在硬盘上的交换区。 IE310, 4-184, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 简单的共享内存写入示意writeshm.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<linux/shm.h> struct wShm { //相关多方约定一致的数据结构(共享内存的存取格式) int nShm; //本例子实际使用的字段,这里约定为int char szBuf[1020]; //其它约定的多个字段 }; int main(int argc, char ** argv) { int shmid; //定义共享内存内部标识shmid * } struct wShm pShm; //定义附接共享内存的虚拟地址 int nCount = 0; //计数 shmid = shmget(2345,sizeof(struct wShm),0666|IPC_CREAT);//获取共享内存 pShm = (struct wShm *) shmat(shmid, NULL, 0);//附接到进程的虚拟地址空间 while (nCount <= 10) { //循环输入信息 pShm->nShm = nCount ++; sleep(2); } shmdt((char *) pShm); //断开附接 IE310, 4-185, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 简单的共享内存读取示意readshm.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<linux/shm.h> struct rShm { //相关多方约定一致的数据结构(共享内存的存取格式) int nShm; //本例子实际使用的字段,这里约定为int char szBuf[1020]; //其它约定的多个字段 }; int main(int argc, char ** argv) { int shmid; * } struct rShm pShm; //定义附接共享内存的虚拟地址 shmid = shmget(2345, sizeof(struct rShm), 0666|IPC_CREAT);//创建共享内存 pShm = (struct rShm *) shmat(shmid, NULL, 0);//附接到进程的虚拟地址空间 pShm->nShm = 0; while (pShm->nShm < 10) { printf("Current pShm->nShm is %d\n", pShm->nShm); sleep(1); } shmdt((char *) pShm); //断开附接 shmctl(shmid, IPC_RMID,0); //撤消共享内存 IE310, 4-186, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 未经互斥保护的共享内存的复杂结构写入示意writeshm_no_lock.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<linux/shm.h> struct shm { int nShm; char szMesg[256]; }; int main(int argc, char ** argv) { int shmid; //定义共享内存内部标识shmid struct shm * pShm; //定义附接共享内存的虚拟地址 int nCount = 0; //计数 shmid = shmget(3456, sizeof(struct shm), 0666|IPC_CREAT);//获取共享内存 pShm = (struct shm *) shmat(shmid, NULL, 0);//附接到进程的虚拟地址空间 while (nCount <= 10) { //循环输入信息 pShm->nShm = nCount ++; usleep(500000); //此间一般总是发生进程调度,被剥夺CPU sprintf(pShm->szMesg, "nCount的当前值是%d", nCount); usleep(1000000); } shmdt((char *) pShm); //断开附接 } IE310, 4-187, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 未经互斥保护的共享内存的复杂结构读取示意readshm_no_lock.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<linux/shm.h> struct shm { int nShm; char szMesg[256]; }; int main(int argc, char ** argv) { int shmid; struct shm * pShm; //定义附接共享内存的虚拟地址 shmid = shmget(3456, sizeof(struct shm), 0666|IPC_CREAT);//创建共享内存 pShm = (struct shm *) shmat(shmid, NULL, 0);//附接到进程的虚拟地址空间 pShm->nShm = 0; while (pShm->nShm < 10) { printf("Current pShm->nShm is %d, %s\n", pShm->nShm, pShm->szMesg); usleep(300000); } shmdt((char *) pShm); //断开附接 shmctl(shmid, IPC_RMID,0); //撤消共享内存 } IE310, 4-188, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 经互斥锁保护与条件变量触发的 具有复杂结构的共享内存访问示意 对共享内存中的数据完整性保护,包括互斥保护以及读者写者访问 规则的实现,需要借助于信号量,或者,互斥锁pthread_mutex_t 和条件变量pthread_cond_t Linux中互斥锁pthread_mutex_t和条件变量pthread_cond_t的使用: 最大限度地演示基于共享内存的保护与触发的有效性的例子 writeshm_lock_n_cond.c和readshm_lock_n_cond.c 启动测试的脚本文件(批处理文件)为big_shm.sh 使用共享内存的保护与触发的典型例子 在同一个进程范围之内的,可以使用该进程范围内的内存空间进行 跨不同进程的,则必须借助于共享内存进行 writeshm_pop.c和readshm_pop.c 启动测试的脚本文件(批处理文件)为pop_shm.sh 是课程设计任务的很好借鉴,不妨对之作简化删节使用 后面具体介绍一个简单的共享内存互斥访问例子 仅演示了互斥锁 Fastmutex在共享内存之中 IE310, 4-189, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 经互斥保护的共享内存的复杂结构写入示意writeshm_lock.c,测试脚本文件shm_lock.sh struct shmStruct { pthread_mutex_t fastmutex; int nShm; char szMesg[256]; }; int main(int argc, char ** argv) { int shmid; struct shmStruct * pShmStruct; int nCount = 0; //定义共享内存内部标识shmid //定义附接共享内存的虚拟地址 //计数 shmid = shmget(2345, BUFSIZ, 0666|IPC_CREAT); //创建共享内存 pShmStruct = (struct shmStruct *) shmat(shmid, 0,0);//附接到进程的虚拟地址空间 while (nCount < 5) { //循环输入信息 nCount ++; printf("\n\n%d, >>>> 调用pthread_mutex_lock...\n", nCount); pthread_mutex_lock(& pShmStruct -> fastmutex); printf("修改nShm为%d\n", nCount); pShmStruct -> nShm = nCount; printf("%d, 现在模拟CPU被剥夺,但由于互斥锁,读取方暂时无法读取\n", nCount); sleep(5); //此间一般总是被剥夺CPU,但由于互斥锁,读取方暂时无法读取 sprintf(pShmStruct -> szMesg, "nCount的当前值是%d", nCount); printf("修改szMesg为%s\n", pShmStruct -> szMesg); printf("%d, <<<< 调用pthread_mutex_unlock...\n", nCount); pthread_mutex_unlock(& pShmStruct -> fastmutex); printf("\n%d, 调用sleep(5)\n", nCount); sleep(5); } shmdt((char *) pShmStruct); } //断开附接 IE310, 4-190, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.5.5进程通信(Linux的进程通信之共享内存) 经互斥保护的共享内存的复杂结构读取示意readshm_lock.c ,测试脚本文件shm_lock.sh struct shmStruct { pthread_mutex_t fastmutex; int nShm; char szMesg[256]; }; int main(int argc, char ** argv) { int shmid; struct shmStruct * pShmStruct; int nCount = 0; //定义附接共享内存的虚拟地址 //计数 shmid = shmget(2345,BUFSIZ,0666|IPC_CREAT); //获取共享内存 pShmStruct = (struct shmStruct *) shmat(shmid, 0,0);//附接到进程的虚拟地址空间 pShmStruct -> nShm = 0; pthread_mutex_init(& pShmStruct -> fastmutex, NULL); while (pShmStruct -> nShm < 5) { printf(" %d, >>>> 调用pthread_mutex_lock...\n", nCount); pthread_mutex_lock(& pShmStruct -> fastmutex); printf(" %d, 从pthread_mutex_lock调用返回\n", nCount); printf(" 当前nShm为%d,当前szMesg为%s\n", pShmStruct -> nShm, pShmStruct -> szMesg); printf(" %d, <<<< 调用pthread_mutex_unlock...\n", nCount); pthread_mutex_unlock(& pShmStruct -> fastmutex); printf("\n usleep(500000); nCount ++; %d, 调用usleep(500000)\n", nCount); } pthread_mutex_destroy(& pShmStruct -> fastmutex); shmdt((char *) pShmStruct); //断开附接 shmctl(shmid, IPC_RMID,0); //撤消共享内存 } IE310, 4-191, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6 死锁 死锁的概念及其产生原因。 死锁的预防、避免和检测方法。 死锁发生后该如何解除和系统恢复。 IE310, 4-192, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6 死锁 4.6.1 死锁的概念 死锁(deadlock)的定义 指多个进程(或线程)因竞争使用资源而形成的一个僵 局。在这个僵局中,每个进程都占有一定的资源,都 申请使用已被其它进程占用而又不能剥夺的资源,所 有这些进程都进入了阻塞状态而不能继续运行。 两个或者多个进程因相互等待对方释放资源而被永远 阻塞的现象。 类似于交通阻塞现象、两队海盗各持半张藏宝图互不 相让。 IE310, 4-193, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.1 死锁(死锁的概念) 死锁(deadlock)的定义 车流 申请 R1 P2 占 占 有 有 车流 车流 申请 P1 R2 车流 IE310, 4-194, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.1 死锁(死锁的概念) 死锁产生的原因 系统资源不足。 进程推进的顺序不合适,比如,进程分别等 待对方释放其已经占用了的资源。 资源分配不当,比如,总体资源数量足够, 但是各进程均分配得到了一部分资源,但是 分配得到资源的数量均不够继续其正常的执 行。 IE310, 4-195, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.1 死锁(死锁的概念) 系统资源可分成两类: 可剥夺资源 在一个进程获得该类资源之后,可以被其它 进程或者系统所剥夺。 比如内存区、CPU等。 不可剥夺资源 当系统把该类资源分配给进程之后,再不能 强行收回,只能在进程用完之后自行释放。 比如磁带机、打印机等。 IE310, 4-196, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.1 死锁(死锁的概念) 产生死锁的四个必要条件 互斥条件(mutual exclusion) 不剥夺条件(no preemption) 资源申请者不能强行地从资源占有者手中夺取资 源,资源只能由占有者自愿释放; 保持而(部分)请求条件(request while holding) 涉及的资源是非共享的,每次只能给一个进程使用; 进程每次仅申请一部分资源,在请求新资源期间,继续保持 对已分配资源的占有; 循环等待(环路)条件(circular wait) 多个进程形成一个循环等待链,链中的各个进程恰恰请求已 经被其链中的前一个进程所占有的资源。 IE310, 4-197, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.1 死锁(死锁的概念) 死锁的处理方法 预防:具体做法是破坏产生死锁的四个必要条件之一。 避免:不事先采取限制去破坏产生死锁的条件,而是 在资源的动态分配过程中,用某种方法去防止系统进 入死锁状态,从而避免死锁的发生。 检测:事先并不采取任何限制,也不检查系是否进 入不安全区,允许死锁发生,但可通过检测机构及时 检测出死锁的发生,并精确确定与死锁有关的进程和 资源。 解除:与检测死锁相配套,用于将进程从死锁状态解 脱出来。常用的方法是撤消或挂起一些进程。以回收 一些资源,再将它们分配给处于阻塞状态的进程,使 之转为就绪状态。 IE310, 4-198, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6 死锁 4.6.2 死锁的预防 死锁的预防一般是从破坏导致发生死锁的必要条件着手, 只要能使四个必要条件其中的任何一个不成立,就可防 止死锁。 破坏互斥条件:把独享资源改变为共享资源,如利用假脱机技 术(用可共享使用的设备模拟非共享的设备); 破坏不剥夺条件:由操作系统强行剥夺某个进程已经获得的资 源,比如剥夺已分配的CPU; 破坏请求和保持条件:当不能获得新的资源时主动释放已经获 得的资源或者采用运行前静态分配策略(一次性将所需资源全部 申请到位); 破坏循环等待条件:采用资源的有序申请/分配策略(将资源编 号,各进程对于资源的请求只能按序号的递增顺序进行,比如 请求了R1才能请求R2),可能导致程序开发的复杂性。 IE310, 4-199, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.2 死锁(死锁的预防) 资源分配的两种方式 静态分配:在作业级实施 当一个作业运行前,将它要求的所有资源一次性分配给该作 业,直到该作业完成时释放其占用的所有资源,分配给作业 的资源伴随作业的整个运行过程。 缺点:效率太低 动态分配:在进程级实施 当一个进程要求使用某个(类)资源时,向系统提出资源的 请求,系统响应进程的请求将某种资源分配给进程,进程使 用完毕后立即释放该资源 优点:系统资源的利用率提高 缺点:有可能造成死锁 IE310, 4-200, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6 死锁 4.6.3 死锁的避免 要从根本上预防死锁是不现实的,因为这会导致资源 使用和进程执行的低效。事实上,死锁的产生跟每个 进程动态申请资源的过程息息相关。如果能掌握并发 进程中与每个进程有关的资源动态申请情况,同样可 以避免死锁的发生。 不妨定义系统的两种状态:安全状态与不安全状态, 分别对应于可以避免死锁和产生死锁的情况。 避免死锁的具体做法是: 为申请者分配资源前先判断系统在该次分配之后的状 态是否安全,若此次分配不安全,则拒绝分配;否则, 接受申请,分配资源。 IE310, 4-201, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 安全与不安全状态 安全状态是指在某一时刻,系统至少存在一 个安全序列<P1, P2, …, Pn>,按照此序列来 为每个进程分配所需资源,直到满足其最大 需求,使每个进程都能顺利地完成,则称此 时系统处于安全状态。 反之,称之为不安全状态。 IE310, 4-202, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 安全与不安全状态 比如,对于如下所示的系统,存在一个安全分配序列<P2, P1, P3> 进程号 总共需求资 已经分配资 系统剩余资 源数量 源数量 源数量 P1 10 5 P2 4 2 P3 9 2 3 IE310, 4-203, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 安全与不安全状态 在引入安全状态之后,要使系统不发生死锁,只需在 每次进行资源分配时,保证系统状态仍然是安全的。 当然,系统处于安全状态并不意味着一定不会出现死 锁。如果按不安全的序列进行资源的分配,就有可能 发生死锁。 因此,死锁避免的本质是在允许死锁存在的前提下, 确保每次资源分配之后,系统仍处于安全状态。 按照安全序列分配资源便能避免死锁的发生。 IE310, 4-204, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 银行家算法(问题描述) 一个银行家把他固定数量的资金借给若干客户。 这些客户对资金的要求如果能够满足,就能完成其交 易,把所借资金归还给银行家。 每个客户均能预先声明(比如年度用款计划)各自所需 的最大资金量,但是均将分成若干次进行借款,每次 仅提出部分资金量的借款申请。 如果银行家满足了特定客户对资金的最大需求量,则 该客户在完成资金运作后,将在有限的时间内归还全 部所借资金给银行家。 如果银行家不能满足特定客户对资金的最大需求量, 则该客户将不能完成交易,从而也就不能归还已经借 用的资金给银行家。 IE310, 4-205, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 银行家算法(解决办法) 银行家对客户的每次借款操作进行安全性判 断,确定自身的安全性(能否支持该客户的全 部借款,直到全部归还); 如果是安全的,则借款给该客户,否则暂不 借款。 客户可以被看做为进程。 资金可以被看作为资源。 银行家可以被看做为操作系统。 IE310, 4-206, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 银行家算法(解决办法) 10 剩余 2 1 3 已分配 待分配 已分配 待分配 已分配 待分配 已分配 待分配 PA 0 8 4 4 5 3 4 4 PB 0 3 2 1 2 1 PC 0 9 2 7 2 7 已完成 2 7 (a) 安全 序列 (b) (c) (d) 很多 <PB, PA, PC> <PB, PA, PC> 不安全状态 IE310, 4-207, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 银行家算法的扩展(算法中的数据结构) 可用资源向量Available[m] 整数Available[i]=t表示系统中尚剩余t个i类可用资源 最大需求矩阵Max[n×m] 整数Max[i, j]=k表示进程i最多需要k个j类资源 分配矩阵Allocation[n×m] 整数Allocation[i, j]=t表示进程i已经占用t个j类资源 需求矩阵Need[n×m](=Max-Allocation,系冗余信息) 整数Need[i, j]=t表示进程i还需申请t个j类资源 进程的请求矩阵Request[n×m] 整数Request[i, j]=t表示进程i当前请求t个j类资源 IE310, 4-208, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 银行家算法的扩展(算法) 当进程Pi提出资源申请Request[i, j]时执行如下算法: ① 若Request[i, j]≤Need[i, j],转②;否则错误返回 ② 若Request[i, j]≤Available[j],转③;否则进程Pi等待 ③ 假设系统分配了资源,则有: Available[j]=Available[j]-Request[i, j]; Allocation[i, j]=Allocation[i, j]+Request[i, j]; Need[i, j]=Need[i, j]-Request[i, j]; ④ 执行安全性算法检查。若系统新状态是安全的,则完成 正式分配;否则,取消本次试探性分配,恢复原有的资 源分配状态及其相关数据结构,进程Pi等待。 IE310, 4-209, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.3 死锁(死锁的避免) 安全性检测算法 1. 设置两个向量 2. Work[m]表示系统可提供给进程继续运行的资源数目,开始 执行安全性算法时,Work=Available。 Finish[i]表示系统是否有足够的资源分配给进程Pi,让它运行 完成。对于每个进程来说,其初始Finish[i]均为False。 从剩余进程集合中找到一个能满足下列条件的进程Pi: Finish[i]=False Need[i, 0~m-1]≤Work[0~m-1] IE310, 4-210, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 3. 4.6.3 死锁(死锁的避免) 安全性检测算法(续) 当进程Pi获得资源后,可以顺利执行完毕,并释放出 分配给的资源,所以需执行 Finish[i]=true 4. Work[0~m-1]=Work[0~m-1]+Allocation[i, 0~m-1] Goto (2) 如果所有进程的Finish[0~n-1]=true,则表示系统处于安 全状态;反之,系统处于不安全状态。 IE310, 4-211, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6 死锁 4.6.4 死锁的检测与解除 系统运行期间,可以对死锁进行相应的检测, 以对可能已经发生的死锁予以解除。 系统可以用资源分配图记录资源的请求和分 配信息。 然后可以定时地运行一个死锁检测程序。 死锁检测程序利用某种算法检查资源分配图 中是否有循环等待,以判断系统内是否发生 了死锁。 若检测到死锁,则设法加以解除。 IE310, 4-212, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 资源分配图 是一种描述系统中进程和资源的申请和分配 情况的有向图。 有向图的顶点为资源或进程。 从资源R到进程P的边表示R已经分配给P。 从进程P到资源R的边表示P请求资源R。 资源顶点上的点表示资源的个数。 IE310, 4-213, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 资源分配图 .. R1 P1 P2 R2 . IE310, 4-214, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 死锁的检测 通过简化资源分配图来实现。 要检测系统是否处于死锁状态,只需判断简 化后的资源分配图中是否形成环路。 死锁定理:系统处于死锁状态的充要条件是, 当且仅当该状态的资源分配图是不可完全简 化的。 IE310, 4-215, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 死锁的检测之资源分配图的简化过程 删除不处于等待状态的进程,即没有从该进 程出发的边。 如果一个进程的请求能得到满足,则删除从 其出发的边。 如果简化后的资源分配图中已不存在任何边, 则说明系统不会出现死锁。反之,如果资源 分配图中还存在一些边且这些边的指向形成 一个环路,则说明存在死锁。 IE310, 4-216, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 死锁的检测之资源分配图的简化过程 进程P2申请R1资源,R1资 源能满足。 同样,当进程P2获得了R1 资源并顺利执行完毕后, 将释放之前占用的R2资源, 即可以删除从R2资源出发 指向P2进程的边。 由于进程P1顺利得到R2资 源后也能执行完毕,因此, 也应该删除指向或被指向 进程P1的边。 简化结果如右所示。 .. R1 P1 P2 R2 . IE310, 4-217, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 死锁的解除 撤销全部死锁进程。立即结束全部死锁进程的执行, 根据需要重新启动操作系统。破坏性最大。 撤销部分死锁进程。有选择地撤销占有资源多的部分 进程,直到不存在死锁。选择撤销进程基于最小代价 原则进行。每撤销一个进程,调用检测死锁算法。如 Windows之任务管理器。 进程回退。让一个或多个进程回退到以前不存在死锁 的某个检测点。要求能保存的历史信息,实现进程的 自愿释放资源,缺乏可操作性。 资源剥夺。抢占某些死锁进程的资源给其它的死锁进 程。 IE310, 4-218, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.6.4 死锁(死锁的检测与解除) 死锁的解除之最小代价原则 到现在为止使用CPU的时间最少 进程的优先级最低 估计剩余的执行时间最长 到现在为止分配资源的总量最少 到现在为止产生的输出量最少 对其它进程的影响最小 IE310, 4-219, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7 线程 4.7.1 线程的引入 在传统的操作系统之中,进程既是系统资源分配的基 本单位,又是CPU可独立调度和分派的基本单位。 系统以进程为单位分配存放其映像的虚地址空间,执 行需要的主存空间代码,在任何时候只有一个执行控 制流。 进程的创建、撤消与切换,均具有一定的系统开销。 过多的进程数量,过于频繁的进程创建、撤销与切换, 必然带来极大的系统开销,反而会限制系统的总体并 发能力。 IE310, 4-220, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题 经常需要同时处理基于同一服务程序的多个 服务请求,比如多窗口订票系统、数据远程 采集系统等。 多个服务请求可能并行发生。请求的产生具 有一定的动态随机性。 对所有服务请求的处理可能需要基于共享的 数据资源对象,比如剩余机票和订座信息等。 IE310, 4-221, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题之第 一种处理方式 始终用一个服务进程顺 序处理所有的服务请求。 在当前请求的处理期间, 服务进程不会响应其它 请求。 系统开销最小。 系统响应最慢。 类似于一个上了年纪个 体户的pizza店。 比如一个烤箱 P IE310, 4-222, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题之 第二种处理方式 启用一个独立的服 务进程处理每一个 新的服务请求。 系统开销最大。 系统响应较快。 类似于一个有着好 多位上了年纪雇员 的pizza店。 比如多个烤箱 P1 P2 Pn ... IE310, 4-223, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题之第 三种处理方式 始终用一个服务进程处 理所有的服务请求。 服务进程对于所有系统 调用采用异步方式,以 并发处理多个请求。 系统开销最小。 系统响应最快。 类似于一个年轻人个体 户的pizza店。 比如多个烤箱 Zzz P IE310, 4-224, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题之第四种 处理方式 始终用一个服务进程处理所 有的服务请求。 服务进程对于所有系统调用 采用同步方式。 启用一个独立的服务线程处 理每一个新的服务请求。 系统开销较小。 系统响应较快。 控制逻辑相对简单。 类似于一个有着好多位上了 年纪大家庭成员共同经营的 pizza店。 比如多个烤箱 T1 T2 P Tn ... IE310, 4-225, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.1 线程(线程的引入) 多服务请求问题的高效解决方案 为解决此问题,人们想到将进程的两个属性分开。 进程: 线程是进程内相对独立的、CPU可独立调度和分派的执行单元或控制 流。线程: 进程基本上仍然作为系统资源分配的基本单位,但不再是CPU调度和分派的 基本单位。 进程相对拥有较多的资源,不对之进行频繁切换。 线程是调度执行的基本单位,但不是资源分配的基本单位。 线程从属于某个进程,是该进程的某个执行路线。 线程相对拥有较少的资源,使之成为CPU调度和分派的基本单位。 引入进程的目的是为了使多个程序并发执行,以改善资源利用率、提 高系统吞吐量。 引入线程则是为了减少实现程序并发执行所需的系统开销,提高并发 执行实现的系统效率。 使用线程的好处在于: 有多个任务需要处理机处理时,能够减少处理机切换的时间; 充分利用多处理机; 线程的创建和结束所需要的系统开销也比进程的要小得多; 改善程序结构。 IE310, 4-226, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7 线程 4.7.2 什么是线程 线程是进程内相对独立的、CPU可独立调度和分派的执 行单元或控制流。 线程是进程中的一个执行单元或控制流,每个执行单位 或控制流就是一个线程,它是可以被系统独立调度和分 派的基本单位。 线程自己基本不拥有系统资源,只拥有少量必不可少的 资源:程序计数器、一组寄存器、栈。 线程可与同属一个进程的其它线程共享进程所拥有的全 部资源。 一个线程可以创建和撤消另一个线程。 同一进程中的多个线程之间可以并发执行。 同一个进程内的线程之间的切换不需要通过进程调度。 IE310, 4-227, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7 线程 生活中的多线程例子 纯净水生产中瓶子和纯净水的制造。 可以由两个不同的公司分别进行制造。 也可以由一家公司中的两个不同部门来分别进行 制造。 学校中的若干事务 多个职能部门:服务接口复杂、高效合作 后勤、安保事务外包:服务接口清晰、减少包袱、 减少扯皮 IE310, 4-228, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.2 线程(什么是线程) 多线程进程的结构 在单线程进程结构中,进程由进程控制块(PCB)、用 户地址空间和堆栈组成。 在多线程进程结构中,每个线程都有自己独立的线程 控制块(TCB)和堆栈。 线程由与其相关的堆栈、寄存器和线程控制块TCB组 成。 线程控制块一般包含线程执行时各寄存器值、堆栈、 线程的优先级、线程状态、线程信号屏蔽等信息。 IE310, 4-229, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.2 线程(什么是线程) 多线程进程结构图示意 IE310, 4-230, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7.3 线程(线程的状态) 4.7.3 线程的状态 与进程类似,线程也是一个动态的概念,也有一 个从创建到消亡的过程,它也具有以下几个基本 状态: 就绪态:具备执行条件,等待调度。 运行态:正占有CPU 处于运行中。 阻塞态:等待某个事件发生。 结束态:线程结束执行,释放其寄存器上下文和堆栈 的内容。 线程的控制包括线程创建、线程终止、线程阻塞 等。 IE310, 4-231, xqfang.sjtu@gmail.com 4.7.4 线程(进程和线程的比较) Shanghai Jiao Tong University 4.7.4 进程和线程的比较 拥有资源 进程是拥有资源的基本单位,相对较多。 线程拥有的资源很少,一般只有程序计数器(PC)、一 组寄存器和堆栈。 局部变量存放在哪里? 同一个进程中的所有线程可以共享该进程的资源,包 括为其分配的地址空间。 进程一般不能访问其它进程中的资源(除共享资源), 进程的地址空间相互独立。 一个进程内的线程对于其它进程来说是不可见的。 IE310, 4-232, xqfang.sjtu@gmail.com 4.7.4 线程(进程和线程的比较) Shanghai Jiao Tong University 并发性 不仅进程之间可以并发执行,而且在一个进 程中的多个线程之间亦可并发执行。 多线程系统的并发性得到了很大的提高,能 更有效地使用资源,提高系统的吞吐量。 IE310, 4-233, xqfang.sjtu@gmail.com 4.7.4 线程(进程和线程的比较) Shanghai Jiao Tong University 调度与切换 在单线程多进程系统中,进程既是资源分配的基本单 位,又是系统调度与切换的基本单位。 而在多线程系统中,进程只是系统资源分配的基本单 位,而线程才是系统调度和切换的基本单位。 线程的切换非常迅速且开销小。 对进程的调度与切换总是要比线程的调度与切换花费 更多的系统开销。 同一个进程中的线程调度与切换不会引起进程的调度 与切换。? 只有当一个进程的线程直接切换到另一进程的线程时, 才引起进程的调度与切换。 IE310, 4-234, xqfang.sjtu@gmail.com 4.7.4 线程(进程和线程的比较) Shanghai Jiao Tong University 通信 进程间通信可通过多种方式进行,包括共享内存。 在同一个进程中的各个线程,都可以共享该进程所拥 有的资源,它们具有相同的地址空间(进程的地址空 间),可以访问该空间中的每一个虚地址,还可以访 问进程所拥有的已打开文件、定时器、信号量等。 线程间通信主要通过进程内公共存储空间(全局变量) 的形式,直接读写进程的数据段即可完成通信。 一旦某线程对其虚拟空间做任何改变,比如全局变量, 其它线程就会马上感知到。 总体上,线程间通信的效率较高。 由于进程内资源的充分共享而不利于对资源的管理和 保护。 IE310, 4-235, xqfang.sjtu@gmail.com 4.7.4 线程(进程和线程的比较) Shanghai Jiao Tong University 系统开销 由于在创建或撤消进程时,系统都要为之分 配或回收资源,如内存空间、I/O设备等。 对线程的创建或撤销所涉及的资源相对较少, 因为线程使用的其它资源是被分配给进程而 被其内部所有线程所共享的。 因此,操作系统创建或撤消进程所付出的开 销将明显地大于在创建或撤消线程时的开销。 比如:裁撤/创建社会上的一个公司内的一个 部门;裁撤/创建社会上的一个公司。 IE310, 4-236, xqfang.sjtu@gmail.com 4.7 线程 Shanghai Jiao Tong University 4.7.5 常见的多线 程关系 各线程成为应用 任务处理流程中 的前后继,之间 往往可以归纳为 生产者和消费者 之间的关系 人机 人机 T1 人机 P T3 T2 网络 网络 IE310, 4-237, xqfang.sjtu@gmail.com 4.7.5 线程(常见的多线程关系) Shanghai Jiao Tong University 线程间的同步 采用与进程间同步一样的方式,比如信号量、 互斥锁(短期/预期互斥锁定,目的是保护)、条 件变量(不确定时间长度的等待、目的是触发与 通知)等。 比如: 想读写教室的空闲标志。 医生等病人到达;病人等骨髓配型成功。 线程间的通信 进程内公共存储空间(全局数据)的形式,直接读 写进程的数据段即可完成通信。 IE310, 4-238, xqfang.sjtu@gmail.com 4.7.6 线程(线程的实现方式) Shanghai Jiao Tong University 4.7.6 线程的实现方式 内核级线程 由操作系统内核管理,维护各线程的各种管理表格,根据需要 进行线程的创建和撤销。 由操作系统负责线程在处理机的调度与切换,实现在同一处理 机上的并发执行,甚至在多处理机上的并行执行。 内核级线程的切换时间相对较长,系统开销也相对较大。 但不管怎样,线程切换时间还是小于进程的切换时间。 当进程中的一个线程被阻塞时,该进程中的其它线程仍可运行。 同一进程中的线程切换要有两次态的转换(用户态→内核态→用 户态),因为线程的调度运行在内核态,而应用程序运行在用户 态。 比如,生产队(操作系统)直接安排管理每一个队员(线程)的工作。 IE310, 4-239, xqfang.sjtu@gmail.com 4.7.6 线程(线程的实现方式) Shanghai Jiao Tong University 用户级线程 用户级线程不依赖于操作系统核心,它由应用程序自身来支持。 线程的管理全部在进程的用户空间中进行。 应用进程利用线程库来提供创建、同步、调度和管理线程的函 数来控制用户线程。 线程调度在应用软件内部进行,由用户自行选择调算法和过 程,通常采用非抢先式和更简单的规则。 线程的调度算法与操作系统的调度算法无关。 线程调度只能进行线程上下文的切换而不能进行处理机的切换, 当然切换速度是特别的快。 当进程中某线程执行一个系统调用而被阻塞时,会导致本进程 的其它线程也阻塞。 不便利用多处理器,因为每次只有一个进程的一个线程在一个 CPU上运行。 比如,生产队(操作系统)分配土地(内存等资源)给每一个家庭 (进程) ,由各家庭(进程)自行安排管理其所属家庭成员(线程)的 具体工作。 IE310, 4-240, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 4.7 线程 4.7.7 Linux线程系统调用 创建线程 int pthread_create(pthread_t*thread, pthread_attr_t*attr, void* (*start_routine)(void *), void*arg); thread——返回创建的线程标识符ID,是个指向线程标识符的指针; attr——设置线程的属性,如果使用NULL则表示为系统默认属性; start_routine——线程执行函数的地址(线程没有自己的存储空间,故 线程所执行函数需要在创建它的进程中加以定义),该函数必须具有 void*返回值; arg——线程执行函数的参数,如果不带参数则可以使用NULL 。 返回值:成功返回0 出错则返回错误码 IE310, 4-241, xqfang.sjtu@gmail.com 4.7.7 线程(Linux线程系统调用) Shanghai Jiao Tong University 线程终止 int pthread_exit(void*retval); retval——传递给创建它的父线程的终止信 息。 功能:终止一个线程。 返回值:成功返回0; 出错则返回错误码 IE310, 4-242, xqfang.sjtu@gmail.com 4.7.7 线程(Linux线程系统调用) Shanghai Jiao Tong University 等待线程终止 int pthread_join (pthread_t th, void **thread_return); th——被等待的线程标识符, * thread_return——用户定义的指针,它可以用来存储被 等待线程的终止信息。类似于wait( [int*stat_addr,] 0 ) 中的*stat_addr。如果不需要取终止信息则可使用NULL。 功能:回收终止的线程,类似于回收进程waitpid()。调用 它的函数将一直等待到被等待的线程结束为止。当函数 返回时,被等待线程的资源被收回。 返回值:成功返回0 出错则返回错误码 IE310, 4-243, xqfang.sjtu@gmail.com 4.7.7 线程(Linux线程系统调用) Shanghai Jiao Tong University 例子gcc –o mltithrd mltithrd.c -lpthread #include<pthread.h> #include<stdio.h> #include<string.h> void * thread_fuction(void*arg) //定义线程执行函数 { int nI = 0, nJ, nK; nJ = ((char *)arg)[6] - '0'; while (nI < 4) { for (nK = 1; nK < nJ; nK ++) printf(" "); printf("%s开始睡%d秒钟...\n", arg, nJ);fflush(stdout); sleep(nJ); nI ++; } for (nK = 1; nK < nJ; nK ++) printf(" ");printf("%s开始退出!!!\n", arg);fflush(stdout); } int main(int argc, char ** argv) /*主线程*/ { int nRet, i, j; pthread_t threads[5]; //定义多线程标识符数组 char message[10]={"thread1","thread2","thread3","thread4","thread5"}; for (i=0; i<5; i++) { nRet = pthread_create(&threads[i], NULL, thread_fuction, message[i]); if (0 != nRet) { perror("线程创建失败"); exit(1); } } for(i=0;i<5;i++) { pthread_join(threads[i], NULL); //等待5个子线程终止 for (j = 0; j < i; j ++) printf(" ");printf("线程%d已经退出!!!\n", i+1); } printf("主线程开始退出!!!\n"); } IE310, 4-244, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 小结一 应用程序均可能对I/O等外设进行操作, 为了避免发生CPU的等待,期间转而执行 其它就绪的程序,从而出现了程序的并发 执行。 进程既是系统资源分配的基本单位,又是 CPU可独立调度和分派的基本单位。 进程是程序的一次执行过程,是一个活动 的实体。具有并发、动态、异步、结构化 等特性。 IE310, 4-245, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 小结二 进程至少要有三种基本状态:运行态、就绪 和阻塞态。 进程的控制包括进程的创建、进程的撤销、进 程的阻塞、进程的唤醒。 进程之间往往涉及资源的共享,但是需要做到 协调访问,从而出现了进程间的互斥与同步, 一般采用信号量机制实现。 进程之间还具有若干高级的通信手段,比如共 享内存、消息队列、管道等。 IE310, 4-246, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 小结三 进程(线程)间的互斥与同步,可能引起死锁, 从而涉及死锁的预防、避免和检测和解除。 为了降低实现并发控制所需的(进程切换)系统 开销,进一步引入了线程的概念。 一个进程内部可以有多个线程。 每个线程是进程内部的一个独立的代码执行控 制流。 在一个进程内部各线程的切换期间,并不涉及 进程的切换。 IE310, 4-247, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 1. 2. 3. 4. 5. 6. 7. 8. 什么是进程?进程与程序有什么区别? 说明进程的结构、特征和基本状态。 试说明进程在三个基本状态之间转换的典型原因? 什么是进程控制块?它有什么作用? Unix进程映像由哪几部分组成?其PCB又由哪几部分 组成? 下列进程状态的变迁哪些是可能的,哪些是不可能的? A. 等待态→运行态 B. 运行态→等待态 C. 等待态→就绪态 D. 就绪态→等待态 Unix中进程状态有哪些?处于僵死状态的进程其状态 还会发生改变吗? “若无进程处于运行状态,则就绪队列和等待队列均 为空。”这句话对吗? IE310, 4-248, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 9. 10. 11. 12. 13. 当进程状态发生变迁时,在哪些情况下,进程状态的变化会引起操作系 统进程调度程序的执行? 在使用fork()创建一个子进程后,该系统调用的返回值是什么?其含义是 什么? 当系统调用exit()被执行后,如果在该语句后面还有其它语句,那么它们 还会执行吗?为什么? 给出下列程序执行后几种常见的输出结果。 int main(int argc, char ** argv) { int p; int x=0; while (-1 == (p=fork())); if (p==0) { x=1; printf(”M%d”,x); } else { printf(”N%d”,x); } } 为什么需要对创建的子进程加载属于它自己的可执行程序? IE310, 4-249, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 14. 15. 如果使用4个fork()创建子进程,其间不用条件分支将 父子进程的程序空间隔开,那么,该程序运行后,系 统将会为它创建多少个进程?画出其进程家族树。 如果父进程创建3个子进程,其父子关系如下左图,其 程序结构如何?如果父进程创建2个子进程,其中子进 程P2又创建孙子进程P3,如下右图,其程序又如何? 给出描述这两种情况的程序。 父 父 P P 1 P P 2 3 P 1 2 P 3 IE310, 4-250, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 16. 17. 18. 19. 20. 21. 什么是临界区和临界资源?临界区应遵循的准 则是什么? 同步问题与互斥问题的主要区别是什么? 已经有PV操作可用做同步工具,为什么还要有 消息传递机制? 管道通信的使用条件是什么? 消息缓冲通信中发送进程与接收进程之间的同 步由谁实现?共享内存通信中又由谁实现? Linux操作系统提供的进程通信机制有哪些种 类?它们各自的特点是什么? IE310, 4-251, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 22. 23. 24. 在信号量S上作P、V操作时,S的值发生变化,当S>0、 S=0、S<0时,它们的物理意义分别是什么? 桌上有一只盘子,最多可以容纳5个水果,每次仅能放 入或取出1个水果。爸爸向盘子中放苹果(apple),妈 妈向盘子中放橘子(orange),两个儿子专等盘子中的 橘子,两个女儿专等吃盘子中的苹果。试用信号量和P、 V操作编写实现爸爸、妈妈、儿子、女儿间正确工作的 算法。 在X、Y两点之间是一段南北的单车道,当X、Y之间有 车辆在行驶时,同方向的车辆可以同时进入XY段,但 另一方向的车必须在XY段外等待;当X、Y之间无车辆 行驶时,到达X点(或Y点)的车辆可以进入XY段,但不 能同时从X点和Y点驶入、当某方向在XY段行驶的车辆 驶出了XY段且暂无车辆进入XY段时,应让另一方向等 待的车辆进入XY段行驶。请用信号量和P、V操作实现 其管理算法。 IE310, 4-252, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 25. 26. 27. 设有n个进程共享一个互斥段,如 果: 每次只允许一个进程进入互斥段; 每次最多允许m个进程(m≤n)同时 进入互斥段。 试问:所采用的互斥信号量初值是 否相同?信号量值的变化范围如何? 设有4个合作进程P1、P2、P3、P4, 它们的同步关系如下图所示,试用 信号量的P、V操作实现其同步。 n个进程共享m个资源,每个进程一 P1 次只能申请或释放一个资源,每个 进程最多的资源请求数为m个,所 有进程总共需求的资源数小于m+n 个,证明该系统此时不会产生死锁。 P2 P4 P3 IE310, 4-253, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 课后任务(书面作业/择机上交) 28. 29. 30. 31. 32. 什么是死锁?死锁产生的必要条件有哪些?操 作系统如何规避死锁? 操作系统对于资源的分配有哪两种基本方式? 静态分配与有序分配分别破坏的是死锁必要条 件中的哪个条件? 如果系统中有5个进程,它们对于某类独占资 源的需求皆为8个,操作系统对于资源的分配 采用需要多少分配多少的方式。试问系统至少 需要多少个该类资源才不会产生死锁? 什么是线程?为什么要引入线程这一概念?线 程与进程有哪些相同和不同之处? IE310, 4-254, xqfang.sjtu@gmail.com Shanghai Jiao Tong University 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 课后课程设计任务 自学第9章Linux应用基础; 自学第10章Linux的操作命令界面与内核编译; 练习实验1 实验环境与Linux操作系统的安装(虚拟机、红帽子Linux安装、 虚拟网卡的安装); 练习实验2-1 Linux的键盘命令; 练习实验2-2 Linux的图形界面; 练习实验2-3 Linux的批处理; 练习实验3-1 Linux进程的创建与父子进程同步; 练习实验3-2 Linux子进程映像的重新加载; 练习实验4-1 Linux软中断通信; 练习实验4-2 Linux管道通信; 练习实验4-3 Linux消息缓冲通信; 练习实验4-4 Linux共享存储通信; 练习实验5 Linux信号量与P、V操作; 练习实验6 动态申请内存(malloc/realloc/free); 练习实验7 Linux文件系统操作(open/creat/read/write/chmod/close) ; 联系TCP连接访问 IE310, 4-255, xqfang.sjtu@gmail.com ...
View Full Document

Ask a homework question - tutors are online