一.进程(最开始的信息,在引入线程之后会有变化)
定义:
程序: 就是一个指令序列
进程:就是程序的一次执行过程(动态性)。它是系统进行资源和调度的一个独立单位。
程序段、数据段、PCB三部分组成了进程实体(进程映像)。一般情况下,我们把进程实体就简称为进程,例如,所谓创建进程,实质上是创建进程实体中的PCB;
而撤销进程,实质上是撤销进程实体中的PCB。注意:PCB是进程存在的唯一标志!
进程的组成:
PCB是进程存在的唯一标志
进程描述信息
1、PCB: 进程控制和管理信息(进程的管理者(操作系统)所需要的数据都在PCB)
资源分配清单
处理相关信息
2、程序段: 存放要执行的代码(程序本身的运行所需要的数据在程序段、数据段中)
3、数据段: 存放程序运行过程中处理的各种数据。
进程的组织形式:
1、链式方式:按进程状态将PCB分为多个队列,操作系统持有指向各个队列的指针
2、索引方式:按照进程状态建立几张索引表,各表项指向一个PCB
进程的特征:
1、动态性 (进程的基本特征):进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进 程具有自己的生命周期和各种不同的状态,在程序中是没有这些概念的。进程是程序的一次执行过程,是动态地产生、变化和消亡的
2、并发性:并行指在同一时刻有多条指令在多个处理器上同时执行;
并发才旨在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。大部分操作系统都支持多进程并发 执行,现代的操作系统几乎都支持同时执行多个任务。
3、 独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己的私有的地址空间。在没有经过进程本身允许的情况下,一个 用户进程不可以直接访问其他进程的地址空间。是资源分配、接受调度的基本单位。
4、异步性:是指进程是按异步方式运行的,即按各自独立的、不可预知的速度向前推进。正是淤于此因,才导致了传统意义上的程序若参与并发执行,会产生其 结果的不可再现性。为使进程在并发运行时虽具有异步性,但仍能保证进程并发执行的结果是可冉现的,在OS中引进了进程的概念,并且配置相应的进程同步 机制。‘’进程同步机制‘’
5、结构性:每一个进程都会配置一个PCB。结构上看,进程由程序段、数据段、PCB组成。
进程的状态与转换:
三种基本状态
1、运行状态(Runing):占有CPU,并在CPU上运行。CPU ✔,所需其他资源 ✔。
注意:单核处理机环境下,每一时刻最多只有一个进程处于运行态。(双核环境下可以同时有两个进程处于运行态)
2、就绪状态(Ready):已经具备运行条件,但由于没有空闲CPU,而暂时不能运行。CPU ✘,所需其他资源 ✔。
进程已经拥有了除处理机之外所有需要的资源,一旦获得处理机,即可立即进入运行态开始运行。
即:万事俱备,只欠CPU
3、阻塞状态(Waiting/Blocked,又称:等待态):因等待某一件事情而暂时不能运行。CPU ✘,所需其他资源 ✘。
如:等待操作系统分配打印机、等待读磁盘操作的结果。CPU是计算机中最昂贵的部件,为了提高CPU的利用率,需要先将其他进程需要的资源分配到位,才能 得到CPU的服务
另外两种状态
1、创建状态(New,又称:新建态):进程正在被创建,操作系统为进程分配资源、初始化PCB
2、终止状态(Terminated,又称:结束态):进程运行结束(或者由于bug导致进程无法继续执行下去,比如数组越界错误),需要撤销进程。
操作系统需要完成撤销进程相关的工作。完成将分配给进程的资源回收,撤销进程PCB等工作。
进程状态间的转换
1、就绪态—>运行态 :进程被调度
2、运行态—>就绪态 :时间片到,或CPU被其他高优先级的进程抢占
3、运行态—>阻塞态 :等待系统资源分配,或等待某件事发生(主动行为)
4、阻塞态—>就绪态 :资源分配到位,等待某件事发生(被动行为)
5、创建态—>就绪态 :系统完成创建进程相关的工作
6、运行态—>终止态 :进程运行结束,或运行过程中遇到不可修复的错误
进程状态间的转换图
进程控制
基本概念
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
简化理解:反正进程控制就是要实现进程状态转换
用原语实现进程控制。原语的特点是执行期间不允许中断,只能一气呵成。
这种不可被中断的操作即原子操作。
原语采用“关中断指令”和“开中断指令”实现 。
原语是一种特殊程序。
用于:
进程的终止
撤消原语(就绪态/阻塞态/运行态→终止态>无)
1、从PCB集合中找到终止进程的PCB
2、若进程正在运行,立即剥夺CPU,将CPU分配给其他进程
3、终止其所有子进程
4、将该进程拥有的所有资源归还给父进程或操作系统
5、删除PCB
引发进程终止的事件
1、正常结束
2、异常结束
3、外界干预
进程的阻塞和唤醒(阻塞原语和唤醒原语必须成对使用)
进程的阻塞
阻塞原语(运行态—>阻塞态):
1、找到要阻塞的进程对应的PCB
2、保护进程运行现场,将PCB状态信息设置为”阻塞态“,暂时停止进程运行
3、将PCB插入相应事件的等待序列
引起阻塞的事件:
1、需要等待系统分配某种资源
2、需要等待相互合作的其它进程完成工作
进程的唤醒
唤醒原语(阻塞态—>就绪态):
1、在事件等待序列中找到PCB
2、将PCB从等待序列移除,设置进程为就绪态
3、将PCB插入就绪队列,等待被调度。
引起进程唤醒的事件:
1、等待事件发生(注意:因何事阻塞就应何事唤醒)
进程的切换
切换原语(运行态—>阻塞态/就绪态,就绪态—>运行态)
1、将运行环境信息存入PCB
2、PCB移入相应的队列
3、选择另一个进程执行,并更新其PCB
4、根据PCB恢复新进程所需要的运行的环境
引起进程切换的事件
1、当前进程时间片到
2、有更高优先级的进程到达
3、当前进程主动阻塞
4、当前进程终止
进程通信:
定义:
顾名思义,进程通信就是指进程之间的信息交换。进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。
为了保证安全,一个进程不能直接访问另一个进程的地址空间。
但是进程之间的信息交换又是必须实现的。为了保证进程间的安全通信,操作系统提供了一些方法。
共享存储
两个进程对共享空间的访问必须是互斥的(互斥访问通过操作系统提供的工具实现)。操作系统只负责提供共享空间和同步互斥工具(如P、V操作)。
设置一个共享空间,要互斥的访问共享空间。
两种方式:
基于数据结构的共享:比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
基于存储区的共享:在内存中画出一块共享存储区,数据的形式、存放位置都由进程控制,而不是操作系统。相比之下,这种共享方式速度更快,
是一种高级通信方式。
消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。
传递结构化的信息(消息头/消息体)
系统提供”发送/接受原语“
直接通信方式 :消息直接挂到接受进程的消息缓冲队列上
间接通信方式 :消息要先发送到中间实体(信箱)中,因此也称 “信箱通信方式” ,Eg:计网中的电子邮件系统。
管道通信
1、管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
2、各进程要互斥地访问管道。
3、数据以字符流的形式写入管道,当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,
此时读进程 的read()系统调用将被阻塞。
4、如果没写满,就不允许读。如果没读空,就不允许写。
5.、数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。
二、线程概念多线程模型。
线程:
1、可以把线程理解为“轻量级进程”
2、线程是一个基本的CPU执行单元,也是程序执行流的最小单位。引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,
从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
3、引入线程后,进程只作为除CPU之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)
4、线程则作为处理机的分配单元。
引入线程的变化:
1、资源分配、调度:
传统进程机制中,进程时资源分配,调度的基本单位
引入线程后,进程是资源分配的基本单位,线程是调度的基本单位
2、并发性:
传统进程机制中,只能进程间并发
引入线程后,各线程间也能并发,提升了并发性
3、系统开销:
传统的进程间并发,需要切换进程的运行环境,系统开销很大
线程间并发,如果是同一进程内的线程切换运行,则不需要切换进程环境,系统开销小
引入线程后,并发所带来的系统开销小
4、类比:
去图书馆看书。切换进程运行环境:有一个不认识的人要用桌子,你需要你的书收走,他把自己的书放到桌上同一进程内的线程切换=你的舍友要用这张书桌,
可以不把桌子上的书收走
线程的属性:
1、线程是处理机调度的单位。
2、多CPU计算机中,各个线程可占用不同的CPU。
3、每个线程都有一个ID、线程控制块(TCB)。
4、线程也有就绪、阻塞、运行三种基本状态。
5、线程几乎不拥有系统资源。同一进程的不同线程间共享进程的资源。
6、由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预。
7、同一进程中的线程切换,不会引起进程切换。不同进程中的线程切换,会引起进程切换。切换同进程内的线程,系统开销很小。切换进程,系统开销较大。
线程实现方式:
用户级线程(User-Level Thread, ULT)
用户级线程由应用程序通过线程库实现。所有的线程管理工作都由应用程序负责(包括线程切换)
用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预。
在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。(用户级线程对用户不透明,对操作系统透明)
可以这样理解,“用户级线程”就是“从用户视角看能看到的线程”
内核级线程(Kernel-Level Thread,KLT,又称“内核支持的线程”)
内核级线程的管理工作由操作系统内核完成。线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
可以这样理解,“内核级线程”就是“从操作系统内核视角看能看到的线程”
在同时支持用户级线程和内核级线程的系统中,可采用二者组合的方式:将n个用户级线程映射到m个内核级线程上 ( n >= m)
重点重点重点:
操作系统只“看得见”内核级线程,因此只有内核级线程才是处理机分配的单位。
例如:左边这个模型中,该进程由两个内核级线程,三个用户级线程,在用户看来,这个进程中有三个线程。但即使该进程在一个4核处理机的计算机上运行,也最多只能被分配到两个核,最多只能有两个用户线程并行执行。
多线程模型
多对一模型
多对一模型:多个用户及线程映射到一个内核级线程。每个用户进程只对应一个内核级线程。
优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。
缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。
一对一模型
一对一模型:一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
多对多模型
多对多模型: n用户及线程映射到m个内核级线程(n >= m)。每个用户进程对应m个内核级线程。
克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
进程同步、进程互斥
进程同步
知识点回顾:进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。
并发性带来了异步性,有时需要通过进程同步解决这种异步问题。
有的进程之间需要相互配合地完成工作,各进程的工作推进需要遵循一定的先后顺序。
进程通信――管道通信
读进程和写进程并发地运行,由于并发必然导致异步性,因此“写数据”和“读数据”两个操作执行的先后顺序是不确定的。而实际应用中,又必须按照“写数据→读数据”的顺序来执行的。如何解决这种异步问题,就是“进程同步”所讨论的内容。
同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。
同时共享方式
系统中的某些资源,允许一个时间段内由多个进程 “同时” 对它们进行访问资源。
进程互斥:
进程的“并发”需要“共享”的支持。各个并发执行的进程不可避免的需要共享一些系统资源(比如内存,又比如打印机、摄像头这样的I/O设备)
互斥共享方式
系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问资源。
我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。
此外还有许多变量、数据、内存缓冲区等都属于临界资源。
对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。
当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
对临界资源的访问,需要互斥的进行。即同一时间段内只能允许一个进程访问该资源。
四个部分:
1、进入区:检查是否可进入临界区,若可进入,需要 “上锁”
2、临界区:访问临界资源的那段代码
3、退出区:负责 “解锁”
4、剩余区:其余代码部分
需要遵循的原则:
1、空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
2、忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
3、有限等待:对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)。
4、让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。
注意:临界区是进程中访问临界资源的代码段。进入区和退出区是负责实现互斥的代码段。临界区也可称为“临界段”。
进程互斥的软件实现方法:
单标志法:
1、在进入区只做 “检查”,不 “上锁”
2、在退出区把临界区的使用权转交给另一个进程(相当于在退出区既给另一个进程 “ 上锁”,又给自己 “上锁”)
3、主要问题:不遵循 “空闲让进” 原则
双标志先检查:
1、在进入区先 “检查” 后 “上锁”,退出区 “解锁”
2、主要问题:不遵循 “忙则等待” 原则
双标志后检查:
1、在进入区先 “加锁” 后 “检查”,退出区 “解锁”
2、主要问题:不遵循 “空闲让进,有限等待” 原则,可能导致 ”饥饿“
Peterson算法:
1、在进入区 ”主动争取 — 主动谦让 — 检查对方是否想进、己方是否谦让“
2、主要问题:不遵循 ”让权等待“ 原则,会发生 ”忙等“
进程互斥的硬件实现方法:
中断屏蔽方法:
使用 ”开/关中断“指令实现,有点:简单高效。缺点:只适用于单处理机;只适用于操作系统内核进程
TestAndSet(TS指令/TSL指令):
old记录是否已被上锁;再将look设置为true;检查临界区是否已被上锁(若已上锁,则循环重复前几步)
优点:实现简单;适用于多处理机环境;
缺点:不满足”让全等待“
Swap指令(XCHG指令):逻辑同上TSL。
信号量机制
整型信号量
记录型信号量
复习回顾+思考:之前学习的这些进程互斥的解决方案分别存在哪些问题?
进程互斥的四种软件实现方式(单标志法、双标志先检查、双标志后检查、Peterson算法)进程互斥的三种硬件实现方式(中断屏蔽方法、TS/TSL指令、Swap/XCHG指令)
1.在双标志先检查法中,进入区的“检查”、“上锁”操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题;
2.所有的解决方案都无法实现“让权等待”
1965年,荷兰学者Dijikstra提出了一种卓有成效的实现进程互斥、同步的方法――信号量机制。
信号机制:
用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,
就可以设置一个初值为1的信号量。
原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,
因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。
一对原语: wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为 wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。
wait、signal原语常简称为P、V操作(来自荷兰语 proberen和 verhogen)。因此,做题的时候常把wait(S)、signal(S)两个操作分别写为P(S)、V(S)
整型信号量
1、用一个整数型变量作为信号量,数值表示某种资源数
2、整型信号量与普通整型变量的区别:对信号量只能执行初始化、P、V三种操作
3、整型信号量存在的问题:不满足让权等待原则
记录型信号量
1、S.value表示某种资源数,S.L指向等待该资源的队列
2、Р操作中,一定是先S.value--,之后可能需要执行block 原语
3、V操作中,一定是先S.value++,之后可能需要执行wakeup 原语
4、注意:要能够自己推断在什么条件下需要执行block 或 wakeup
5、可以用记录型信号量实现系统资源的“申请"和“释放”
6、可以用记录型信号量实现进程互斥、进程同步
wait(S)、signal(S)也可以记为P(S)、v(S),这对原语可用于实现系统资源的“申请”和“释放”。
S.value的初值表示系统中某种资源的数目。
对信号量s的一次Р操作意味着进程请求一个单位的该类资源,因此需要执行S.value--,表示资源数减1,当S.value <0时表示该类资源已分配完毕,
因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。
可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
对信号量s的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是S.value <=o,
表示依然有进程在等待该类资源,因此应调用wakeup 原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态→就绪态)。
用信号量实现进程互斥、同步、前驱关系
除了互斥、同步问题外,还会考察有多个资源的问题,有多少资源就把信号量初值设为多少。申请资源时进行P操作,释放资源时进行V操作即可。
实现进程互斥:
1、分析问题,确定临界区
2、设置互斥信号量,初始值为1(互斥问题,信号量初值为1)
3、临界区之前对信号量执行P操作
4、临界区之后对信号量执行V操作
实现进程同步:
1、分析问题,找出哪里需要 ”一前一后“ 的同步关系
2、设置同步信号量,初始值为0(同步问题,信号量初始值为0)
3、在 ”前操作“ 之后执行V操作
4、在 ”后操作“ 之前执行P操作
实现进程的前驱关系:
1、分析问题,画出前驱图,把每一对前驱关系都看成一个同步问题(前驱关系问题,本质上就是更复杂的同步问题)。
2、为每一对前驱关系设置同步信号量,初值为0
3、在每个 ”前操作“ 之后执行V操作
4、在每个 ”后操作“ 之前执行P操作
生产者消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。
(注:这里的“产品”理解为某种数据)
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各进程必须互斥地访问。