操作系统—进程and线程007

操作系统——进程007

在这里插入图片描述

一、进程(PROCESS)描述

1.1如何管理 CPU,如何引入进程

CPU一个个程序去执行,那么等待时间长,所以利用率很低,所以CPU需要交替的执行程序,那么就意味着切来切去,那么就需要记录切换程序时的状态,所以引入了进程。
启动一个进程,让CPU去执行这个进程,操作系统需要启动多个进程,CPU去跑多个进程,从而CPU的利用率就会更高。

1.CPU的工作原理:自动的取值、执行

将一个程序存放到内存中,设置一个pc起始地址例如50,CPU就会把起始地址放到地址总线上,发出一条取指的命令。从而内存就会把地址50的这条指令,通过总线发送到CPU中。CPU得到指令后,进行解释和执行。
如何使用CPU,让它工作起来呢: 设置好PC的初值,不断的取值,执行。
管理CPU最直观的方法是:设好初值。

2.接1,问题就是I/o速度慢,CPU速度快,如何解决?

CPU执行1百万条指令,和执行一条I/o执行,时间是相等的。那么利用率是百分之五十。所以说很浪费CPU的效率。那么如何解决?
在这里插入图片描述
例子引入进程:当我们烧水的时候,将暖壶放好后,不需要一直等待着暖壶水开,在这段时间我们可以去做其他事情,等到暖壶水开了,我们在切换到暖壶这里就好了。这样CPU就会忙碌起来,而不是一直等待。CPU的利用率就高了,管理CPU也就好了。

多个程序在内存中切来切去,多道程序交替执行。
一个CPU上交替的执行多个程序:并发
在这里插入图片描述

3.动态的程序,需要记录此时的信息。PCB

在切换CPU时,不光要程序切换,而是将记录也要切换(也就是说将数据保存到PCB数据结构中)。
PCB就是用来记录保存程序切时的所有信息,将来切回来才会继续执行。
例子,当我看书时,有人敲门,我需要先将页面记住,开门后。在回来看书时,我在继续从记录的页面开始往后看。
在这里插入图片描述

4.引入进程的概念

进程是运行的程序,所以说进程是一个动态的。

1.2、进程定义

  • 进程: 一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程
    在静态程序中,操作系统将执行程序调入到内存后, 执行起来后,能通过CPU来执行这个程序的一条条指令,对数据进行处理,完成功能。这是一个动态过程。这个执行过程称为进程。
  • 可以将运行的程序,看为进程。
  • CPU的利用率忽高忽低代表进程占用CPU多与少。
  • 进程在某种形式上,代表一种运行程序执行的过程,同时也会消耗计算机各种资源,CPU、内存、网络、i/o…
  • 为什么要有进程?
    当时操作系统只能支持跑一个程序,经过发展CPU能力越强,从而可以在内存中存放更多地程序。 在操作系统中多个程序运行,用程序表示多个程序不太合适了,因为一个程序可能跑了多份进程。所以引出了进程,进程可以更好的表示程序运行的过程。

1.3为什么会有进程图像?

  • 操作系统要管理CPU。
    CPU是什么呢?取值执行的计算机硬件,自动化部件。
    管理CPU需要先让CPU工作起来。
    取指令执行是什么呢?取出指令,执行指令。所有让程序执行起来,CPU也就工作起来了。

  • 运行的程序,和静止的程序是有很大的变化。
    比如说当前数据变化,指令第多少,运行的地址到多少了。所以引出了“进程”,来管理保存当前的信息。而用PCB来存储信息。
    启动一个进程,让CPU跑进程。CPU就开始工作了。 但为了高效需要CPU在不同时刻,交替的跑多个进程。那么就是多进程图像。

  • 操作系统是如何支持多进程图像。
    用户使用计算机, 就是启动一堆进程。
    用户管理计算机,就是管理一堆进程。

  • 多进程图像,多进程如何组织?
    操作系统 感知进程 和 组织进程 全靠PCB
    操作系统只有组织好多进程,才能合理推进多个进程。

2、进程的组成

2.1-多进程图像,多进程如何组织?

操作系统 感知进程 和 组织进程 全靠PCB
操作系统只有组织好多进程,才能合理推进多个进程。
操作系统管理进程核心就是PCB
用PCB这个数据结构,构成一个队列(例如创建,就绪、运行、阻塞,终止)

1. 一个进程包括:

  1. 程序的代码;
  2. 程序处理的数据
  3. 程序计数器中的值,指示下一条将运行的指令
  4. 一组通用的寄存器当前值,堆栈;(一直会动态的变化)
  5. 一组系统资源(如打开的文件)
  6. 一组系统资源(如打开的文件、内存资源、文件系统、网络等)
    总之,进程包含了正在运行的一个程序的所有状态信息。

2. 进程和程序的联系 (对多对的映射关系)

  1. 程序是产生进程的基础;进程执行的所有功能,是在程序中有具体的描述。进程执行的功能受制于程序的代码。代码限制了进程能完成何种功能。
  2. 程序的每次运行构成不同的进程;程序只有一份,放在硬盘中,可以多次执行程序,由于数据可能不一样,那么执行的结果也是不一样的,但功能是一样的。所以说进程也不同。
  3. 进程是程序功能的体现;
  4. 通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包含多个程序。进程也可以包含多个程序,因为可能多个程序包含一起完成一个功能。

3. 进程和程序的区别

  1. 进程是动态的,程序是静态的:程序是有序代码的集合;
  2. 进程是程序的执行,进程有核心态、用户态的区分。
    核心态:核心态在操作系统中运行,程序员写的代码都是用户态,代码没有写操作系统的代码。那为什么进程还有核心态呢?。是因为 进程在运行时需要完成特定的功能,只有操作系统才能提供,比如说读文件(操作系统来完成),进程对操作系统发出请求,操作系统代表进程在内核中进行,这时进程处于核心态。所以说进程有核心态。
  3. 进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序是可长久保存。。:进程在程序中运行,有开始和结束,结束后进程也就结束了。程序是永久保存在硬盘中。
  4. 进程与程序组成不同;进程的组成包括程序、数据和管理系统建立的进程控制块(及进程状态信息)
    在这里插入图片描述

3、进程的特点

  1. 动态性:动态的创建进程、结束进程; 还可以进行进程切换
  2. 并发性:进程可以被独立调度并占用处理运行;并发并行。
    并发是指:一段时间内有多个进程在执行。
    并行是指:一个时刻有多个进程在执行。(一个CPU不可能出现并行)
  3. 独立性:不同进程的工作不相互影响。
    对独立性的解释:操作系统不停的在调度不同进程来进行,进程的执行时间,可能受到其他进程的影响。但是正确性不受到影响,操作系统保障,进程之间不会相互破坏。操作系统如何保障进程正确执行?通过页表可以使得不同的程序,访问不同的地址空间,所以说不可能越过不属于范围的地址空间。如果越过地址空间,那么就会缺页异常,从而操作系统判断是否可以访问,越过范围之内,肯定不可以进行访问。这个页表保护进程独立的机制。有了页表可以不同的进程在独立的空间中运行。从而进程不会相互影响。
  4. 制约性:因访问共享数据/资源或者进程间同步而产生制约。
    进程之间可能有交互、时序,不能完全独立。操作系统根据时序等特点,进行管理,从而有相互制约。
  • 图一:不同的进程有切换。体现动态性。
  • 图二:相互独立进行
  • 图三:操作系统进行了时间调度,开始执行A,在执行B…
    在这里插入图片描述

-

4、进程控制结构(PCB与进程是一对一的关系)

  • 进程控制结构:操作系统管理控制进程运行所用的信息集合。操作系统用PCB来秒描述进程的基本情况以及运行变化的过程,PCB是进程存在的唯一标识。
  • 一个进程的存在,必然有PCB。PCB是跟随进程,创建以及结束。
  • 进程的创建:为该进程生成一个PCB
  • 进程的终止:回收它的PCB
  • 进程的组织和管理:通过对PCB的组织管理来实现。

1.PCB含有以下三大类信息:

  1. 进程表示信息:
    如本进程的表示,本进程的产生者标识(父进程标识);用户标识
  2. 处理机状态信息保护区。
    保存进程的运行现场信息:cpu会使用寄存器很多事情,比如说加减乘除,将数据存在寄存器,进行保护状态,比如是否溢出。
    1.用户可见寄存器,用户程序可以使用的数据,地址等寄存器
    2.控制和状态寄存器,如程序技术器pc,程序状态字psw
    3.栈指针,过程调用/系统调用/中断处理和返回时需要用到它。
  3. 进程控制信息:
    1.调度和状态信息,用于操作系统调度进程并占用处理机使用
    2.进程间通信信息,为支持进程间的与通信相关的各种标识、信号、信件等,这些信息存在接收方的进程控制块中。
    3.存储管理信息,包含有指向本进程映像存储空间的数据结构
    4.进程所用资源,说明由进程打开、使用的系统资源,如打开的文件等。
    5.有关数据结构连接信息,进程可以连接到一个进程队列中,或连接到先关的其他进程的pcb

2.PCB的组织方式

通常使用链表:因为进程是一个动态过程,需要进行动态插入删除等…
链表比数组的插入删除效率略高。

在这里插入图片描述

二、进程状态(State)

1、进程的生命期周期

1. 进程创建
引起进程创建的三个主要事件:

  • 系统初始化时;—— 简称init进程,负责创建新进程。
  • 用户请求一个新进程;——新进程:用户发出请求,操作系统完成创建进程。
  • 正在运行的程序执行了创建进程的系统调用;——可能进程也需要进程,由操作系统创建。
    2. 进程运行
    内核选择一个就绪的进程,让它占用处理机并执行,那么为何选择?如何选择?
    ——有了新进程不一定运行,还需要让我们的操作系统选择一个可以执行的进程(称为就绪进程),再执行。
    3. 进程等待
    在执行过程中,肯能产生等待,是可能要完成一个事情,这个事情没法马上完成。
    一个进程需要执行下去,某种原因不得不等待,这时操作系统把进程从运行态转换为等待态,等待时不占CPU,其他就绪就可以占用CPU运行。进程只能自己阻塞自己,因为只有进程自身才能知道何时需要等待某种事件的发生。
  • 在以下情况,进程等待(阻塞):
  1. 请求并等待系统服务,无法马上完成;
  2. 启动某种操作,无法马上完成;
  3. 需要的数据没有到达;
    例如1:读文件,文件存在硬盘上,从硬盘读到内存中来,这个过程相对于CPU很慢。这时候让CPU等待的话,浪费CPU的资源。这时候就让进程进行等待,先让其他就绪的进程运行。自身等待操作系统读到内存中。
    例如2:与其他进程协调完成某种工作,其他没有进行,该进程也需要等待。
    例如3:等待数据到达。

4. 进程唤醒
既然有“等待”,就有唤醒。唤醒就是等待情况的满足。
进程只能被别的进程或操作系统唤醒。

  • 唤醒进程的原因:
  1. 被阻塞进程需要的资源可以被满足;
  2. 被阻塞进程等待的事件到达;
  3. 将该进程的PCB插入到就绪队列;
    唤醒后,就会将“等待态”变为就绪态,也就说被操作系统的调用,CPU的执行。
    5. 进程结束
    进程从 执行、等待、唤醒可能多次重复,最后会结束
  4. 正常退出(自愿的)——完成了所有功能
  5. 错误退出(自愿的)——可能没有完成功能,出现错误
  6. 致命错误(强制性的)——操作系统强制退出。比如说访问其他地址空间,操作系统是不允许的。
  7. 被其他进程所杀(强制性的)——比如说管理进程,进程占用太多了,破坏其他进程的安全性。

2、进程状态变化模型

2.1例子:

创建:去学校读书
就绪:拿好饭卡去打饭,在排队。
就绪到运行,排队到我了,我正在打饭。
运行:正在打饭
运行到就绪,打完饭了,发现吃不饱,在从新排队再打一次饭
运行到阻塞,打完饭了,发现吃不饱,发现饭卡丢了,去补办饭卡路上。
阻塞到就绪,补办好饭卡了,去排队买饭。
运行到终止,我打完饭了。结束。

1、进程的三种基本状态:
进程在生命结束前处于且仅处于三种基本状态之一,不同系统设置的进程状态数目不同。

  • **运行状态(Running):**当一个进程正在处理机上运行时。
  • **就绪状态(Ready):**一个进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。
  • 等待状态(又称阻塞状态Blocked): 一个进程正在等待某一事件,而暂时停止运行时。如等待某自愿,等待i/o完成
    2、进程其它的基本状态:
    创建状态(New):一个进程正在被创建,还没有被转到就绪状态之前的状态。
    结束状态(Exit):一个进程正在从系统中消失时的状态,这是因为进程结束或由于其他原因所导致的。
    在这里插入图片描述
    3、可能的状态变化如下:
  1. NULL —>New :一个新进程被产生出来执行一个程序。——一开始没进城,由操作系统创建一个PCB,对PCB进行初始化工作,从空到New 状态,产生PCB了。
  2. New —>Ready:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态。是否会持续很久??——从new到ready,是说数据结构初始化完毕了,进程就可以执行了。并不会很久,很快。因为只是完成一个pcb的初始化过程。
  3. ready–>running:处于就绪状态的进程被进程调度程序选中后,就分配到处理机上来运行。 进程调度算法来选择进程去运行
  4. runnning–>Exit:当进程表示它已经完成或者因出错,当前运行进程会由操作系统作结束处理。
  5. runing–>ready:处于运行状态的进程在其他运行过程中,由于分配给它的处理机时间片用完而让出处理机。谁完成?
  6. Runing–>Blocked:运行到阻塞状态。 当进程请求某样东西且必须等待时。例如,读写文件。
  7. Bocked->Ready:当进程要等待某事件到来时,由操作系统来完成,它从阻塞状态变到就绪状态。

3、 进程挂起模型

进程没有占用内存空间,就需要进行挂起。
在这里插入图片描述

  • 阻塞挂起状态(Blocked-suspend):进程在外存并等待某事件的出现。
    进程本身处于阻塞状态,然后在挂起。进程在磁盘上,等待某个事件出现,1已经落在磁盘上了,2等待某个事件出现。
  • 就绪挂起状态(Ready-suspend):进程在外存,但只要进入内存,即可运行;
    进程本身就是就绪态,被挂起。
    在这里插入图片描述
    在这里插入图片描述
  • OS怎么通过PCB和定义的进程状态来管理PCB,帮助完成进程的调度过程?
    在这里插入图片描述
    在这里插入图片描述

三、调度切换

1、调度CPU去执行下一个进程

调度:调度选择下一个进程,得到下一个进程的PCB,将CPU的寄存器和PCB中的信息进行交换。
调度是找到了下一个进程,现在就要进行切换到下一个进程。如何切换呢??
在这里插入图片描述

2、多进程需要交替向前进行,如何交替(切换)?

  • 如何切换到下一个进程:
    例子:正在看书,来了一个客人,将现在关于书的内容记下来(物理CPU中的寄存器内容保存到pcb结构体中),是为了回来继续往下看书。
    现在要切换到接待客人(将客人信息从PCB结构体中,放到CPU中),是为了继续执行客人这个进程。
    在这里插入图片描述

四、多进程内存中如何相互影响:

1、回顾:操作系统要支持多进程图像,那么需要做:

  1. 操作系统如何组织多个进程? 根据PCB状态,形成不同的队列放到不同的位置(启动、就绪、运行、阻塞、结束…)
  2. 操作系统如何完成操作系统的切换? 核心就是调度选择下一个进程,得到下一个进程的PCB,根据CPU的寄存器和存放在进程的PCB中的信息进行交换。
  3. 多个进程放在内存中,会相互影响。只有放在内存中,CPU才会进行取指执行。

为什么多个进程都需要放在内存中。因为只有放在内存中CPU才会取指令,执行。所以多个进程需要同时存在于内存中。

  • 就会出现问题:
    例如进程1访问100,而100很有可能是进程2的内存地址。而进程1和2都是在内存中,这种访问有可能出现严重问题。

2、进程中的物理内存地址

  • 如何解决呢??内存管理方面内容。
    通过映射表来完成,映射表的分离。
    因为多进程都在内存中,就必须要分离。
    进程是通过映射表来访问地址。
    进程1的100并不是真正内存地址,而是每个进程通过自己的映射表,100对应的实际地址是另外一个数。
    进程2的100,通过映射表,就是另外一个地址了。而真正进程中的物理内存是物理分离开的。
    所以说,只有多个进程对应的物理内存地址分离,不相互打扰。多进程不相互影响,所以会很好的在内存中共存
    在这里插入图片描述

3、有时多进程需要合作。

多个进程在系统中同时在交替执行,有时会出现合作现象。
例如1:打印进程,例如进程1需要打印,进程2需要打印,首先进行打印队列,而进程就需要从队列中取出来。这就是合作了。
例如出现:进程1和2 都往打印队列里面存放,可能都往一个队列号存放(可能进程1还没有存放完,如果切换到进程2 也进行了同一队列序号进行存放)。那么打印出来不就乱套了嘛。
而进行顺序执行,就不会出现问题了,进程1执行完了以后,进程2 在进行执行。
在这里插入图片描述
例子2:
两个C语言代码:c++ 和c–,而实际是需要汇编代码,也就是下面的可能执行序列:
c=5, c++ c-- 最后的结果一个还是5,但是如果在执行代码时,产生了进程切换,那么就产生了不一样的结果。导致错误现象。
在这里插入图片描述
适当的解决方法是:上锁
合理的推进顺序,并不是随机的进程的切换。
在这里插入图片描述

五、线程(THREAD)

1.1用户级线程

为什么将进程的切换,要学线程呢?
线程和用户级线程,和切换什么关系呢?
一个执行序列,切换到另一个执行序列。我们不光切换进程,还要切换进程的相关信息。
多个执行序列交替执行。
进程进行切换时,切换进程和内存相关信息,由此要引出线程,只需要切换程序…不需要切换内存。
在这里插入图片描述
进程可以产生多个指令序列也就是多个函数,就是线程。
线程:保留了并发的优点(多段程序交替执行),避免了进程切换的大代价。
减小代价, 是因为不需要切换映射表,(映射表根内存有关系),是需要切一段程序切换到另一段程序。
在这里插入图片描述

1、为什么使用线程

线程的切换比进程的少了一步:只是切换指令序列,而不是涉及资源的切换。
例子:

  • 单进程方式:这种方式 能否连贯进行播放声音,,如果CPU不够强,可能是断断续续。因为在read时,可能进程处于等待时刻,是因为要读文件,而文件放在硬盘中,读出来的速度远远慢于内存和CPU的时间,等很长时间才可以进行减压在播放。也可能是read处于阻塞状态。各个程序不能并行进行,所以效率可能比较差。
    在这里插入图片描述
    在这里插入图片描述
  • 那我们应该用多进程方式来实现,可以将三个部分,进行拆开。三者处于有序的集合,实现高效音频播放。看起来是可行的,问题是:进程之间如何通行?创建和切换,需要对进程信息的保存与恢复。

在这里插入图片描述
怎么解决这些问题?
需要提出一种新的实体,满足以下特征:
1.实体之间可以并发地执行;
2.实体之间共享相同的地址空间、相同的资源;
那么这个实体就是线程(Thread)

2、什么是线程

  • 线程就是进程当中的一条执行流程。
  • 从两个角度来重新理解进程:
  1. 从资源组合的角度:进程把一组相关的资源组合起来,构成了一个资源平台(环境),包括地址空间(代码、数据段)、打开的文件等各种资源; ——进程就是来管理资源的,将单个进程的执行功能、状态,交给线程来管理。
  2. 从运行的角度:代码在这个资源平台上的一条执行流程(线程)。——认为是线程,线程是进程的组成部分。进程除了完成管理资源之外,还有线程来完成执行过程。
  • 进程有两部分组成:资源管理、一部分是线程。
  • 线程=进程 减去 共享资源
  • 线程是共享进程提供的资源平台。
    在这里插入图片描述
  • 线程= 进程 减去 共享资源
  • 线程的切换比进程的少了一步:只是切换指令序列,而不是涉及资源的切换。
  • 线程的优点:
  1. 一个进程中可以同时存在多个线程;
  2. 各个线程之间可以并发地执行;
  3. 各个线程之间可以共享地址空间和文件等资源
  • 线程的缺点:
  1. 一个线程崩溃,会导致其所属进程的所有线程崩溃
  • 既然有优点和缺点,那么就是实用性:
    线程适合:在高性能计算,很强调性能,所有的执行代码,相对统一。比如:天气预报…
    进程适合:打开浏览器,如果用线程的话,打开一个有病毒的网页,那么整个浏览器都会出现崩溃。所以说现在浏览器会使用进程。

  • 不同的操作系统对线程的支持,也不一样。
    MS-dos是单线程单进程
    Unix :单线程多进程
    win NT:多进程多线程

1.线程和进程的比较:

线程比进程的空间、时间效率要高。

  1. 进程是资源分配单位(资源:内存、文件、网络等)。线程是CPU调度单位(CPU是一种特殊的资源,对控制流所需要的相关信息)
  2. 进程拥有一个弯针的资源平台。 线程是只独享必不可少的资源,如寄存器和栈。
  3. 线程同样具有就绪、阻塞和执行三种基本状态,同样具有状态之间的转换关系。
  4. 线程能减少并发执行的实际和空间开销:
    1.线程的创建时间比进程短。——因为进程在创建时,需要创建其他管理信息(内存管理),而线程直接用进程创建好的资源。
    2.线程的终止时间比进程短。——同理,线程不考虑释放问题。
    3.同一进程内的线程切换时间比进程短。——切换进程需要切换页表,线程不同。
    4.由于同一进程的个线程共享内存和文件资源,可直接进行不通内核的通信。——直接通过内存地址可以通信。

3、线程的实现

1.线程的三种实现方式:

  1. 用户线程:在用户空间实现;
    操作系统看不到的线程,由应用程序的库来管理线程。

  2. 内核线程:在内核中实现;
    有操作系统管理起来的线程。操作系统来管理线程。

  3. 轻量级进程:在内核中实现,支持用户线程。

2.用户线程与内核线程的对应关系

多对一:n个用户线程,对应一个内核线程
一对一:1个用户线程,对应一个内核线程
多对多:n个用户线程,对应n个内核线程

3.用户线程:

3.1用户线程的引入

一个进程包括多个线程(寄存器映像,就是函数),和代码、数据、资源。
线程的切换就是两个函数的单纯切换,跟其他无关。
在这里插入图片描述
要启动一个程序,多个指令序列交替执行。

  • 为什么是切换线程,而不是进程?

例如:打开一个网站,先出现的是文本,然后出现的是图片然后是视频,这就是因为线程的切换。
服务器从网上接受到的文本图片数据放到缓冲区中,显示文本或者图片从缓冲区里面读取数据放到现存中(放到网页上)。也就说他们都是访问同一个映射表,也就是同一个进程有多个线程(文本、图片、视频等等…) 各个线程本身就是要合作共享缓冲区。这就是线程。
线程的切换比进程的少了一步:只是切换指令序列,而不是涉及资源的切换。
在这里插入图片描述

  • 用户态的库,来完成线程控制的管理。线程控制块在库里面进行实现,对操作系统来说,看不到TCB,只能看到进程信息。而进程中的线程信息,是库实现的。所以说整个线程的调度和管理,操作系统不直接参与,是由线程库直接完成。
    在这里插入图片描述
  • 在用户空间实现的线程机制,它不依赖操作系统的内核,由一组用户级的线程库函数来完成线程的管理,包含进程的创建、终止、同步和调度等。
    在这里插入图片描述
  • 用户线程的缺点:
  1. 阻塞性的系统调用如何实现?如果一个线程发起系统调用而阻塞,则整个进程在等待;
    阻塞性系统调用:比如说 读文件可能很长,则堵塞。如果是用户线程发生了堵塞,那么整个进程都会等待。其他用户线程没有堵塞,也会发生等待现象。因为操作系统感受不到线程,因为操作系统只能看到进程,会将此进程堵塞,那么所有属于进程和线程的都会进行堵塞,从而停下来。
  2. 当一个线程开始运行后,除非它主动地交出CPU使用权,否则它所在的线程中其他的线程无法运行;
  3. 由于时间片分配给进程,故与其他进程相比,在多线程执行时,每个线程得到的时间片较少,执行会较慢。

3.2用户线程的切换

在这里插入图片描述

void 应用程序  
生成一个共享的缓冲区
创建多个线程,每个线程执行函数
下载数据包,放到缓冲区中
从缓冲区中取出内容,放到显示器中。
---
还需要在GetData中,增加一些:
当某一个线程下载了文本后,就调一个Yield函数,线程去显示文本(也就是说切换线程)。也就是线程的并发。
  • Yield Create
    Create:线程是同时出发。第一次切换时的样子,
    Yield:此函数是为了能完成切换,必须了解切换时是什么样子的。
    下面是:切换线程,具体例子,打开网页,为什么先显示文本,然后在显示图片…
    是因为 当下载了文本后,就调一个Yield函数显示文本(也就是说切换线程)
    在这里插入图片描述
    在这里插入图片描述

3.3 Yield:是如何切换的??

在这里插入图片描述
在这里插入图片描述
1.是多线程使用一个栈。由于线程切换,栈无法按规定出栈。所以弹栈错误
2. 栈切换后,PC也切换。由于线程切换,虽然能切换到自己的栈,但是不能顺利弹栈(不懂)。
3.栈切换后,PC不应切换。因为通过Yield调回去后,也就是执行完了Yield函数,该执行大括号return了,也就完成了切换。

-------现在假设多线程使用同一个栈——》会出现出栈错误问题--
A函数调到B函数,会将A函数的地址压栈,是为了执行完B后,还会调回来。也就是栈的先进后出。
B函数要执行Yield函数,要调到Yield函数。将返回地址压栈。
而Yield是为了切换到下一个指令序列,核心就是修改PC指针(此时就是线程的切换)。
调到下一个线程后,执行C函数,会调到下一个D函数,会将C函数压栈
D函数遇到Yield函数,将D函数压栈,
Yield函数是切换线程,切换指令序列。 现在要切换到B函数。然后执行B函数return出栈
  但是现在的栈顶并不是B函数,而是最后一个压栈的D函数。出现了问题......
出现了:两个线程公用了一个栈。

-------现在假设一个线程(一个执行序列)对应一个栈——》会出现
A函数执行,A入栈,调到B函数,执行Yield,Y入栈。AB函数压入的同一个栈
Yield调到下一个执行队列,C函数
C函数执行,调到D函数,C入栈。D函数执行,调到Yield函数。D入栈。 CD同一个栈
Yield调到下一个执行队列,调回去也就是B函数。 
void Yield(){
    
    
TCB2.esp=esp;
esp=TCB1.esp;
jmp=204;
概念:
切执行队列,也要把栈切回去,TCB存放栈指针。
将栈信息放到TCB中,是为了将来切回时,找到该队列的esp指针(其实指针就是一个地址),赋值给CPU。
-------------
先将现在CPU运行的栈信息,栈指针保存到TCB中。 为了以后调回来时,继续执行该线程。
再将现在要切过去的线程TCB的栈指针信息,赋值给CPU的esp寄存器。
现在栈切完了,进行线程的切换,也就是PC去切换,调到B函数。
B函数,就要return,进行弹栈。由于寄存器的esp已经切换过来了,可以在自己序列对应的栈中进行弹栈。
当时入栈是因为执行Yield函数,跳到Yield。 而出栈就应该在Yield返回时,出栈。
==但是执行Yield函数最后一条语句是JMP,是进行切换进程的语句,并没有出栈。==
解决方法什么???
那么我们可以将JMP语句去掉。因为通过Yield切出去,切回来正好是执行下一句话。而Yield下一句话就是在栈中。
回来执行就是弹栈,该弹栈就是Yield压的栈。
然后执行大括号,就是return弹栈就是返回到A中。
==所以在Yield只需要切换栈就行,不用使用jmp跳转了。不用PC跟着切换了(就是jmp跳转)因为PC已经压入了栈==
==用Yield返回的右括号就完成了切换。==

}

在这里插入图片描述
在这里插入图片描述


两个线程的样子:两个TCB、两个栈、切换的PC在栈中。现在Yield完成了切换后,

3.4进行Create:做出切换的样子。做出三样东西。

在这里插入图片描述

1.申请一块内存,作为TCB
2.申请一块内存,作为栈
3.在栈里面添加内容,程序的初始地址。也就是执行这个函数的起始地址。
4.栈和TCB进行关联。
当切换时,找到TCB找到ESP,将ESP的值取出来,赋值给真的ESP,在通过yield的右大括号弹栈。所以就可以去执行100了。
  • 所以通过yield和create就完成了:
    首先创建多个线程,将起始地址放在栈中,然后在将TCB进行关联。Create函数做出了可以执行的样子。多个程序出现在了内存当中。
    在程序执行的过程中,调用yield,首先是切换栈,在弹栈,通过yield的有大括号弹栈,实现了切换到另外一个线程…
    1.void应用程序,创建一堆线程。
    2.每个线程有自己的函数
    3.实现ThreadCreate, 在线程中调用yield,CPU在多个线程中进行切换。
    4.将以上所有的函数实现后,同一编译在一起,就是上述例子的浏览器。

在这里插入图片描述

3.5用户线程—Yield是用户程序

Yield执行不需要到内核中,该线程完全是在用户态切换, 所有操作系统感知不到用户态线程。
缺点:
例如:浏览器访问页面,如果出现等待网卡的时候,而网卡是硬件,需要通过操作系统。如果网卡阻塞,内核就要切换到别的进程,内核看不到用户态线程show()。浏览器还是show不出来。
也就是说:虽然通过用户级线程,启动了多个任务序列。如果一旦在内核中阻塞。用户线程的并发性没有任何效果。(不太懂)
在这里插入图片描述

  • 而核心级线程不一样了。核心级线程ThreadCreate是系统调用,在创建线程时,就会进入内核。所以TCB在内核中。如果getDate阻塞了,而缓冲区已经有内容了,那么先进去show线程就可以执行了。所以内核并发性更好一些。

在这里插入图片描述

4.: 内核线程:操作系统可以看到的线程

TCB是放在内核里面的。
比如windows就是内核线程。CPU调用单位是线程,不是进程。
所有的线程的创建终止管理是操作系统来完成。进程主要是对资源的管理。
一个进程PCB会管理到一系列的线程。
在这里插入图片描述
在这里插入图片描述

4.1内核级线程

实际切换进程是切换内核级线程,进程必须是在内核中,所以没有用户级进程。

  • 进程必须是内核,那为什么没有用户级进程?
    是因为进程要分配资源,要访问内存、文件等等。这些都是系统的硬件资源,必须是内核态进行访问这些资源。

  • 为什么要有核心级线程?

多核要想充分发挥作用,必须要支持核心级线程。多线程在内核里才能运用多核。

并发是:同时出发,交替执行。只有一套资源。
并行是:

  • 多核和多处理器的区别?
    多处理器是一个CPU根一个映射和cache
    而多核是多个CPU用一个映射和cache
    多个CPU用一个映射其实就是线程。多个线程可以使用多核。

在这里插入图片描述

4.2和用户级相比,核心级线程有什么不同?

用户级线程,每个线程都有自己的栈。一个用户栈关联一个TCB,TCB切换引起用户栈切换。
核心级线程,每个线程有一套栈,一个用户栈一个核心栈。因为,核心级线程意味着到内核中创建核心线程。所以:要有核心线程,必须既要在用户栈跑,又要到内核栈跑。 每个线程要有自己的核心栈。
核心栈是:一个TCB关联一套栈,再内核里面切换TCB,要切换两个栈(因为一个是内核栈栈,一个是用户栈要跟着切)

在这里插入图片描述

4.3用户栈和内核栈之间的关联。

什么时候进入内核呢?进入内核的唯一方法是中断。所以说中断时,进入中断。
用户态执行:是在用户栈来回切换。一旦有中断INT时,就要到内核栈,启用内核栈。每个线程对应一个栈。通过硬件找到线程对应的内核栈,然后进行压栈。压ss和sp正好是用户态执行的栈。并且压刚才的pc和cs指令(也就是用户态执行到什么地方了)。
一旦要INT指令是进来,就启用了内核栈,并且拉了一条线上去(压栈), 这就是一套栈。内核态通过指针,把用户态联系在一起。
IRET指令是返回,将五个寄存器弹栈,就可以退回用户栈,并且CS 和PC进行了切换。退回到用户态了。
在这里插入图片描述

4.4带着中断进入核心级线程,是如何切换?

用户栈:
方法A开始执行,调到B方法,将104压栈
方法B执行,调到read方法,将204压栈,

-
在read方法中,就要执行 int  0X80;那么int就开始工作了,
只要一工作硬件就准备好了,SS和SP就指向了用户态中的104204(ss=104,sp=204)
cs(cs是段基址,指向第一条指令。)和IP=304(IP就是指向下条运行的指令地址)也准备好了。

- 
中断一返回:cs和ip又会弹,调到304去执行。就完成了指令的跳转(调回了用户栈。)

在这里插入图片描述

就会执行1000,移动到内核,在内核中去执行了.....
启动磁盘读,自己就变成了阻塞状态,一旦阻塞就会引起调度,从而CPU就会让出来,去执行别的。
那么就会找到下一个线程,switch_to(cur,next); 从而切换下一个线程(当前线程的TCB,下一个线程的TCB)
- TCB里面有什么呢? 
找到TCB,根据TCB切换用户栈,然后根据栈里面的东西弹,就切换了PC指针。
将当前的esp保存到TCB当中。在下一个TCB当中会找到esp,然后把ESP放到真的物理寄存器。

- switch_to:仍然是通过TCB找到内核栈指针;然后通过ret切到某个内核程序;最后cs:ps切到用户程序。

在这里插入图片描述
总结:用户态到内核态如何切换?
总结1:
S是用户栈,先是在用户态运行,发生中断进入内核栈了,就用到了TCB。
如果需要切换,就是阻塞了。在内核中:找到S线程内核的TCB,切换到T线程的内核TCB。并且将内核栈切换
而T线程的内核栈关联着T线程的用户栈 。
再用IRET指令(中断返回),在将S线程的用户栈切换到T线程的用户栈。
在这里插入图片描述
总结2:
1.中断,进入了中断后,才会链拉好(内核态和用户态连在一起),从而为了切换做准备。进行中断处理
2.中断处理中,可能会阻塞,从而发生切换。
3.切换,首先要找到下一个执行的TCB。call switch_to :从而完成内核栈的切换。
4.内核栈切换
5.然后IRET中断返回。第五端切换,用户栈的切换。

  • 总结:其实就是两套栈的切换。
    在这里插入图片描述

4.5ThreedCreate 做成那个样子

1.首先申请一段内存,作为TCB。作为内核栈,
2.然后把内核栈进行初始化。
3…
4.TCB关联内核栈。
5.TCB入栈,进入就绪队列。
在这里插入图片描述

4.6用户级线程、核心级线程的对比

在这里插入图片描述

4.7 引入内核级线程的实现

  • 为什么要学会内核级代码的实现?
  1. 进程是由资源 加 执行序列 这两部分组成的。进程必须进入内核,所以进程里的执行序列就是内核级线程。
  2. 学会内核级代码的实现, 进程的代码实现就完成了一半,另一半就是资源管理,资源管理就是内存管理。那么进程就可以代码在操作系统中实现。
  3. 一旦完成进程在操作系统中实现,一个完整的操作系统就不远了。操作系统起码只有支持了进程,才能管理好CPU。
  • 核心级线程的实现关键在于两套栈之间的切换。
    1.通过INT指令从用户栈到内核栈 (第一段是中断入口,建立用户栈和内核栈的关联。)
    2.从内核栈到TCB线程控制块
    3.两个线程的内核态中的TCB线程控制块完成切换
    4.内核态中的内核栈也进行切换
    5.通过IRET实现用户栈的切换。

线程1从用户栈进入了内核,就使用内核栈,而内核栈和用户栈是通过INT指令来拉在一起。在内核中执行的时候,可能会出现“阻塞”从而需要切换线程,那么:首先TCB线程控制块进行切换,并且内核栈也切换过来了。通过IRET指令,用户栈也就跟着切换过来了。
在这里插入图片描述

4.8 内核级线程的代码实现

在这里插入图片描述

第一段:
1.用户程序进入内核靠的是中断 。(fork(); 是创建进程系统调用,会引起中断…)
从main函数开始执行,遇A则跳A函数执行,则压入A返回的地址栈,其实就是B的初始地址。
2.进入A函数执行fork() ,就会变成INT指令—> 一个系统号 赋值给 eax,调用INT指令 0x80 执行完了以后,CPU马上会找到当前的内核栈,在内核栈里面压入了ss和sp,将ss和sp指向了用户栈。并且将当前的cs和IP压进来,进入内核态运行。
在这里插入图片描述
3.
第一种情况.: _sytem_call:将两个用户态的栈,进行压栈。
call sys_fork:进行内核态,内核中执行系统调用,中断产生的效果。 可能会引起切换。
将当前线程 赋值给 eax;
state(%eax)=state+eax=state+current。而current是PCB. 其实就是查看PCB中的state是否为零;非零代表阻塞。
如果不是为0,也就是阻塞了,那么就会调度。比如说进行读写操作,将状态为非0。也就是阻塞了,那么schedule,引起切换内核级线程中间三段。切换完了以后,第五段:中断出口。
第二种情况.:判断current的currenter是否为零,je是相等,如果等于0,也进行调度。也就是时间片是否还有,如果用光了也进行切换。都会进行调度去执行。执行完了以后,就会调回,第五段的:中断返回。
3.第二三四段,执行reschedule
在这里插入图片描述
第五段:中断出口
一堆pop出栈,入口是push入栈。
在进行IRET,切换下一个线程的用户栈。
在这里插入图片描述

第二三四段:(不懂含义)
1.schedule第一个引发的调度是找下一个进程。找到下一个进程(进程=内核的线程TCB)后,就进行切换switch_to(next),此时的Next是下一个进程的PCB。)
switch_to是如何完成切换的:将tss的全部内容,扣到cpu
在这里插入图片描述

4.9ThreadCreate

fork继续往下走,是_copy_process(父进程创建子进程,copy自己的进程) 在进行copy时,就要执行函数,则函数有一堆参数。参数都在内核栈中。因为父进程在进内核栈的时候,压了很多东西(东西:父进程在用户态中执行的样子),而这些东西需要传递给copy_process,所有才会更好的传递给子进程。
在这里插入图片描述
接下里是进行创建copy_process工作了:
在内核态中,获得一页内存。这一页内存用来做PCB;
设置tss,切换的核心就是tss扣在CPU上。完成切换需要tss,而创建一个进程(核心级线程),就是要创建出能切换的样子。也就是需要把tss初始化好。如何初始化好呢:
tss.esp0就是内核栈= Page_sige(就是4K)+P(p就是刚才申请的PCB页的初始地址) 。那么指针就会指向栈顶;下面是PCB,上面就是内核栈。父进程和子进程用的不是同一个栈。
tss.ss0=0x10; 就是内核数据段
tss.esp就是用户栈。 用的是父进程传下来的用户栈。父进程和子进程用的同一个用户栈。
在这里插入图片描述
接下里是初始化tss
将ip放进去; 也是从copy_proess 传进来。eip正好就是父进程在用户态时的样子
在这里插入图片描述

5.: 轻量级线程:

在Linux机制。
在这里插入图片描述

4、上下文切换 多线程编程接口举例

  • 什么是上线文切换?
    当运行不同程序的时候,各个进程是共享CPU资源。
  • 切换的过程称为:进程的上下文切换。
  • 进程切换涉及到两个进程,当A进程执行一段时间后,操作系统说“现在应该执行B进程了”,进程B开始执行。这次进程A将各种寄存器信息保存在进程控制块中(PCB).将B进程的进程控制块中保存到的上下午信息,恢复到CPU中。这样来实现进程的切换。这些操作都是由硬件完成,基本上都是汇编代码进行写。
    在这里插入图片描述
    在这里插入图片描述

六、操作系统的那颗“树”

1、回顾前期内容:

1.如何管理CPU,管理CPU的直观方法
2.程序执行起来,CPU效率低下。解决方法:多个程序执行起来。
3.进程-》多进程图像-》交替执行-》用户级线程交替执行-》内核级线程如何实现交替执行。
CPU管理就是进程管理和内存管理合在一起就是操作系统的kernel。
而CPU管理和内存管理的核心是CPU管理,就是进程管理,进程核心是:多进程切换。

猜你喜欢

转载自blog.csdn.net/weixin_43989347/article/details/120217050
今日推荐