type
status
date
slug
summary
tags
category
icon
password
从这里开始下面介绍的都是单核的多道批处理系统
2.0 本章概念
- 原语:操作系统内核或微核提供核外调用的过程或函数称为原语。原语是由若干条指令构成,用于完成特定功能的一段程序。原语在执行过程不允许被中断。
- 原子操作:执行中不能被其它进程(线程)打断的操作就叫原子操作。当该次操作不能完成的时候,必须回到操作之前的状态,原子操作不可拆分。(原语跟原子操作应该没有什么区别,原语的执行就是一次原子操作)
- 并发性与并发进程:在一个进程的工作没有完成之前,另一个进程就可以开始工作,这些进程就称为可同时执行的。或者称它们具有并发性,并且把可同时执行的进程称为并发进程。(也就是可以异步执行不需要同步的)
- 无关:如果一个进程的执行不影响另一个进程的执行结果,也不依赖另一个进程的进展情况,即它们是各自独立的,则称这些进程相互之间是无关的。
- 交互:如果一个进程的执行要依赖其他进程的进展状况,或者可能会影响其他进程的执行结果,则说这些进程是有交互的。(所以无关和交互应该是反义的,只有当进程之间是无关的,才能进行完全并发,就是完全不需要同步)
- 进程互斥:多个进程不能同时使用同一个资源,某个进程使用该资源时,其他进程必须等待。(因为资源的互斥而造成的进程互斥)——银行存款问题
- 进程同步:多个进程的调用存在时序关系,某些进程的执行必须先于另一些进程。——生产者消费者问题(实际上生产者消费者既要同步也要互斥)
- 进程通信:多个进程之间传递消息。
- 临界区与临界资源:某些资源必须互斥使用,如打印机、共享变量、表格、文件等。这类资源又称为临界资源,访问临界资源的那段代码称为临界区。任何时刻,只允许一个进程进入临界区,以此实现进程对临界资源的互斥访问(临界资源在第一章中已经提过了)
2.1 前趋图和程序执行
- 程序执行方式
- 顺序执行——单道批处理系统(因为单道批处理系统只能在内存中的单个进程执行结束之后才能执行其他的进程)
- 并发执行——多道批处理系统(内存中有多个程序一起执行)
- 应用级并发是指若干应用程序的并发执行。
- 系统级并发是指操作系统自身软件的并发执行。
- 前驱图:前趋图(Precedence Graph)是一个有向无循环图,记为DAG(Directed Acyclic Graph),用于描述进程之间执行的前后关系。
- 注意:前驱图中不能存在循环(如果存在循环的话就表示两个过程相互依赖,就相当于是出现了死锁)
- 节点:表示一条语句、一个程序段等
- 初始节点:没有前驱
- 终止节点:没有后继
- 程序的顺序执行及其特征
- 程序的顺序执行:若一个程序由若干程序段(即操作)组成,而这些操作必须按照某种先后次序执行,这类执行过程就是程序的顺序执行。
- 用前驱图表示的顺序执行实例如下:
- 程序顺序执行的特点:
- 顺序性:处理机的操作严格按照程序所规定的顺序执行。
- 封闭性:程序运行时独占全机资源,程序一旦开始执行,其执行结果不受外界因素影响。
- 可再现性:只要程序执行时的环境和初始条件相同,都将获得相同的结果。(不论程序是从头到尾不停顿地执行,还是“停停走走”地执行。因为就算走走停停地执行,cpu仍然是在处理同一个任务,并不会像多道那样在停的时候去处理其他的进程)
- 程序的并发执行及其特征
- 用前驱图表示的并发执行实例如下:
- 程序并发执行的特征
- 间断性:由于它们共享系统资源,以及为完成同一项任务而相互合作,致使在这些并发执行的程序之间,形成了相互制约的关系,导致并发程序具有“执行——暂停——执行”这种间断性的活动规律。
- 失去封闭性:是多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行已失去了封闭性。
- 不可再现性:程序在并发执行时,由于失去了封闭性,导致不可再现性 。(因为程序执行的环境不仅仅是由当前任务本身决定,还受到其他的任务影响)
2.2 进程的描述
这里的逻辑应该是——先总体介绍进程,然后介绍进程实体的组成部分,但是由于程序和数据没什么好介绍的,都是老生常谈的东西了,所以这里就只介绍一下PCB
- 进程的定义
- 进程是程序的一次执行。
- 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。(阿巴阿巴,大概意思就是第一个定义吧)
- 进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位(正如之前所说的一样,CPU上执行的是一个进程,或者是一个内核级线程,所以进程也是一个调度的独立单位。最好说进程是一个资源分配的基本单位)。
- 进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位(背这个)
- 进程实体的组成:由程序段、相关的数据段和PCB三部分构成了进程实体。
- 进程的特征(在多道系统中的特征)
- 动态性:进程的实质是进程实体的一次执行过程,因此,动态性是进程的最基本的特征。(因为这个特征甚至是与操作系统无关的)
- 动态性表现:“它由创建而产生,由调度而执行,由撤消而消亡”。可见,进程实体是会改变的,是有一定的生命期,所以进程是动态的。(程序是一组有序指令的集合,其本身并不具有运动的含义,因而是静态的。在引论里面也说了,程序是一个静态实体)
- 并发性:多个进程实体同存于内存中,且能在一段时间内同时运行。(也就是:在多道系统中,进程可以并发)
- 独立性:进程实体是一个能独立运行、独立分配资源和独立接受调度的基本单位;
- 异步性:进程按各自独立的、不可预知的速度向前推进,或说进程实体按异步方式运行。
- 其中第一个特征可以说是与操作系统无关的,后三个特征就是与操作系统有关的了,这里指的是多道系统
- 进程的状态
- 进程的三种基本状态(说基本是因为还没有算上挂起。这三种基本状态简称为三态)
- 就绪(Ready)状态:当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。
- 执行状态:进程已获得CPU,其程序正在执行。
- 阻塞状态:正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,把这种暂停状态称为阻塞状态,有时也称为等待状态。
- 进程的三态转换图如下:
- 进程的五种状态:进程的五态就是在三态(运行、就绪、阻塞)的基础上加上了创建状态以及终止状态
- 创建状态:当一个新进程刚刚建立,还未将其放入就绪队列时的状态,称为创建状态。
- 终止状态:当一个进程已经正常结束或异常结束,操作系统已将其从系统队列中移出,但尚未撤消,这时称为终止状态
- 进程的五态转换图如下:
- 进程的七种状态:进程的七态就是在五态的基础上加上了阻塞状态以及就绪状态对应的挂起状态,也就是增加了静止就绪以及静止阻塞状态,并且将原来的就绪状态以及阻塞状态修改为活动就绪以及静止就绪。
- 挂起与激活:
- 当出现了引起进程挂起的事件时,用户请求将自己挂起,或者父进程请求挂起自己的子进程,这时使用挂起原语Suspend( )。挂起原语检查进程状态,如果处于活动就绪状态就改为静止就绪;如果处于活动阻塞就改为静止阻塞。
- 当发生激活事件后,系统利用激活原语Active( ) 将指定进程激活。激活原语将进程从外存调入内存,然后检查进程状态。
- 挂起与阻塞的区别:阻塞状态是进程正在等待事件;挂起状态是进程被换出内存
- 造成挂起的原因
- 终端用户的请求。(比如我自己挂起了虚拟机,这个时候虚拟机就不会占用内存了)
- 父进程请求。
- 负荷调节的需要。当实时系统中的工作负荷较重,把一些不重要的进程挂起,以保证系统能正常运行。(也就是给内存腾空间)
- 操作系统的需要。操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。(防止在统计进程执行的过程中出现了其他进程修改了相关信息)
- 被挂起进程的特征
- 不能立即执行(因为被挂起的进程是在外存上的)
- 被挂起的进程可能也在等待某事件发生,若是,则阻塞条件独立于挂起条件,即使阻塞事件发生,该进程也不能执行(也就是静止与否跟就绪阻塞与否是独立的,这个时候如果时间发生的话也只是从静止阻塞变成了静止就绪)
- 使之挂起的进程为:自身、其父进程、OS(这里应该再包含一个终端用户进程)
- 只有挂起它的进程才能使之由挂起状态转换为其他状态(当然除了自己。自己虽然能挂起自己,但是如果把自己挂起了,自己就不在内存中了,就更不可能把自己激活了。这个时候就只能由其他的进程来将这个进程激活)
- 与挂起相关的状态转换
- 挂起原语(Suspend):活动就绪->静止就绪、活动阻塞->静止阻塞
- 激活原语(Active):静止就绪->活动就绪、静止阻塞->活动阻塞
- 进程的七态转换图如下:
- 进程与可执行程序的区别
- 程序是永存的(也就是程序是静态的);进程是暂时的(进程是有生命周期的),是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
- 程序是静态的观念,进程是动态的观念;
- 进程具有并发性,而程序没有;
- 进程是竞争计算机资源的基本单位(或者说计算机资源分配的基本单位),程序不是。
- 进程和程序不是一一对应的:一个程序可对应多个进程即多个进程可执行同一程序(就好像web编程中的客户端会拉好几个起来一样);一个进程可以执行一个或几个程序。
- 进程控制块PCB
- PCB的作用:进程控制块的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位—进程。在进程的整个生命期中,操作系统总是通过PCB对进程进行控制的(如创建一个进程的本质上就是操作系统创建了一个进程的PCB)。所以说,PCB是进程存在的唯一标志(其实一个主要的原因还是PCB是常驻内存的,当进程被挂起的时候程序和数据都会被移到外存,但是PCB不会)
- PCB的内容:
- 进程标识符信息:进程标识符用于唯一地标识一个进程。一个进程通常有两种标识符
- 内部标识符。为每一个进程赋予一个唯一的数字标识符,通常是进程的序号。设置内部标识符主要是为了方便操作系统使用。(操作系统使用的进程ID)
- 外部标识符。它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。(用户使用的进程ID)
- 处理器状态信息:处理机状态信息主要是由处理机的各种寄存器的内容组成的(可以说就是进程的现场了。想想嵌入式操作系统上学的现场的内容)
- 通用寄存器,又称为用户可视寄存器。
- 指令计数器(PC),其中存放了要访问的下一条指令的地址。
- 程序状态字PSW,其中含有状态信息,如条件码、执行方式、中断屏蔽标志等
- 用户栈指针(SP),用于存放系统调用参数及调用地址。栈指针指向该栈的栈顶。
- 进程调度信息
- 进程状态。指明进程的当前状态。
- 进程优先级。
- 进程调度所需的其它信息。如:进程已等待CPU的时间总和、进程已执行的时间总和等;
- 事件。是指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
- 进程控制信息
- 程序和数据的地址——进程的程序和数据所在的内存或外存地址。(堆栈地址等等)
- 进程同步和通信机制——实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等。
- 资源清单——进程所需的全部资源及已经分配到该进程的资源的清单;
- 链接指针——本队列下一个进程的PCB的首地址。
- PCB的组织形式
- 线性方式:把系统中所有的PCB都组织在一张线性表中(这个意思是将所有不论是什么状态的进程的PCB都放到同一个线性表中)
- 如下图:
- 链接方式:把具有同一状态的PCB,用其中的链接指针(上面介绍过,就是指向本队列中下一个PCB的地址)链接成一个队列。
- 如下图:
- 索引方式:相同状态的进程PCB组织在一张表格中,系统根据所有进程的状态建立几张索引表,系统分别记载各PCB表格的起始地址(这个跟前两个的本质区别就是,他已经摆脱了在PCB块上建立线性表。这个就已经有点像uCOS的组织方式了,比如说根据位图就能查询到在就绪队列中的PCB,这个时候位图应该就是一个索引表)
- 如下图:
- 因为进程的主要操作就是插入和删除,因此,链接方式使用更多一些。
当然这里只是说了可以根据进程的不同状态来分队列。还可以再进一步细分,比如在阻塞队列中根据不同的阻塞事件进一步划分队列,这样生成的PCB组织形式也是一个链接方式
2.3 进程控制
- 进程控制的主要内容:根据进程的七状态,有以下几个
- 创建一个新进程,终止进程(对应进程七状态中的创建状态和终止状态)
- 进程运行中的状态转换(对应进程七状态中剩下的状态转换)
- 操作系统内核:在第一章中有说到现代OS都是微内核结构
- CPU工作模式:CPU有两种工作模式,即特权模式和用户模式。
- 只有操作系统能够工作在特权模式上,这个模式可以直接访问硬件,执行特权指令
- 用户程序都工作在用户模式,在这种模式工作的CPU只能执行基本的指令,当用户程序想干些关键的操作时,他会向操作系统请求,由操作系统帮他完成,即"系统服务"
- CPU(处理器)的执行状态:系统态和用户态。
- 用户态:用户程序执行的状态,只能执行规定的指令,访问规定的寄存器和存储区。
- 系统态:也称“核心态”或“管态”,OS运行的状态,能执行一切指令,访问所有的寄存器和存储区
- 所以CPU的工作模式和执行状态应该是对应的。进程控制是操作系统的功能,而不是用户希望实现的,也不能移动到用户区(因为进程控制是一个操作系统必要的部分),所以进程控制控功能运行在系统态
- 进程的模式和类型:对应CPU的不同状态,进程就被划分为两大类
- 一类是系统进程,只运行在内核模式,执行操作系统代码;
- 另一类是用户进程。
- 内核的功能:需要注意的是,内核只是操作系统的一部分,因为有一部分操作系统的功能在现代是被放在用户空间的是,内核中放的都是最重要、最基本的功能
- 支撑功能
- 中断处理
- 时钟管理
- 原语操作
- 资源管理功能(背这个不如直接背操作系统的主要功能)
- 进程管理
- 存储器管理
- 设备管理
- 进程的创建与撤销
- 进程图:进程图是用于描述一个进程的家族关系的有向树。子进程可以继承父进程所拥有的资源。当子进程被撤消时,应将其从父进程那里获得的资源归还给父进程。在撤消父进程时,也必须同时撤消其所有的子进程。
- 进程图实例如下:
- 引起创建进程的事件
- 用户登录。(一般而言是对每一个用户都创建一个进程来负责)
- 作业调度。(在外存上的才叫作业,当调度一个新作业的时候就需要先创建一个进程来准备处理这个作业)
- 提供服务。例如:I/O请求(可能这里是指来了一个I请求,在这之前并没有执行相关操作的进程)
- 应用请求:基于应用进程的需求,由它自己创建一个新进程,以便使新进程以并发运行方式完成特定任务。
- 调用进程创建原语步骤:
- 申请空白PCB。
- 为新进程分配资源。
- 初始化进程控制块。
- 初始化标识信息。
- 初始化处理机状态信息。使程序计数器指向程序的入口地址,使栈指针指向栈顶;
- 初始化进程控制信息:进程的状态、优先级。
- 初始化进程调度信息:如获取程序和数据的地址等
- 将新进程插入就绪队列,启动调度。
- UNIX用fork()函数创建进程
- Fork():复制父进程全部资源。(子进程拷贝父进程的数据段、代码段,这个时候子进程和父进程之间都有内存保护。并且此时父子进程之间的执行顺序是不确定的)
- Clone():有选择的复制父进程的资源。(带参数。这个就不需要管了)
- Vfork():创建一个线程。复制除task_struct和系统空间堆栈外的所有资源。(子进程和父进程共享数据段;代码段等就直接拷贝。这个函数保证了子进程一定是先于父进程执行的。这个函数也不用管)
- 引起进程终止(撤销)的事件
- 正常结束。
- 异常结束:如越界错误、保护错、非法指令、特权指令错、运行超时、等待超时、算术运算错、被0除、I/O故障。(稍微看一下就好了)
- 外界干预(强制关机是吧):干预外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。
- 操作员或操作系统干预:由于某种原因,例如,发生了死锁,由操作员或操作系统终止该进程
- 父进程请求终止该进程(父进程还未终止)
- 当父进程终止时,OS也将他的所有子孙进程终止。(父进程终止)
- 还记的前面介绍进程的五状态的时候介绍的终止状态吗,就是说进程正常结束或者异常结束了
- 进程的终止过程
- 根据被终止进程的PID找到它的PCB,从中读出该进程的状态。
- 若被终止进程正处于执行状态,应立即终止该进程的执行,重新进行调度。
- 若该进程还有子孙进程,立即将其所有子孙进程终止。
- 将被终止进程所拥有的全部资源,归还给其父进程,或者归还给系统。
- 将被终止进程的PCB从所在队列中移出。
- 进程的阻塞与唤醒
- 引起进程阻塞的原因
- 请求系统服务
- 启动某种操作:如I/O操作
- 新数据尚未到达
- 无新工作可做
- 进程的阻塞过程
- 正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻塞原语block把自己阻塞;(阻塞是主动行为)
- 把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入阻塞队列;
- 转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换。
- 引起进程唤醒的过程
- 当被阻塞进程所期待的事件出现时,则由有关进程调用唤醒原语wakeup( ),将等待该事件的进程唤醒。(唤醒是一种被动行为)
- 把被阻塞的进程从等待该事件的阻塞队列中移出,并将其PCB中的现行状态由阻塞改为就绪
- 将该PCB插入到就绪队列中等待CPU调度
- 进程的挂起与激活
- 进程的挂起过程
- 当出现了引起进程挂起的事件时,系统将利用挂起原语suspend( )将指定进程挂起或处于阻塞状态的进程挂起。(挂起是主动行为,意思是我可以主动挂起自己。)
- 检查将要被挂起的进程的状态,若状态为就绪状态就转换为静止就绪,若为阻塞状态,就转换为静止阻塞,若为运行状态,就转换为静止就绪,并且CPU调度标志为真(这个标志位应该就是用来为下面的是否运行检查做铺垫的)
- 将被挂起进程的PCB复制到指定的内存区域。
- 若处于运行状态,则转向调度程序重新调度。
- 调用挂起原语的进程只能挂起自己或其子孙进程
- 进程的激活
- 当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,系统将利用激活原语active( )将指定进程激活。 (激活是被动行为,就好像是唤醒一样,唤醒也是一个被动行为)
- 检查将要被挂起的进程的状态:若是静止就绪状态就切换为活动就绪状态,若是静止阻塞状态就切换为活动阻塞状态
- 检查是否要进行重新调度
- 一个进程只能解挂自己的子孙进程,而不能解挂其他族系的进程
- 进程的调度点
- 非抢占系统:当前进程主动放弃CPU时
- 可抢占系统
- 中断请求被服务例程响应完成时
- 就绪队列发生变化或当前进程释放处理器
- 进程状态辨析——挂起与阻塞
- 阻塞:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行。此时引起进程调度,OS把处理机分配给另一个就绪进程,而让受阻进程处于暂停状态,一般将这种状态称为阻塞状态。(此时进程还在内存中)
- 挂起:由于系统和用户的需要引入了挂起的操作,进程被挂起意味着该进程处于静止状态。如果进程正在执行,它将暂停执行,若原本处于就绪状态,则该进程此时暂不接受调度。(此时进程就已经在外存中了)
- 共同点:
- 进程都暂停执行(阻塞进程等待事件,挂起进程等待被换入内存)
- 进程都释放CPU,即两个过程都会涉及上下文切换(只有当挂起的进程是正在执行的进程的时候才会涉及到释放CPU,才会涉及到上下文切换)
- 不同点
- 对系统资源占用不同:虽然都释放了CPU,但阻塞的进程仍处于内存中,而挂起的进程通过“对换”技术被换出到外存(磁盘)中。
- 发生时机不同:阻塞一般在进程等待资源(IO资源、信号量等。或者说是等待特定事件)时发生;而挂起是由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提高内存利用率需要将暂时不能运行的进程(处于就绪或阻塞队列的进程)调出到磁盘
- 恢复时机不同:阻塞要在等待的资源得到满足(例如获得了锁,或者相关事件触发)后,才会进入就绪状态,等待被调度而执行;被挂起的进程由将其挂起的对象(如用户、系统)在时机符合时(调试结束、被调度进程选中需要重新执行)将其激活
这里应该是除了running状态都介绍了,因为running状态没什么好介绍的
2.4 进程同步
- 临界区的使用条件
- 空闲让进。如果临界区空闲,则只要有进程申请就立即让其进入。
- 忙则等待。每次仅允许一个进程处于临界区。
- 有限等待。进程只能在临界区内逗留有限时间,不得使其他进程在临界区外无限期等待。
- 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,以免进程处于“忙等”状态。
- 下面给出一个互斥使用临界区的实例:
下面给出几个可以实现同步互斥的方法
- 信号量
- 信号量的类型
- 按照功能划分
- 互斥信号量:用于申请或释放资源的使用权,常初始化为1。(也就相当于是一个互斥锁了)
- 资源信号量:用于申请或归还资源,可以初始化为大于1的正整数,表示系统中某类资源的可用个数。(代表的是资源的可用数量)
- 按照信号量机制的发展(了解)
- 整形信号量:定义为一个整型量 ,用来表示空闲资源的数目。仅能通过两个标准的原子操作 wait(s)和signal(S)来访问,又称为P、V操作(p就是pend,所以对应的是wait)
- PS:
- 必须成对使用wait和signal原语
- wait、signal原语不能出现次序错误、重复或遗漏:遗漏wait原语则不能保证互斥访问;遗漏signal原语则不能在使用临界资源之后将其释放(给其他等待的进程)
- 记录型信号量:记录型信号量机制,是一种不存在“忙等”现象的进程同步机制。
- 记录型信号量的数据结构如下:
- AND型信号量
- AND型信号量的基本思想:将进程在整个运行过程中需要的所有资源,一次性全都地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源,也不分配给他(有点像是预防死锁)。而AND型信号量又是一个原子操作,所以所有资源要么全部分配到进程,要么一个也不分配
- AND型信号量wait的执行逻辑如下:
- 信号量集:在记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1 或减1 操作,意味着每次只能获得或释放一个单位的临界资源。在每次分配时,采用信号量集来控制,可以分配多个单位的资源。
- 信号量集wait操作的执行逻辑:
- 信号量集的使用场景
- 信号量的原子操作——wait(s) 和signal(s)原语
- wait操作用于申请资源(或使用权),进程执行wait原语时,可能会阻塞自己(也就是一个让权等待)。也被称为P操作
- 在记录型信号量中,执行逻辑如下(实际上就是上面的那个流程图)
- signal操作用于释放资源(或归还资源使用权),进程执行signal原语时,有责任唤醒一个阻塞进程(在整形信号量中就没有这个职责)。也被称为V操作
- 在记录型信号量中,执行逻辑如下(实际上也是上面的那个流程图)
- 信号量的物理意义
- s.value > 0 ,表示还可执行wait(s)而不会阻塞的进程数(可用资源数)。每执行一次wait(s)操作,就意味着请求分配一个单位的资源
- s.value ≤0 ,表示已无资源可用,因此请求该资源的进程被阻塞。此时,s.value的绝对值等于该信号量阻塞队列中的等待进程数。执行一次signal操作,就意味着释放一个单位的资源。
- 信号量的取值范围(这里只对互斥信号量进行讨论,因为资源信号量还与系统资源数量有关)
- 当仅有两个并发进程共享临界资源时,互斥信号量仅能取值1、0、-1。其中
- s.value=1, 表示无进程进入临界区
- s.value=0,表示已有一个进程进入临界区
- s.value= - 1,则表示已有一进程正在等待进入临界区
- 当用s来实现n个进程的互斥时,s.value的取值范围为1~-(n-1) (因为最多只能阻塞n-1个,这是由于空闲让进原则)
- 信号量小结
- 信号量的应用
- 利用信号量实现互斥(这里的信号量s就是一个互斥信号量):
- 利用信号量来描述前趋(合作)关系(也就是用信号量实现同步)
- 如下图:
整形信号量工作逻辑如下:
因为此时wait操作是一个循环检查,所以这里是在忙等的状态,没有满足临界区使用条件中的让权等待条件
记录型信号量的工作逻辑如下:
可以发现记录型信号量实际上就是通过一个信号量阻塞队列(使信号量知道当前信号量使哪些进程阻塞了),将整形信号量中忙等的while转换为了if
其中:value的初值表示系统中某类资源的数目, value的初始值>1时,称为资源信号量, value的初始值=1时,称为互斥信号量(所以上面所说的互斥信号量和资源信号量都是一个记录型信号量)
相较于整形信号量,就是多了一个信号量的阻塞队列。也就是在整形信号量中循环判定时,如果条件没有满足的话,就将阻塞当前进程(相当于是把while换成了if)。这个时候就实现了让权等待。下面使用的信号量应该都是记录型信号量
注意这里是先判断,因为先减value就表示把资源分配给当前进程,这与AND型信号的基本思想违背
AND型信号量的signal的执行逻辑如下:
需要注意的是,这里t和d可以不相等,但是t一定要大于等于d。也就是说当前这个进程要求当前有若干个资源,但是我并不全部申请。
信号量集signal操作的执行逻辑:
不难发现,大多数情况都是将t和d设置为同一个值,都为1时并且只有一个信号量时实际上就是一个记录型信号量
注意是先减value再进行if,所以当value小于0的时候才会阻塞(小于0就表示在分配当前资源之前value的值小于1,就是没有资源,所以当前进程需要阻塞。不难发现此时信号量中的value可能是负无穷)
注意这里同样的是先加上value然后再进行if,当value为负的时候就可以表示被阻塞的进程数量(为0的时候表示所有的资源都被占用,为-1的时候就表示在当前信号量上被阻塞了一个进程。如果加1后value仍然小于等于0,就表示原来的value是小于等于-1的,就表示最少有一个进程被阻塞,所以这个时候需要去唤醒一个进程)
这里在一般信号量中所说的临界值应该就是指那个t,只有当t满足的时候才能进行资源的分配,才能进入临界区
- 硬件同步机制
- 关中断:进入锁测试前关闭中断,完成锁测试并上锁后打开中断(这里的锁测试应该就是指临界区)。进程在临界区时计算机系统不响应中断,不会引发调度
- 优点:关中断是实现互斥的最简单的方法之一
- 缺点:
- 滥用关中断会引发严重后果
- 关中断时间过长会影响系统效率
- 不适用于多CPU系统(因为关中断一下只能关当前处理器的中断)
- 执行逻辑
- 关中断实现互斥的逻辑如下:
- 利用Test-and-Set指令实现互斥:实际上就是一直检查标志位,只不过现在做成了一条指令
- TS逻辑如下:
- 利用Swap指令实现进程互斥:该指令称为对换指令,在Intel 80x86中又称为XCHG指令,用于交换两个字的内容
- 实现逻辑如下:
实际上就是检查当前锁的状态(用old表示),如果没有上锁(也就是lock为false),那么就给这个lock置为true,这个时候TS返回的就是false了;如果当前已经上锁了,那么传入的lock在未修改之前就已经是true了,这个时候返回的也是true,所以就会进行等待。实际上就是flase就进入并且置位,true的话就等待
只有当进程拿着自己的true key(每次进入都需要将key置为true)换到了lock的false的时候才能去执行临界区。执行结束之后需要再释放锁,也就是将lock置为false
- 管程:需要知道管程也是一种进程同步的机制
- 管程的定义:
- 当共享资源用共享数据结构表示时,资源管理程序可用对该数据结构进行操作的一组过程来表示(例如,资源的请求和释放过程request和release),我们把这样一组相关的数据结构和过程一并称为管程。
- Hansan为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”。
- 管程的组成
- 管程的名字
- 局部于管程的共享变量说明
- 对该数据结构进行操作的一组过程;
- 对局部于管程的数据设置初始值的语句。
- 管程的特点(就相当于定义了一个管程这个东西来保护临界区)
- 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
- 一个进程通过调用管程的一个过程进入管程。
- 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被挂起,以等待管程变成可用的
2.5 经典进程的同步问题
- 生产者消费者问题
- 问题描述:生产者和消费者进程共享一个大小固定的缓冲池。一个或多个生产者生产数据,并将生产的数据存入缓冲池;一个或多个消费者从缓冲池中取数据(取数据是要取出来的,而不是就只是读。所以这个时候所有的进程之间都需要互斥,因为都会改变缓冲区的状态)
- 问题分析:
- 所有的进程之间都需要互斥,因为所有的进程都会修改当前缓冲区的状态
- 生产者消费者进程之间需要同步,生产者进程不能向满缓冲区写数据;消费者进程不能从空缓冲区中取数据
- 问题解决
- 流程图:
- 生产者
- 消费者
- 注意点
- wait的顺序很重要,一定要先申请资源信号量再申请互斥信号。如果producer 先申请互斥,进入后,申请空资源,发现没有空资源,必须等待comsumer先申请满资源,使用后释放出来。(因为先申请互斥的话就不能保证后面不会因为其他信号量的操作而无法释放锁,说白了就是互斥信号量要是最后一个申请的)
- 哲学家进餐问题
- 问题解决
- 采用记录型信号量
- 采用AND型信号量(因为每次需要获取两个资源,所以这个时候可以使用and型信号量。另一方面采用记录型信号量会出现死锁,而and型信号量刚好可以避免死锁):实际上就是要求要同时获取所有的筷子才能进餐
- 规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则相反.按此规定,将是2,3号哲学家竞争3号筷子,4,5号哲学家竞争5号筷子.即三个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会1个哲学家能获得两支筷子而进餐(这样的话在最开始的时候就会有哲学家被阻塞并且没有获得资源,所以这个办法是可行的)
- 注意点:就是使用记录型信号量的时候需要小心死锁
注意:如果此时没有设置最大容量room的话就有可能出现每一个哲学家拿一只筷子的情况,这个时候就造成了死锁
- 读者写者问题
- 问题描述:若干读进程只能读数据,若干写进程只能写数据。因为此时并不是取出数据,而是单纯的读数据,所以读者之间是不需要互斥的,但是读写、写写之间需要互斥
- 限制条件:
- 允许多个读者进程可以同时读数据;
- 不允许多个写者进程同时写数据,即只能互斥写数据;
- 若有写者进程正在写数据,则不允许读者进程读数据。(读写互斥的话,就有两种方式了,也就是读者优先还是写者优先)
- 问题分析
- 当一个读者正在读数据时,另一个读者也需要读数据,应允许第二个读者进入,同理第三个及随后更多的读者都被允许进入。现在假设一个写者到来,由于写操作是排他的,所以它不能访问数据,需要阻塞等待。如果一直都有新的读者陆续到来,写者的写操作将被严重推迟。该方法称为“读者优先”。即,一旦有读者正在读数据,允许多个读者同时进入读数据,只有当全部读者退出,才允许写者进入写数据。(也就是写者到来了仍然允许读者进入)
- 使用记录型信号量解决
- 读者
- 写者
- 使用信号量集解决(同样是读者优先。因为这里写者仅当所有读者都不在读的时候才能进入。这里甚至读者都不会被写者搞得延后)
需要注意的是读者可能也会因为第一个wait(wmutex)被读者疯狂推迟,但是这个概率应该比较小,毕竟进程之间是平等的。
需要注意的是这里读者进程中的0,因为这里没有像记录型信号量一样的if了,所以这里不能随便占用wmutex,因为读者进程如果不用if的话就不知道在什么时候释放wmutex。读者不占用wmutex仍然不会使写者进入是因为写者那边需要多加一个信号量,就是判断当前有没有读者在读。所以将最后一个值设置为0就相当于是一个if,前面的就是判断写者在不在写;后面的就是判断有没有读者在读
- 解题方法
- 一般情况下,每个同步关系对应一个信号量。转化为:可用的资源数。互斥信号量一般是一个共享资源一个
- 信号量小结
- 信号量S的物理含义
- S>0表示有S个资源可用;
- S=0表示无资源可用;
- S<0则| S |表示S阻塞队列中的进程个数。
- wait(S):表示申请一个资源;
- signal(S):表示释放一个资源。
- vwait,signal操作必须成对出现,有一个wait操作就一定有一个signal操作。
- 如果wait(S1)和wait(S2)两个操作在一起,那么wait操作的顺序至关重要,一个同步wait操作与一个互斥wait操作在一起时同步wait操作在互斥wait操作前。
- 而两个signal操作无关紧要(所以那个交叉应该就是纯纯扯淡,但是最好能不交叉就不交叉了)
2.6 进程通信
- 进程通信的类型
- 低级通信:进程之间的互斥和同步,由于其所交换的信息量少而被归结为低级通信。(这些信息只是实现进程之间的同步互斥的)
- 信号量机制:信号量机制作为同步工具是卓有成效的,但作为通信工具,则不够理想,主要表现在下述两方面:
- 效率低,生产者每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;
- 通信对用户不透明。(并没有展示内部的加一减一操作)
- 高级通信:是指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据的一种通信方式。常用的高级进程通信机制如下:
- 共享存储器系统:在内存中分配一片空间作为共享存储区
- 基于共享数据结构的通信方式:诸进程共用某些数据结构,借以实现诸进程间的信息交换(如生产者消费者问题中就是使用共享的有界缓冲区这种数据结构)。这种通信方式是低效的,只适于传递相对少量的数据
- 基于共享存储区的通信方式:由操作系统在内存中划分出一块区域作为共享存储区。进程在通信前向操作系统申请共享存储区中的一个分区。然后,申请进程把获得的共享存储分区连接到本进程上,此后便可像读/写普通存储器一样地读/写共享存储分区。该方式下,通信进程之间的同步与互斥访问共享存储区由进程负责。(相较于上面的方式,只不过是把共享数据结构换成了共享的部分存储器)
- 管道通信:写者以字符流的形式向管道文件中写入数据;读者从该文件中读出数据。实现读者写者之间大量的数据传送。管道通信不仅需要保证互斥和同步,还需要确定通信的对方存在
- 消息传递系统:以消息(Message)为单位、通过通信原语在进程间进行数据交换
- 客户机--服务器系统:实际上跟计网里面的那一套是一样的疑问(会考吗)
- 消息传递通信的实现方法
- 直接通信方式:进程之间直接使用send( ),receive( )原语进行收发数据
- 对称寻址方式:Send(Receiver, message); 发送一个消息给接收进程;Receive(Sender, message); 接收Sender发来的消息;
- 非对称寻址方式:Send(P, message);发送一个消息给接收进程P;Receive(id, message); 接收来自任何进程的消息,进程id不固定
- 使用直接通信方式解决生产者消费者问题:
- 间接通信方式:信箱(就相当于是把消息放在一个共享的存储区吧)
- 信箱相关原语
- Send(mailbox, message); 将一个消息发送到指定信箱;
- Receive(mailbox, message); 从指定信箱中接收一个消息
- 信箱的分类
- 私用信箱:用户进程可为自己建立一个新信箱,并作为该进程的一部分。信箱的拥有者有权从信箱中读取消息,其他用户则只能将自己构成的消息发送到该信箱中。这种私用信箱可采用单向通信链路的信箱来实现。当拥有该信箱的进程结束时,信箱也随之消失
- 公用信箱:它由操作系统创建,并提供给系统中的所有核准进程使用。进程既可把消息发送到该信箱中,也可从信箱中读取发送给自己的消息。显然,公用信箱应采用双向通信链路的信箱来实现。通常,公用信箱在系统运行期间始终存在。
- 共享信箱:它由某进程创建,在创建时或创建后,指明它是可共享的,同时须指出共享进程(用户)的名字。信箱的拥有者和共享者,都有权从信箱中取走发送给自己的消息。
之前使用信号量只是实现了共享区的互斥访问,这里使用直接通信实际上就是把共享区和信号量这两个部分合在一起了。在消息通信原语中已经实现了之前使用信号量实现的同步和互斥逻辑。
- 消息缓冲队列通信机制:需要注意的是消息缓冲队列是直接挂载在PCB上的,PCB上有消息队列首指针、消息互斥信号量(需要这个是因为要做到写互斥,读是只有当前进程在读)、消息资源信号量
- 消息缓冲队列的示例如下:
- 发送消息
- 接收消息
- 消息传递系统实现中的若干问题(了解即可)
- 通信链路(communication link):为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路。有两种方式建立通信链路。
- 第一种方式:由发送进程在通信之前,用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路;在链路使用完后,也用显式方式拆除链路。这种方式主要用于计算机网络中。
- 第二种方式: 发送进程无须明确提出建立链路的请求,只须利用系统提供的发送命令(原语),系统会自动地为之建立一条链路。这种方式主要用于单机系统中。
- 通信链路分类
- 根据通信链路的连接方法,又可把通信链路分为两类:
- 点—点连接通信链路,这时的一条链路只连接两个结点(进程);
- 多点连接链路,指用一条链路连接多个(n>2)结点(进程)。
- 而根据通信方式的不同,则又可把链路分成两种:
- 单向通信链路,只允许发送进程向接收进程发送消息;
- 双向链路,既允许由进程A向进程B发送消息,也允许进程B同时向进程A发送消息。
- 消息的格式
- 定长消息:在某些OS中,消息是采用比较短的定长消息格式,这减少了对消息的处理和存储开销。
- 变长消息:在有的OS中,采用另一种变长的消息格式,即进程所发送消息的长度是可变的。系统在处理和存储变长消息时,须付出更多的开销,但方便了用户。
- 进程同步方式
- 发送进程阻塞、 接收进程阻塞:进程间汇合同步,有消息时传递,无消息时同时阻塞。
- 发送进程不阻塞、 接收进程阻塞:发送者尽快发送消息,接收者平时阻塞收到消息才唤醒,例如多个用户共享一个打印服务。
- 发送进程和接收进程均不阻塞:发送者和接收者均忙于自身事务,直到无法继续才阻塞。例如发送进程和接收进程间关联一个长度为n的消息队列。
2.7 线程与线程控制
线程的概念实际上就是将传统的进程拆分了,所以之前所说的所有东西都应该是指传统的进程,所以之前会说进程是资源分配和调度的基本单位。(ppt原话:调度和分派的部分通常称为线程或轻型进程(lightweight process),而资源所有权的部分通常称为进程)
- 线程概述
- 线程的引入:操作系统为了并发执行,需要频繁的切换进程,但是进程又是资源的拥有者,所以切换开销大。所以引入了进程
- 线程的共享问题:进程内的所有线程共享进程的很多资源(这种共享又带来了同步问题)
- 线程间共享的资源:进程代码段、进程中的全局变量、进程打开的文件……
- 线程私有的资源:线程ID、线程上下文(一组寄存器值的集合)、线程局部变量(存储在栈中)
- 线程的互斥问题:对全局变量(进程资源)的访问步骤——这个过程需要互斥
- 将内存单元中的数据读入寄存器
- 对寄存器中的值进行运算
- 将寄存器中的值写回内存单元
- 进程与线程的比较
- 相互关系:线程不能脱离进程而存在,线程的层次关系,执行顺序并不明显,会增加程序的复杂度;进程中至少包含一个线程没有通过代码显示创建线程的进程,可以看成是只有一个线程的进程
- 调度:在传统的操作系统中,进程作为拥有资源和独立调度、分派的基本单位。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位。在同一进程中,线程的切换不会引起进程的切换;但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。
- 并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,使得操作系统具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量(并发套并发是吧)
- 资源:线程只拥有少量在运行中必不可少的资源,如线程上下文、线程栈、线程的ID;而进程占用资源多,线程占用资源少,使用灵活
- 独立性:同一进程中的不同线程共享进程的内存空间和资源,所以同一进程中的不同线程的独立性低于不同进程。
- 系统开销:线程的切换只需要保存和设置少量的寄存器内容,不涉及存储器管理方面的操作。由于一个进程中的多个线程具有相同的地址空间,在同步和通信的实现方面线程也比进程容易。在一些操作系统中,线程的切换、同步和通信都无须操作系统内核的干预。
- 支持多处理机系统:一个进程分为多个线程分配到多个处理机上并行执行,可加速进程的完成。
- 线程的属性:
- 轻型实体:线程自己基本不拥有系统资源,只拥有少量必不可少的资源:TCB,程序计数器、一组寄存器、栈。
- 独立调度和分派的基本单位:在多线程OS中,线程是独立运行的基本单位,因而也是独立调度和分派的基本单位。
- 可并发执行:同一进程中的多个线程之间可以并发执行,一个线程可以创建和撤消另一个线程。
- 共享进程资源:它可与同属一个进程的其它线程共享进程所拥有的全部资源。
- 线程的状态:线程在运行时有三种状态(对应进程的三态)
- 执行状态:表示线程正获得CPU而运行;
- 就绪状态:表示线程已具备了各种运行条件,一旦获得CPU便可执行;
- 阻塞状态:表示线程在运行中因某事件而受阻,处于暂停执行的状态;
- 线程的三态转换如下图:
需要注意的是线程中的挂起状态是指线程的“不可运行状态”(阻塞状态)
- 线程的组成——TCB(线程控制块。这个只存在于内核级线程中,在用户级线程中这些信息都是由应用程序维护的)的内容如下:
- 一个唯一的线程标识符 (线程ID只在他自己的进程环境中唯一)
- 一组寄存器 :包括程序计数器、状态寄存器、通用寄存器的内容;
- 线程运行状态:用于描述线程正处于何种运行状态;
- 优先级:描述线程执行的优先程度;
- 线程专有存储器:用于保存线程自己的局部变量拷贝;
- 信号屏蔽:对某些信号加以屏蔽。
- 两个栈指针:核心栈、用户栈
- 线程的创建与终止
- 线程的创建:在多线程OS环境下,应用程序在启动时,通常仅有一个“初始化线程”线程在执行。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数。如指向线程主程序的入口指针、堆栈的大小,以及用于调度的优先级等。在线程创建函数执行完后,将返回一个线程标识符供以后使用
- 线程的终止:线程完成了自己的工作后自愿退出或线程在运行中出现错误或由于某种原因而被其它线程强行终止。(也就是正常结束、异常结束和外部干预)
- 线程终止的三种方式
- 线程从启动例程函数中返回,函数返回值作为线程的退出码(正常结束或者异常结束,这个时候要自己调用exit)
- 线程被同一进程中的其他线程取消(外部干预,这个时候需要其他线程调用cancel)
- 线程在任意函数中调用pthread_exit函数终止执行(外部干预)
- 疑问(线程创建、终止、清理等函数要不要背。还有怎么7里面又一个线程属性,这个线程属性感觉跟前面的线程属性差不多,只不过这里的线程属性是表示函数调用时需要传入的线程)
- 线程的同步与通信机制
- 互斥锁(mutex)
- 互斥锁是一种比较简单的、用于实现线程间对资源互斥访问的机制。由于操作互斥锁的时间和空间开销都较低,因而较适合于高频度使用的关键共享数据和程序段。互斥锁可以有两种状态, 即开锁(unlock)和关锁(lock)状态。 (这个就相当于是信号量里面的互斥信号量了)
- 阻塞方式:lock(mutex)->访问->unlock(mutex)。这个时候其他的线程再来就会被阻塞在lock
- 非阻塞方式:if(trylock) then...else...。如果互斥体已经被上锁,该调用不会阻塞等待,而会返回一个错误代码,这个时候就会跑到else分支去了(这个时候else执行完之后应该还要回来吧)
- 条件变量(疑问看不懂思密达)
- 每一个条件变量通常都与一个互斥锁一起使用。
- 单纯的互斥锁用于短期锁定,主要是用来保证对临界区的互斥进入。而条件变量则用于线程的长期等待, 直至所等待的资源成为可用的。
- 使用步骤:
- 线程首先对mutex执行关锁操作,若成功便进入临界区,然后查找用于描述资源状态的数据结构(这个描述资源状态的数据结构,应该是一个临界资源),以了解资源的情况。
- 只要发现所需资源R正处于忙碌状态,线程便转为等待状态,并对mutex执行开锁操作后,等待该资源被释放;
- 若资源处于空闲状态,表明线程可以使用该资源,于是将该资源设置为忙碌状态,再对mutex执行开锁操作。
- 信号量机制
- 私用信号量(private samephore):当某线程需利用信号量来实现同一进程中各线程之间的同步时,可调用创建信号量的命令来创建一私用信号量,其数据结构存放在应用程序的地址空间中。私用信号量属于特定的进程所有,OS并不知道私用信号量的存在,因此,一旦发生私用信号量的占用者异常结束或正常结束,但并未释放该信号量所占有空间的情况时,系统将无法使它恢复为0(空),也不能将它传送给下一个请求它的线程。 (也就是只有特定进程中的线程可见)
- 公用信号量(public semaphort):公用信号量是为实现不同进程间或不同进程中各线程之间的同步而设置的。有着一个公开的名字供所有的进程使用,故称为公用信号量。其数据结构是存放在受保护的系统存储区中,由OS为它分配空间并进行管理,故也称为系统信号量。如果信号量的占有者在结束时未释放该公用信号量,则OS会自动将该信号量空间回收,并通知下一进程。(也就是之前看的那些信号量,是由操作系统进行管理的)
- 线程的实现方式
- 用户级线程:用户级线程仅存在于用户空间中。对于这种线程的创建、撤消、线程之间的同步与通信等功能,都无须内核来实现。对于用户级线程的切换,通常是发生在一个应用进程的诸多线程之间,这时,也同样无须内核的支持。由于切换的规则远比进程调度和管理的规则简单,因而使线程的切换速度特别快。可见,这种线程是与内核无关的。(进程的切换是与内核有关的,所以进行不同进程间中的线程切换的时候会涉及到内核)
- 用户级线程的特点——由线程库管理,与内核无关
- 由应用程序完成所有线程的管理:通过一组管理线程的函数库来提供一个线程运行管理系统(运行系统,也就是线程库)
- 内核不知道线程的存在(与内核无关)
- 线程切换不需要核心态特权(与内核无关)
- 调度算法可以是线程专用的
- 用户级线程的优点
- 线程切换不调用内核
- 调度是应用程序特定的:可以选择最好的算法(不用考虑系统的整体性)
- 可运行在任何操作系统上(只需要线程库),可以在一个不支持线程的OS上实现
- 用户级线程的缺点
- 当线程执行一个系统调用时,该线程及其所属进程内的所有线程都会被阻塞。(因为阻塞了当前线程,对处理机而言,他只知道当前的进程被阻塞了)
- 多线程应用不能利用多处理机进行多重处理。(因为处理机只能看见一个进程)
- 用户级线程的工作模型
- 内核支持线程
- 内核支持线程,是在内核的支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程(也就是所有的线程都与系统内核有关),他们的创建、撤消和切换等,是依靠内核实现的。在内核空间中为每一个内核支持线程设置了一个线程控制块TCB, 内核是根据该控制块而感知某线程的存在的,并对其加以控制。
- 内核支持线程的特点——由内核管理并提供相关的API
- 所有线程管理由内核完成
- 没有线程库,但内核提供API
- 线程的创建、撤消是依靠内核实现的
- 线程之间的切换需要内核支持
- 以线程为基础进行调度
- 内核是根据每个线程的控制块来控制线程的
- 内核支持线程的优点——内核可以看见线程
- 在多处理器系统中,内核能够同时调度同一进程中多个线程并行执行;
- 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程;
- 内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小;
- 内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
- 内核支持线程的缺点——内核可见,线程切换需要切换模式
- 在同一个进程中,从一个线程切换到另一个线程时,需要从用户态转到内核态再转到用户态进行,这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的,系统开销较大。
- 内核支持线程的工作模型
- 内核支持线程与用户级线程的区别
- 由内核完成线程的创建、调度
- 每个执行序列需有两个栈: 用户栈+内核栈。其中用户栈记录普通的函数调用,而内核栈记录系统调用、中断处理。对于用户级线程,就只有用户栈
- 对于设置了用户级线程的系统,其调度仍是以进程为单位进行,在采用时间片轮转调度算法时,各进程间是公平的,但各进程的线程间是不公平的
- 组合方式(就是用户级线程加上内核支持线程,就是在线程上搭线程)
- 组合方式的工作模型(是最后一个)
也就是线程都在用户空间
可以发现用户创建的就是内核级线程
或者:
这里的LWP是轻型进程,他实现了用户级进程和内核之间的隔离。LWP和KST应该是一对一的关系(所以表现的好像是轻型进程与内核支持线程是一样的,因为链接到一个轻型进程就相当于是链接到了一个内核支持线程)
- 线程的具体实现(了解即可吧)
- 内核支持线程的实现
- 用户级线程的实现(用户级线程运行在中间系统上)
- 通过用户空间的线程库实现:用于管理和控制线程的函数的集合,包括创建、撤消线程函数、线程同步和通信函数、线程调度函数等。用户级线程不能直接利用系统调用,必须通过运行时系统间接利用系统调用。
- 通过轻型进程实现(这个实际上就已经是组合模式了):核心线程又称为轻型进程LWP(Light Weight Process)。每个进程都可拥有多个LWP,每个LWP都有自己的TCB,其中包括线程标识符、优先级、状态、栈和局部存储区等
- 线程实现方案的对比
也就是预先分配TCB。内核支持线程的调度和切换与进程的调度和切换十分相似,只是线程的调度和切换比进程的开销小的多
- 采用线程的优点(世界线收束是吧)
- 在一个已有进程中创建一个新线程比创建一个全新进程所需的时间少。(创建)
- 终止一个线程比终止一个进程花费的时间少。(终止)
- 线程间切换比进程间切换花费的时间少。(状态切换)
- 线程提高了不同的执行程序间通信的效率。同一个进程中的线程共享存储空间和文件,它们无需调用内核就可以互相通信。(通信)
- 线程总结(稍微看看就好了,这一页ppt讲的很麻,前面看好了就行了)
2.8 练习
- 练习一
- 问题:
- 答案:
- 练习二
- 问题&答案:
- 进程主动完成状态转换,还是被动完成?——不一定
- 进程状态是否唯一?——唯一
- 时间片用完是否是进程由执行变为就绪的唯一原因?——不是,还可以是高优先级进程抢占
- 在单处理机系统中,是否可以有多个进程同时处于执行状态?——不可以
- 在多处理机系统中,是否可以有多个进程同时处于执行状态?——可以
- 练习三
- 问题&答案
- 静止就绪和就绪状态的区别?——静止就绪在外存,就绪在内存
- 静止就绪状态是否可以进入执行状态?——静止就绪不能进入执行状态,要先进入活动就绪才能进入执行状态
- 静止阻塞状态是否可以进入执行状态?——静止阻塞状态不能进入执行状态
- 挂起状态的进程是否可以自我激活?——挂起状态不可以自我激活(挂起可以主动,激活是被动的)
- 练习四
- 问题&答案
- 进程可以由自己创建——错误
- 进程可以由自己阻塞——正确
- 进程可以由自己挂起——正确
- 进程可以由自己激活——错误
- 进程可以由自己唤醒——错误
- 进程可以由自己终止——错误(进程的终止只能是正常结束、异常结束、外部干预)
- 练习五
- 问题:如果单处理器系统中有N个进程,运行的进程最多几个,最少几个?就绪进程最多几个,最少几个?等待进程最多几个,最少几个?若OS的进程有运行、就绪和阻塞三个基本状态,则阻塞进程最多几个,最少几个?(注意这里的等待是指不在CPU上运行,不论是在就绪还是在等待)
- 答案:运行状态最多1个,最少0个;就绪状态最多N-1个,最少0个。等待状态最多N个,最少N-1个;阻塞状态最多N个,最少0个
- 练习六
- 问题:
- 答案:
PS:PV操作一定要成对使用,并不意味着在同一个进程中一定要有同一个信号量的PV操作,也不代表着P操作一定要在V操作之前,只要总体来看PV是成对的就好了(有P就有V就好了)
- 练习七
- 问题&答案:
- 进程能否先申请互斥信号量,再申请资源信号量?——进程应该先申请资源信号量,再申请互斥信号量,顺序不能颠倒。
- 对任何信号量的wait与signal操作是否必须配对?——对任何信号量的wait与signal操作必须配对。
- 同一进程中的多对wait与signal语句能否交叉?——同一进程中的多对wait与signal语句只能嵌套,不能交叉。(意思就是不能wait(a)-wait(b)-signal(a)-signal(b)?就把这个当成一个规范来写吧)疑问
- 信号量的wait和signal操作能否在不同的进程中?——同一个互斥信号量或资源信号量的wait与signal可以不在同一个进程中。
- 练习八
- 问题:生产者消费者问题?
- 练习九
- 问题:读者写者问题的读者优先?写者优先?
- 练习十
- 问题:哲学家进餐问题?
- 练习十一
- 问题:
- 答案:
注意这里的资源信号量相当于资源信号量是因为资源只有一个,当资源不止一个的时候这两个信号量就不一样了
- 练习十二
- 问题:
- 答案:
这个实际上就是一个最基本的生产者消费者问题套了一层壳。如果把这个运输工具搞多一点仍然需要增加一个互斥信号量,因为具体需要看仓库中需要执行什么操作了,如果是一个共享变量++或者--的话那就一定需要互斥
- 练习十三
- 问题:
- 答案:
- 引申:
- 答案:
在上面因为盘子只能放一个东西,所以盘子就相当于是一个资源兼互斥信号量。而在这里盘子的容量变成了2,并且要求互斥,所以这个时候就需要额外加互斥信号量了
- 练习十四
- 问题&答案(A)
- 练习十五
- 问题&答案(C)
- 练习十六
- 问题:
- 答案:
- 作者:Noah
- 链接:https://imnoah.top/article/OS/Chapter2
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。