操作系统清华向勇陈渝版笔记(七) 进程与线程 PCB TCB 进程挂起 用户线程 内核线程 轻量级进

2019-07-14 06:32发布

前篇在此
操作系统笔记(五)覆盖和交换,虚拟内存
操作系统笔记(六)页面置换算法 index
7-1 进程的定义
7-2 进程的组成
7-3进程的特点
7-4 进程控制结构
7-5 进程状态
7-6 进程状态变化模型
7-7 进程挂起
7-8 线程管理
7-9线程定义

7-10 线程的实现
7-11 进程的上下文切换
7-11进程控制


7-1 进程定义
OS系统从只能跑一个程序到能跑多个。进程可以描述程序的执行过程。
进程:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
只有当一个程序被OS加载到内存中,cpu对其执行时,这个过程是动态的,称为进程。 7-2 进程的组成 包含了正在运行的一个程序的所有状态信息
 程序的代码
 程序处理的数据
 要知道现在执行哪条指令,程序计数器中的值指示将运行的指令。
 CPU寄存器会动态变化,一组通用寄存器的当前值,堆,栈等;
 各种系统资源,内存,外存,网络 进程与程序的联系
程序是进程的基础,代码控制操作,可以多次执行程序,每次构成不同的进程;进程是程序功能的体现;多次执行——某一个程序对应多个进程;调用关系——某一个进程包括多个程序
多对多的映射关系 进程与程序的区别
程序静态,有序代码的集合;进程动态,执行中可以是核心态/用户态,写的代码都是用户态,但有些操作比如读写文件只能由OS完成,
OS代表进程在内核中执行,此时为核心态;
进程是暂时的,是状态变化的过程,程序永久;
组成不同,进程包括程序,数据(可能变化),进程控制块(进程状态信息) 7-3进程的特点 动态性;
并发性(在一段时间内有多个程序在执行,不同于并行,是一个时间点有多个在跑,需多个CPU即多核,进程可以被独立调度并占用处理机运行):
独立性,正确性不受影响(通过OS给不同的进程分配不同页表);
制约性,因访问共享数据/资源或进程间同步产生制约,要同步互斥; 这里写图片描述

描述进程的数据结构:进程控制块,PROCESS control block PCB
OS给每个进程都维护了一个PCB,保存与之有关的所有状态信息。 7-4 进程控制结构 PCB进程控制块:进程存在的唯一标识,操作系统管理控制进程运行所用的信息集合,描述进程的基本情况和运行变化的过程。 用PCB的生成,回收,组织管理来完成进程的创建、终止和管理。 PCB含有三大类信息:
(1) 进程标识,哪个程序在执行,执行了几次(本进程的标识),产生者标识(父进程标识),用户标识
(2) 处理机状态信息保存区,主要就是寄存器,保存进程的运行现场信息:
  • 用户可见寄存器,程序使用的数据,地址
  • 控制和状态寄存器,程序计数器pc,程序状态字PSW
  • 栈指针,过程调用/系统调用/中断处理和返回时需要用到
(3) 进程控制信息
  • 调度和状态信息,用于操作系统调度进程并占用处理机使用。运行状态?等待?进程当前的执行现状
  • 进程间通信信息,各种标识、信号、信件等
  • 进程本身的存储管理信息,即指向本进程映像存储空间的数据结构,内存信息,占了多少?要不要回收?
  • 进程所用资源,打开使用的系统资源,如文件
  • 有关数据结构连接信息,父进程,子进程,构成一个链,进程可以连接到一个进程队列,或链接到其他进程的PCB
PCB的组织方式:
链表(便于插删,用于通用的OS):同一状态的进程PCB为一链表,多个状态对应更多个不同的链表,就绪链表,阻塞链表
索引表(数组,不利于插删,使用于固定数目的进程,相对创建更快捷):同一状态的归入一个index表(每一个index指向PCB),就绪/阻塞索引表
这里写图片描述 7-5 进程状态 进程生命期管理:创建—>运行—>等待—>唤醒—>结束
引起进程创建的3个主要时间:系统初始化时,创建INIT进程,INIT再负责创建其他进程;用户请求创建一个NEW PROCESS,正在运行的进程执行了创建进程的系统调用。
运行:内核选择一个就绪的进程,让它占用处理机(cpu)并执行
调度算法—-如何选择?
等待(阻塞)的三种情况:请求并等待系统服务,无法马上完成;启动某种操作(和其他进程协调工作),无法马上完成;需要的数据没有到达
进程自己触发阻塞,因为只有自己才知道何时需要等待某事件
唤醒的情况:需要的资源可被满足,等待的事件到达,都意味着可将该进程的PCB插入到就绪队列
因为自身没有占用cpu执行,所以只能被OS或其他进程唤醒
结束的情形:自愿(正常退出,错误退出),强制性的(致命错误,被其他进程所杀) 7-6 进程状态变化模型 结束前的三种状态:运行态(running),就绪态(ready 获得除CPU即处理机之外的一切资源,一旦得到CPU就可以运行),等待状态(阻塞态,blocked) 进程状态变化模型
这里写图片描述
此外还有:
这里写图片描述
创建态(new) 已创建还没就绪
结束态(exit) 正在从OS中消失,PCB还存在 running to ready:分时-时间片到了,就切到另一个程序,这个转变为ready,由OS结合时钟完成。
new->ready:很快,只是生成pcb 进程挂起模型 7-7 进程挂起
不同于进程阻塞。挂起时没有占用该内存空间,而是映像在磁盘上。类似虚存中,有的程序段被放到了硬盘上。
Suspend:把一个进程从内存转到外存。
可以合理、充分地利用系统资源。 两种挂起状态:
 阻塞挂起,进程在外存等事件blocked suspend
 就绪挂起,进程在外存,但进了内存就能执行ready suspend 状态转换:
在内存中被挂起:
blocked to blocked suspend: 没有进程ready或ready进程要求更多内存时挂起,以提交新进程或运行就绪进程
ready to ready suspend: 多个就绪进程挂优先级低的,有高优先级阻塞(OS认为会很快就绪)进程和低优先ready进程时,挂低优先的就绪进程
running to ready blocked: 抢先式分时系统,空间不够或高优先级阻塞挂起进程进入就绪挂起时,正在运行的进程被就绪挂起
外存中:
blocked suspend to ready suspend: 相关事件出现时,转变为ready suspend但还在硬盘上。
解挂/激活 activate:外存到内存,需要运行该进程时
ready suspend to ready: 没有就绪进程或挂起就绪优先进程优先级高于当前就绪进程时
blocked suspend to blocked:当一个进程释放了足够内存时,OS把高优先级阻塞挂起(认为很快出现等待的事件)转换为阻塞进程 OS怎么通过PCB和定义的进程状态来管理? 以进程为基本结构的OS,底层为CPU调度程序(执行哪个?中断处理等),上面是各种进程。 OS要维护一组状态队列(重要的数据结构),表示系统中所有进程的当前状态。
就绪队列,各种类型的阻塞队列,挂起队列
PCB根据状态排入相应队列,状态变化加入和脱离队列 这里写图片描述

7-8 线程(thread)管理 更小的能独立运行的基本单位——线程
单进程实现方式:
如果CPU能力不够强,排在前面的函数太慢,各个函数之间不能并发,影响实现效率。
多进程的变化方法:拆开函数,并发,轮着走。进程间通信,共享数据?维护进程的OS开销大,创建,切换,都要保存回复当前进程的状态信息 提出一种新的实体,满足实体间可以并发执行,共享相同的地址空间。而不是像进程,需要OS帮助通信和交换信息。 7-9 什么是线程
thread: 进程中的一条执行流程 重新理解进程,由两部分功能组成:
(1)资源管理,包括地址空间(代码段,数据段),打开的文件等的资源平台(环境)等 和 (2)从运行的角度,线程,即代码在这个资源平台上的一条执行流程
一个进程所拥有的线程共用进程的资源平台。利于通信。
这里写图片描述 线程有自己的TCB,thread control block, 只负责这条流程的信息,包括PC程序计数器,SP堆栈,State状态,和寄存器。有不同的控制流,需要不同的寄存器来表示控制流的执行状态,每个线程有独立的这些信息,但共享一个资源。 线程=进程-共享资源
线程是控制流,一个进程中可以同时存在多个线程,各个线程并发执行,共享地址空间和文件等资源。
如果一个线程写错了,崩溃,如破坏了数据,会导致这个进程的所有线程都崩溃。 适用范围:高精计算中比如天气预报,代码相对统一,对性能要求高,用线程;网络有关如开一个浏览器的N个页面,一个页面有问题所有的都关闭了,现在都改成了用进程。如Chrome,用的就是一个进程打开一个网页。 多线程,在进程空间内有多个控制流且执行流程不一样,有各自独立的寄存器和堆栈,但共享代码段,数据段,资源。
这里写图片描述 线程与进程的比较
  • 进程是资源分配单元(内存,打开的文件,访问的网络),线程是CPU调度单位,CPU也是一种特殊的资源,要执行控制流需要的相关信息
  • 进程拥有一个完整资源平台,而线程只独享必不可少的资源如寄存器和栈
  • 线程同样具有就绪,阻塞和执行三种基本状态和转换关系
  • 线程能减少并发执行的时空开销:
    — 线程的创建时间、终止时间、同一进程内线程的切换时间都更短,因为进程要创建一些对内存和打开的文件的管理信息,而线程可以直接用所属的进程的信息,因为同一进程内的线程有同一个地址空间,同一个页表,所有信息可以重用,无失效处理。而进程要切页表,开销大,访问的地址空间不一样,cache,TLB等硬件信息的访问开销大。另外线程的数据传递不用通过内核,直接通过内存地址可以访问到,效率很高。
7-10 线程的实现 三种实现方式:
用户线程,在用户空间实现,OS看不到,由应用程序的用户线程库来管理;POSIX Pthreads, Mach C-threads
内核线程,在内核中实现,OS管理的;Windows
轻量级进程lightweight process:内核中实现,支持用户线程。Solaris, Linux 用户线程与内核线程间,可以是多对一,一对一,多对多。
这里写图片描述 在用户空间实现的线程机制,不依赖于OS内核,有一组用户级的线程库函数来完成线程的管理,包括创建、终止、同步和调度等。 使用用户态线程的优缺点:
优点:OS只能看到线程所属的进程,可用不支持线程技术的多进程OS,每个进程都有私有的TCB列表来跟踪记录各个线程的状态信息(PC, 栈指针,寄存器),TCB由线程库函数来维护;切换线程也由线程库函数完成,无用户态/核心态切换,速度快;每个进程由自定义的线程调度算法。灵活。
缺点:一个线程发起系统调用而阻塞,那整个进程都在等待(OS只能判断进程);一个线程开始运行后,除非主动交出CPU使用权,否则所在进程中的其他线程都无法运行;由于时间片分给进程,多线程时时间片更少会慢。 这里写图片描述 内核线程,OS内核来完成线程的创建终止和管理。OS的调度单位不再是进程而是线程。进程主要完成资源的管理。
内核维护PCB和TCB,PCB维护了一系列的TCB,具体调度是TCB完成,只要完成一次切换(或创建,终止)就要完成一次用户态到内核态的变化,系统调用/调用内核函数,OS开销比用户线程大。在一个进程中,某个内核线程阻塞不影响其他线程运行;时间片分给线程,多线程的进程自动获得更多CPU时间。Windows NT/2000/XP 轻量级进程
内核支持的用户线程。一个进程有一个或多个轻量级进程,每个轻量级进程由一个单独的内核线程来支持。 7-11 进程的上下文切换 context switch 进程共享CPU,停止当前进程,并调度其他进程的切换叫做上下文切换。
必须切换前存储上下文,切换后恢复让进程不知道被暂停过,必须快速(上下文切换频繁),因为有时间开销所以也要尽量避免
要存储哪些context?
寄存器(pc,sp,…),CPU状态等 进程执行中要关注寄存器,PC(进程执行到了什么地方),栈指针(调用关系,相应的局部变量位置)等。
这些信息要被保存到PCB中,进程挂起时要把PCB的这些值重置,恢复到寄存器中去,使接下来进程可以继续在CPU上执行。 上下文切换的开销越小越好,且所有信息都与硬件紧密相连,所以OS中实现是用汇编代码。 需要知道哪些进程能切换?
PC为活跃进程准备了进程控制块PCB,OS将不同PCB放在不同的状态队列链表里便于选择
 就绪队列
 等待I/O队列—-分为每个设备的队列
 僵尸队列 7-11进程控制——创建进程,加载和执行进程,等待和终止进程
创建——————fork() 父进程创建子进程 加载:
系统调用EXEC()加载程序取代当前运行的进程
这里写图片描述
如果出现了WHY would I execute? 说明CALC出错了
PID是子进程的ID,存在PCB中的一个整数,父进程要等子进程。WAIT返回,子进程结束了。
应该是else if (pid>0) 当加载时,代码数据都复制了一份,PID没变,从Shell变为calculation
用户: int pid=fork(); if(pid==0) exec(“/bin/calc”); else wait(pid); --> int calc_main() exec_in(ln) 操作系统: pid=127 open files=”/bin/sh” last_cpu=0 --> pid=128 open files=”/bin/calc” last_cpu=0 进程控制块 process control blocks(PCBs)
首先fork创建了一个新的地址空间,然后子进程的地址空间完全复制了父进程,加载后PCB的信息有变化,用户空间的STACK heap CODE 也都有变化。
  • exec()允许一个进程加载一个不同段程序并在MAIN执行,=_start
  • 允许一个进程指定参数的数量argc和字符串参数数字argv
  • 允许进程指定不同的控制流,OS中可指定不同的应用程序,即调用成功=相同进程,运行的是不同的程序!
  • Code&&stack&&heap重写
Fork()的简单实现:
对子进程分配内存,复制父进程的内存和CPU寄存器到子进程中,开销大大大!
99%调用fork是为了接下来调用exec:fork中内存复制没用,子进程将可能关闭打开的文件和连接,开销高 如何优化,结合在一个调用中(windows)?省略复制父进程到子进程的过程, 早期LINUX采用虚拟fork
Vfork()
  • 一个创建进程的系统调用,不用创建一个同样的内存映像
  • 一些时候是轻量级fork,只是复制了一小部分父进程的内容
  • 子进程应该几乎立即调用exec
  • 现在不再使用,如果采用了copy on write COW技术(通过OS的虚存管理,只复制了父进程meta元数据即页表,指向的是同一地址空间,对某个地址单元进行写操作时触发异常,父子各把要用的页复制成两份,使父进程和子进程拥有不同的地址。按需,光读不用复制,写才用)
等待:
wait()系统调用是被父进程用来等待子进程的结束,一个子进程向父进程返回一个值,父进程必须接受这个值并处理。
为什么要让父进程等?而不是直接结束?
当进程执行完毕退出后,几乎所有资源都回收到OS中。但有个资源很难回收,就是PCB,PCB是代表进程存在的唯一标识,OS要依据PCB执行回收。这个功能由父进程完成。 Wait使父进程睡眠,当子进程调用exit时操作系统解锁父进程,将通过exit传递得到的返回值作为wait调用的一个结果(连同子进程的pid一起)。关闭所有打开的文件和连接,释放内存,释放大部分支持进程的OS结构,检查父进程是否存活。
如果父进程存活,它保留exit结果的值直到父进程需要它,进入zombie/defunct状态。
如果父进程先于子进程死掉了,它释放所有的数据结构,这个进程死亡。
最后清理所有等待的僵尸进程。 最早的进程ROOT进程/主动进程/RIT进程会定期扫描PCB列表,找到僵尸状态的进程并清理,使OS中不会僵尸越积越多。
如果这里没有子进程存活,wait立刻返回。如果有父进程的僵尸等待,WAIT立即返回其中一个值并解除僵尸状态。 僵尸状态:
就是调用了EXIT但还没有wait返回的时候。将死,还没死。无法正常工作,只是等待被父进程回收。 执行EXEC()时,进程可以处于不同的状态。首先是runnig, 然后加载、运行,加载时间长要等running->blocked。
这里写图片描述 下一篇:
操作系统(八)CPU调度 短剩余时间 吞吐量 轮循 实时调度 多处理器调度 (清华 向勇 陈渝版)