【操作系统】第二章--进程的描述与控制--深入与解释(1)

深入解释之前可以先看相应的笔记理解→【操作系统】第二章–进程的描述与控制–笔记与理解(1)

第二章–进程的描述与控制–深入与解释(1)

从操作系统的角度认识进程

OS为什么要引入进程
  • 整体来说:为了使程序在多道程序环境下能并发执行,并能对并发执行的程序加以控制和描述,从而在操作系统中引入了进程概念;
  • 从状态发展需要来说:在多道程序环境下,执行时需要共享系统资源,从而导致各程序在执行过程中出现相互制约的关系,此时程序的执行表现出了间断性(执行-暂停-执行)的特征,这些特征在程序执行的过程中发生就产生了动态性,而传统的程序本身就只是一组指令的集合,是静态的概念,而静态无法描述程序执行的情况。此时,我们根本没有办法从程序的表面看出程序执行、停顿的状态,更没办法看出程序之间的关系,所以为了在静态的概念上深刻描述程序动态性的执行过程与程序并发执行的过程,引入了具有动态性的进程;
  • 从并发特性与独立特性来说:程序因为没有建立PCB而不能参与并发执行,所以引入进程就是为了使其进程实体等和其他进程实体并发执行;同样,没有建立PCB的程序不能作为一个独立的单元参与运行,因而引入能独立运行、独立获得资源、独立接收调度的进程作为基本单位;
到底什么是进程
  • 严格定义:传统OS中进程定义:进程是进程实体的运行过程,是系统进行资源分配的调度的一个独立单位;
  • 其余典型定义:①进程是程序的一次执行;②进程是一个程序及其数据在处理机上顺序执行时所发生的活动;③进程是具有独立功能的程序在一个数据集合上运行的过程,他是系统进行资源分配和调度的一个独立单位;
为什么说动态性和并发性是进程的主要特征,其他特征如何体现
  • 动态性:相比于程序的“不灭不死,不变不动”(静态,指令的集合),进程作为一种实体进行执行是具有生命周期的,他由创建而产生、由调度而执行、由撤销而消亡。就像是网购的物品订单,他不是你买的东西,他只能代表数据整体,只有你对物品下单了,物品从厂商运出仓库被创建出来、经过国家物流的调度以及传输中转被执行最终到自己手里的才是物品本身,倘若你突然不想这个东西了,或者你已经拿到这个东西了,那么此时的订单就已经完成或者无效了,就是整个物品的因你而存在的创建传输执行过程就撤销消亡了;
  • 并发性:OS本身重要的特性就有并发性,而在进程这里再一次提到,不仅仅是因为操作系统,更是因为进程作为程序的实例的运行过程。从程序的角度看,每个程序之间都是平行关系,不存在并发的特质;而当有了进程之后,每个程序的进程就可以通过对CPU以及资源的切换执行,即体现出了并发特性;
  • 独立性:独立性是指进程实体是一个能独立运行、独立获得资源独立接受调度的基本单位。因为每个进程被创建出来的时候,就是独立创建的,创建出来后进行调度以及执行也都是已经赋予好的,就像是网购下单好的东西从发货到递送到收货的过程中,不用再去联系厂商或者做其他额外的事情才能保证他的正常执行,这就是独立性;
  • 异步性:是指各进程各自独立、各自以不可预知的速度向前推进。也就是各个进程之间就算同时被创建进行执行,也不能确定到底是哪个进程率先完成执行。就像是同时网购下单的物品也不一定是哪一个先到,因为他各自的速度是不可预知的;
进程和程序之间到底是什么样的关系
  • 进程和程序关系理解:我认为可以类比到书本知识(程序)和程序设计(进程),书本中有的信息不过是一个一个的干条条、一个一个的知识点罗列、一篇一篇章节的介绍和解释(数据的集合),他是静的(静态),知识就那样印在书本里不管你理解没理解看没看,都不会对书有什么影响;而程序设计却是用到了书本中的各个知识,他将书本中的知识通过代码段或者电路设计通过我们的思维与理解(CPU调度)呈现出来,他就是书本知识框架与联系的一个体现;而程序设计时,可能用到了不止一个课本及参考书,多个书本的知识(多个程序),通过我们对其的总和理解(并发性)完成出的设计大多比用一个书本做出来的更为全面。
为什么要引入进程的状态
  • 引入状态原因:因为进程是动态的,而且是具有生命周期的,而他的生命周期就可以划分为一组状态,通过这组状态对整个进程的刻画,我们更能直观的感受到进程的生命状态;
状态之间的转换说明了什么
  • 状态之间转换:
    1. 整体性:说明进程各个状态之间的触发以及执行都受到系统的调度,也从侧面说明我们对计算机的操作实际上是通过我们对各个进程的操作实现的;
    • 具体转换:
      1. 就绪态>>运行态:CPU空闲时间被调度选中一个就绪进程执行,说明了系统的多道批处理以及分时处理,不会让CPU有空闲的时间,提高CPU的利用率;
      2. 运行态>>阻塞态:等待使用资源或某事发生,比如键盘输入、外设传输等,说明了我们操作计算机是通过操作进程来实现的,也是操作系统进程图像的体现;
      3. 阻塞态>>就绪态:等待得到满足或某事已经发生,如人工干预结束,说明了尽管我们通过进程与计算机进行交互,进程的执行过程仍然是要按照系统规定好的步骤完成,体现计算及运行过程的严谨性;
      4. 运行态>>就绪态:运行时间片到,或出现更高优先级,一个说明了CPU会为每个进程单独提供一个时间片,可使用的时间就是这么多,不能因为某一个程序的停滞或者异常影响其他进程的执行,体现了独立性;一个说明进程的优先级的高低可以影响到使用CPU以及资源的优先权限;
Linux在描述进程状态上有什么特色
  • Linux查看进程状态:$ ps -l 其中第二列的S代表这个进程的状态;
  • 各个状态描述:
    1. R (Running)— TASK_RUNNING(可执行状态)
    2. S (Sleep)— TASK_INTERRUPTIBLE(可中断的睡眠状态)
    3. D(Deep sleep) — TASK_UNINTERRUPTIBLE(不可中断的睡眠状态)
    4. T (Stopped)— TASK_STOPPED或TASK_TRACED(暂停状态或跟踪状态)
    5. Z (Zombie stop)— TASK_DEAD - EXIT_ZOMBIE(退出状态,成为僵尸进程)
    6. X (Dead)— TASK_DEAD - EXIT_DEAD(退出状态,进程即将被销毁)
    7. t(tracing stop)—跟踪状态
  • 进程状态特色:不仅表明了进程此时正处于的状态,也说明了进程的操作权限(比如S和D)
  • 启发:相比于操作系统的进程状态,Linux的进程状态表述更加的细致,但并不是说操作系统的进程状态表示就没有Linux的进程表述清晰,状态的表述要根据其所处的位置而变化,或细化、或宽泛,就想根据不同学生的特质因材施教一样。
    Linux进程状态
设想你是OS的设计者,你要如何设计进程的PCB
  • 分析:PCB是进程管理快,他是操作系统中最重要的记录性数据结构,对进程进行管理与控制,所以设计出来的PCB必须可以描述进程的所有信息以及所有状态,即是什么?在干什么?
  • 设计:
    • 信息:
      1. ID:每个进程自己独有的唯一的可以表示自己的ID,就像公司中的工号,可以唯一代表自己;
      2. 特征信息:每个进程都需要有自己的进程所属,就像工作中,我们每个人所属的部门;
      3. 状态信息:进程目前所处的状态(就绪、阻塞、运行),就像在工作时的状态,或是正常上班、或是出差在外、在家休假等;
      4. 优先级信息:每个进程可以获得CPU控制的大小,就像是在工作中你的职位薪水能力越高,就越有可能优先代表公司出差交涉;
      5. 进程调度信息:进程当前等待或者已执行的时间,就像是出差人员离交涉开始的时间或者已经交涉了的时间;
      6. 事件信息:进程由执行到阻塞所发生的事件吗,就像是出差过程中交涉中断的原因;
      7. 通信信息:进程之间的通信关系,就像是各个公司派去代表公司开集体会议时,各个代表之间的关系;
      8. 资源需求,分配控制信息:进程执行做需要的资源以及各个进程的分配情况,就像是派去出差所需要的材料以及各个出差项目分配到的人员情况;
      9. 进程实体信息:程序路径名称和进程数据所在位置的信息,就像是出差去的路线以及开会时所在的地点
    • 控制:
      1. 现场保护区:保护处在阻塞状态的进程空间,就像是出差人员对外交涉突然中断时,公司会为提供休息区;
      2. 工作区:进程执行时的空间,就像是出差开会交涉的会议厅
用思维导图对进程控制块进行全面的描述

PCB

进程控制块的方方面面

理解其双向链表的定义
struct list_head {
    struct list_head *next, *prev;
};
  • 正是因为这个链表和我们平时使用的链表不一样,他只有指针域,没有数据域,才提供给我们了很大的方便,让我们很方便的去拓展他,我们也只需要自己定义一种数据类型,指定自己的数据域,只用这里的指针域就可以创建出多种结构:
    1. 双向链表:在原本就有前驱和后继的结构链表中加入数据域,就形成了双向链表;
    2. 双向循环链表:在双向链表的基础上,将头节点的前驱指向尾节点、尾节点的后继指向头结点,就形成了双向循环链表;
    3. 单链表:在双向链表的基础上减少一个指针域,就退化成了单链表;
    4. 队列:在双向链表的基础上先实现单链表,再在单链表的基础上只允许在链表的首尾进行插入删除操作,就形成了队列;
    5. 栈:在双向链表的基础上先实现单链表,再在单链表的基础上只允许在链表的头进行插入和删除操作,就形成了栈;
    6. 树状结构:以树举例:在双向链表的基础上改变前驱和后继的依赖关系,对他人为地赋予一种意思,就可以转化为树结构,比如树中每一个节点的前驱都指向他的第一个孩子,后继都指向他的兄弟结点,就实现了用双向链表结构用孩子兄弟表示法思想的树结构;而在这个数结构的基础上,将前驱的指向解释为左孩子、后驱的指向解释为右孩子,则就将刚刚的树结构转化为二叉树的结构进行表示;
      结构
理解第一宏

解释第一宏

进程/线程创建及其线程模型

为什么引入线程
  • 线程具有许多进程所具有的特征,所以线程又被称为“轻量级进程”,所以线程的引入必然和进程脱不开干系,于是从线程与进程入手,理解一下,引入线程的原因:
    1. 直接动机:
      1. 减少进程切换操作时的较大时空开销:由于进程是资源的拥有者,所以在创建、撤销、切换操作中需要较大的时空开销,限制了并发成都的进一步提高。为了减少进程切换的开销,运用分治(分而治之)的思想,把进程作为资源分配单位和调度单位这两个属性分开处理,即进程还作为资源分配的单位,但是不作为调度的基本单位,把调度执行与切换的责任交出去,重新设定一个来完成调度执行和切换,此时就出现了“线程”来承担这项任务。打个比方,公司(操作系统)最开始各个部门的人手都不是很多,可能有一个人身兼双职(进程两大基本属性)的情况,但是这时公司发现这样不行,要把工作按照类别分开(分治思想),各自负责各自的项目,才能提高公司办事效率,于是就告诉身兼两职的可以自己进行招纳助手,替自己分担任务,于是助手(线程)的出现提高了各项的效率。
      2. 增加并发度:举个例子来说,QQ可以视频通话,在视频通话同时,还能进行文字聊天,同时还能传送文件。但是进程是程序的一次执行,显然,QQ可以同时进行的这些功能并非由一个程序顺序处理就能实现。所以说,有的进程是需要“同时”处理很多事情的,但传统的进程只能串行地执行一系列程序,基于此,就引入了线程,来增加并发度。
    2. 线程自身的优势:
      1. 占用较少的系统资源, 线程的创建、撤销和切换花费的时空间开销都小,尤其是在切换方面,传统的进程间并发,需要切换进程的运行环境,系统开销很大,但是线程间并发的话,如果是同一进程内的线程切换,则不需要切换进程环境,系统开销小;
      2. 提高了并发度:传统进程机制中,只能进程之间并发,但是引入线程后,各线程间也能并发,这就提高了并发度;
      3. 提高了性能、效率和响应度:同一进程内的线程共享内存和文件, 它们之间相互通信无需调用内核, 效率高。如果对一个交互程序采用多线程,那么即使其部分阻塞或执行较冗长的操作,该程序仍能继续运行,从而增加了对用户的响应程度。例如,多线程web浏览器在用一个线程装入图像时,能通过另一个线程与用户交互。
      4. 使多处理机效率更高:在多处理机系统中,传统的单线程进程,不管有多少处理机,该进程只能运行在一个处理机上,但是有了线程之后,有了多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,由此加速了进程的完成。打个比方,以前为公司出差只有这个一个人(单线程进程),那就只能一次去一个地方出差(一台处理机),但是当他有了助手(线程)之后,他就可以将任务分配开,各个出差的地点都可以去,各地的出差任务也可以同时进行(多处理机),必然加速了任务的完成。
      5. 改善了程序的结构。
为什么说线程只拥有栈和少量寄存器,其他资源都共享进程的资源
  1. 因为线程又称为“轻量进程”,为了实现线程的高效性,线程本身并不能携带太多的信息,不然就会像进程一样,速度变慢,所以线程只拥有少量属于自己的资源,比如线程会私有程序计数器,用来记录接着要执行哪一条指令;线程还私有寄存器,用来保存线程当前正在使用的变量;线程还会有堆栈,用来记录程序的执行路径。这些资源和信息就是进程与线程以及线程之间区分以及运作运行的区别,是线程不可缺少的东西。
  2. 共享的资源:地址空间、全局变量、打开的文件、子进程、即将发生的定时器、信号与信号处理程序、账户信息等;
  3. 父亲安排10个孩子干活,整体来说各司其职,会提高干活的效率,同样的,多线程也提高了效率和资源利用率。但是就像10个孩子如果需要同时用到某个工具所以产生冲突一样,因为线程和线程之间的资源使用是共享的,多线程间也有可能产生对资源的争夺与冲突,就需要有合理的分配和调度,或者说在需要资源时进行抢占以及优先排序使用,所以若有10个线程,每个线程在系统调度执行时间上占用的时间片都是1;线程自己基本上不拥有系统资源,所以它不是资源分配的基本单位,它只拥有一部分在运行中必不可少的与处理机相关的资源;就像十个孩子都想要得到父亲仅有的一份零花钱,我们必须通过自己想使用零花钱原因的合理性、优先性,以及抢占的方式获取父亲仅有的零花钱,而当抢到零花钱时,会用最快的速度完成自己的事情,因为自己使用完后,别的孩子还要进行抢占使用。
用户级线程和内核级线程的区别和联系(Linux角度)
  1. 用户级线程和内核级线程:
    1. 用户级线程仅存在于用户级中,由应用程序通过线程库完成所有线程的管理,用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的,内核不知道线程的存在,线程切换也不需要内核特权。内核管理含线程的进程的活动,但不管理线程。而内核级线程所有的线程管理由内核完成,内核维护进程和线程的上下文,线程之间的切换需要内核支持。
    2. 用户级线程执行系统调用指令时将导致其所属进程被中断,内核级线程执行系统调用指令时,只导致该线程被中断,不会影响该进程内的其他线程。
    3. 用户级线程的程序实体是运行在用户态下的程序,而内核级线程的程序实体则是可以运行在任何状态下的程序。
    4. 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在内核级线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
  2. 进程和线程:
    1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;进程之间相互独立,某进程内的线程在其他进程不可见;从调度和切换角度来说,线程上下文切换比进程上下文切换要快得多。
    2. 线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位;资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段,数据段,扩展段。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量;处理机分给线程,即真正在处理机上运行的是线程;线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
进程和线程到底共享哪些资源,哪些不能共享
  • 共享:地址空间、全局变量、打开的文件、子进程、即将发生的定时器、信号与信号处理程序、账户信息、文件描述符表等;
  • 不共享:线程的程序计数器、寄存器、堆栈、状态,进程中的信号屏蔽字等。

线程共享变量+1操作

展示代码和有出错的运行结果

线程共享源

展示原子操作的相关代码和结果

线程共享原子操作

展示加锁操作的相关代码和结果

线程共享加锁

进程同步

什么是进程同步,为什么要引入进程同步
  1. 我们先了解一下同步关系,是多个并发进程(线程)协同完成一项任务时,由于数据交换需要而在进程(线程)执行次序上的的约束关系,也可以称作合作关系,进程同步就是多个并发进程协同完成一项任务时,由于数据交换需要而在进程执行次序上具有约束关系。这种多个相关进程在“执行次序上的协调”称为进程同步。用于保证多个进程在执行次序上的协调关系的相应机制称为进程同步机制。
  2. 原因:
    1. 进程具有异步性,即各个并发执行的进程以各自独立的、不可预知的速度向前推进,操作执行的先后顺序是不确定的。就好比以管道通信为例,它需要写进程先将数据写进管道内,写满后,再由读进程读出数据。读进程和写进程并发执行,由于并发必然导致异步性,因此“写数据”和“读数据”两个操作执行的先后顺序是不确定的。但是实际应用的时候,又必须按照“写->读”的顺序来执行。那么如何解决这种问题,就引入了进程同步,即为完成某种任务而建立的两个或多个进程,因为需要在某些位置上协调它们的工作次序而产生制约关系。
    2. 进程同步机制,可以对多个相关进程在执行次序上进行调整,使并发执行的诸进程之间能按照一定规则或时序共享系统资源,并很好地相互合作,从而使程序的执行具有可再现性。
同步机制应该遵循的原则
  • 同步机制应遵循原则:空闲让进,忙则等待,有限等待,让权等待;
    1. 空闲让进:当进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己临界区,以有效地利用临界资源;
    2. 忙则等待:当已有进程进入临界区时,表名临界资源正在被访问,因此其他试图进入临界区的进程必须等待,以保证对临界资源的互斥访问;
    3. 有限等待:对要求访问临界资源的进程,应保证在有效时间内能进入到自己的临界区,以免造成“死等”状态;
    4. 让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态;
  • 举一个简单的例子:假如说今晚是平安夜,很多人都和家人情侣小伙伴出去吃火锅了。但是火锅店的位子是有限的,你要想执意在这儿吃,你就得排队等候。当一个桌的顾客吃完离开,就空出了位置,此时,排队最前面的就能进去用餐,也就是“空闲让进”;这个人和他的小伙伴占了那个位置,然后火锅店的位置又满了,其他人还是得继续等待,这就是“忙则等待”;但是排队的人等待也不是永远的等待,他肯定会在有限的时间内等到的,不可能让人家永远饿下去啊,这就是“有限等待”;但是你突然发现你没带钱也没带手机,你等也是白等,就算排到了你没带钱也没办法去吃火锅啊,那么就得及时退出,不要白费时间,白占位置,这就是“让权等待”。
什么是信号量,为什么引入信号量机制
  1. 信号量:信号量就是OS提供的管理公有资源的有效手段,是在多线程环境下使用的一种设施,本质上是一个变量(可以是一个整数,也可以是更加复杂的记录型变量),可以用信号量来表示资源的数量。用户编程的时候可以直接调用,不必自己设计。目的是为了实现进程间的同步和互斥。主要是实现进程间的同步。
  2. 引入原因:单纯的信号固然可以解决很多问题,等待信号(睡眠)以及发信号(唤醒),但是真正地实际需求还需要能记录一些信息,比如多个进程的问题,信号是不能记录有多个进程等待的,所以就引入了信号量,来记录几个进程在等待、有多少可用资源,以及当进行某操作后,还有多少等待的进程和可用的资源。这样的话,也就很便捷而有效地实现了对进程同步的监控。
  3. 例如:现在各种大型商场都有地下或者顶层停车场。停车场外除了显示有没有车位,还要显示剩余车位的多少。这就是一个从信号到信号量的过程。假设现在只显示有没有车位不显示剩余车位的数量,那么在只剩一个车位的情况下,很多车驶入了停车场却发现并不能停车,这对他们来说是一种时间上的浪费。但是如果能够时刻更新剩余车位的数量,当司机在停车场外发现没有车位,他就不会再进入停车场。信号量使监控变的更加便捷高效。
给出记录型信号量wait()和signal()操作的实现代码

记录型

用wait()signal()实现司机和售票员的同步

司机与售货员

猜你喜欢

转载自blog.csdn.net/weixin_44321600/article/details/107491316