操作系统常用知识总结(线程+CPU)

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

线程

并发(Concurrent)

并发是一个CPU处理器同时处理多个线程任务。

宏观上是同时处理多个任务,微观上其实是CPU在多个线程之间快速的交替执行CPU把运行时间划分成若干个(微小)时间段,公平的分配给各个线程执行,在一个时间段的线程运行时,其他线程处于挂起状态,这种就称之为并发。

并行(Parallel)

并行是多个CPU处理器同时处理多个线程任务。

当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这就被称之为并行。

同步异步/阻塞非阻塞

当一个request发送出去以后,会得到一个response,这整个过程就是一个同步调用的过程。

  • 哪怕response为空,或者response的返回特别快,但是针对这一次请求而言就是一个同步的调用。

当一个request发送出去以后,没有得到想要的response,而是通过后面的callback、状态或者通知的方式获得结果。

异步请求分两步:

  • 调用方发送request没有返回对应的response(可能是一个空的response)
  • 服务提供方将response处理完成以后通过callback的方式通知调用方

阻塞和非阻塞:

  • 阻塞和非阻塞就看调用方在发送请求后是否block住了。

协程

协程是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。

这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

在这里插入图片描述

用户态和内核态

Kernel 运行在超级权限模式(Supervisor Mode)下,所以拥有很高的权限。

按照权限管理的原则,多数应用程序应该运行在最小权限下。

因此,很多操作系统,将内存分成了两个区域:

内核空间(Kernal Space),这个空间只有内核程序可以访问。

用户空间(User Space),这部分内存专门给应用程序使用。

用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态(User Mode) 执行。

内核空间中的代码可以访问所有内存,我们称这些程序在内核态(Kernal Mode) 执行。

什么情况会导致用户态到内核态切换:

系统调用:用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork()就是一个创建新进程的系统调用。

异常:当 CPU 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常。

中断:当 CPU 在执行用户态的进程时,外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。

  • 如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。

用户态和内核态切换的开销

保留用户态现场(上下文、寄存器、用户栈等)

复制用户态参数,用户栈切到内核栈,进入内核态

额外的检查(因为内核代码对用户不信任)

执行内核态代码

复制内核态代码执行结果,回到用户态

恢复用户态现场(上下文、寄存器、用户栈等)

异常和中断的区别

中断是由硬件设备产生的,而它们从物理上说就是电信号,之后它们通过中断控制器发送给CPU,接着CPU判断收到的中断来自于哪个硬件设备(这定义在内核中),最后,由CPU发送给内核,有内核处理中断。

异常是由CPU产生的,同时,它会发送给内核,要求内核处理这些异常。

系统调用过程:

如果用户态程序需要执行系统调用,就需要切换到内核态执行。

内核程序执行在内核态(Kernal Mode),用户程序执行在用户态(User Mode)。

当发生系统调用时,用户态的程序发起系统调用。

因为系统调用中牵扯特权指令,用户态程序权限不足,因此会中断执行,也就是 Trap(Trap 是一种中断)。

发生中断后,当前 CPU 执行的程序会中断,跳转到中断处理程序。

内核程序开始执行,也就是开始处理系统调用。

内核处理完成后,主动触发 Trap,这样会再次发生中断,切换回用户态工作。

线程调度算法

先到先服务(First Come First Service,FCFS)

就是先到的作业先被计算,后到的作业,排队进行。

最短作业优先

最短作业优先(Shortest Job First, SJF)调度算法通常会同时考虑到来顺序和作业预估时间的长短。

优先级队列(PriorityQueue)

优先级队列可以给队列中每个元素一个优先级,优先级越高的任务就会被先执行。

抢占(Preemption)

抢占就是把执行能力分时,分成时间片段。

让每个任务都执行一个时间片段。

如果在时间片段内,任务完成,那么就调度下一个任务,如果任务没有执行完成,则中断任务,让任务重新排队,调度下一个任务。

多级队列模型:多级队列,就是多个队列执行调度。

在这里插入图片描述

紧急任务仍然走高优队列,非抢占执行。

普通任务先放到优先级仅次于高优任务的队列中,并且只分配很小的时间片。

如果没有执行完成,说明任务不是很短,就将任务下调一层。

下面一层,最低优先级的队列中时间片很大,长任务就有更大的时间片可以用。

通过这种方式,短任务会在更高优先级的队列中执行完成,长任务优先级会下调,也就类似实现了最短作业优先的问题。

CPU

冯诺依曼模型中 CPU 负责控制和计算。

为了方便计算较大的数值,CPU 每次可以计算多个字节的数据。

如果 CPU 每次可以计算 4 个 byte,那么我们称作 32 位 CPU。

如果 CPU 每次可以计算 8 个 byte,那么我们称作 64 位 CPU。

这里的 32 和 64,称作 CPU 的位宽。

CPU指令集权限

Inter把 CPU指令集 操作的权限由高到低划为4级:

  • ring 0
  • ring 1
  • ring 2
  • ring 3

其中 ring 0 权限最高,可以使用所有 CPU 指令集,ring 3 权限最低,仅能使用常规 CPU 指令集不能使用操作硬件资源的 CPU 指令集,比如 IO 读写、网卡访问、申请内存都不行,Linux系统仅采用ring 0 和 ring 3 这2个权限。

ring 0被叫做内核态,完全在操作系统内核中运行

ring 3被叫做用户态,在应用程序中运行

寄存器

CPU 要进行计算,比如最简单的加和两个数字时,因为 CPU 离内存太远,所以需要一种离自己近的存储来存储将要被计算的数字。

这种存储就是寄存器。

寄存器就在 CPU 里,控制单元和逻辑运算单元非常近,因此速度很快。

寄存器中有一部分是可供用户编程用的,比如用来存加和指令的两个参数,是通用寄存器。

还有一部分寄存器有特殊的用途,叫作特殊寄存器。

比如程序指针,就是一个特殊寄存器。

它存储了 CPU 要执行的下一条指令所在的内存地址。

  • 注意,程序指针不是存储了下一条要执行的指令,此时指令还在内存中,程序指针只是存储了下一条指令的地址。

下一条要执行的指令,会从内存读入到另一个特殊的寄存器中,这个寄存器叫作指令寄存器。

指令被执行完成之前,指令都存储在这里。

寄存器的数量通常在几十到几百之间,每个寄存器可以用来存储一定字节(byte)的数据。

32 位 CPU 中大多数寄存器可以存储 4 个字节;

64 位 CPU 中大多数寄存器可以存储 8 个字节。

总线

CPU 和内存以及其他设备之间,也需要通信,因此我们用一种特殊的设备进行控制,就是总线。

总线分成 3 种:

地址总线:专门用来指定 CPU 将要操作的内存地址。

数据总线:用来读写内存中的数据。

  • 当 CPU 需要读写内存的时候,先要通过地址总线来指定内存地址,再通过数据总线来传输数据。

控制总线:用来发送和接收关键信号,比如中断信号,还有设备复位、就绪等信号,都是通过控制总线传输。

  • 同样的,CPU 需要对这些信号进行响应,这也需要控制总线。

存储器分级

当 CPU 需要内存中某个数据的时候,如果寄存器中有这个数据,我们可以直接使用;

如果寄存器中没有这个数据,我们就要先查询 L1 缓存;L1 中没有,再查询 L2 缓存;

L2 中没有再查询 L3 缓存;L3 中没有,再去内存中拿。

L1-Cache:

  • 在 CPU 中,相比寄存器,虽然它的位置距离 CPU 核心更远,但造价更低。
  • 通常 L1-Cache 大小在几十 Kb 到几百 Kb 不等,读写速度在 2~4 个 CPU 时钟周期。

L2-Cache:

  • 在 CPU 中,位置比 L1- 缓存距离 CPU 核心更远。
  • 它的大小比 L1-Cache 更大,具体大小要看 CPU 型号,有 2M 的,也有更小或者更大的,速度在 10~20 个 CPU 周期。

L3-Cache:

  • 在 CPU 中,位置比 L2- 缓存距离 CPU 核心更远。

  • 大小通常比 L2-Cache 更大,读写速度在 20~60 个 CPU 周期。

  • L3 缓存大小也是看型号的,比如 i9 CPU 有 512KB L1 Cache;有 2MB L2 Cache; 有16MB L3 Cache。

程序的执行过程

首先,CPU 读取 PC 指针指向的指令,将它导入指令寄存器。

具体来说,完成读取指令这件事情有 3 个步骤:

  • CPU 的控制单元操作地址总线指定需要访问的内存地址(简单理解,就是把 PC 指针中的值拷贝到地址总线中)

  • CPU 通知内存设备准备数据(内存设备准备好了,就通过数据总线将数据传送给 CPU)。

  • CPU 收到内存传来的数据后,将这个数据存入指令寄存器。

  • 然后,CPU 分析指令寄存器中的指令,确定指令的类型和参数。

  • 如果是计算类型的指令,那么就交给逻辑运算单元计算;如果是存储类型的指令,那么由控制单元执行。

  • PC 指针自增,并准备获取下一条指令。

  • 比如在 32 位的机器上,指令是 32 位 4 个字节,需要 4 个内存地址存储,因此 PC 指针会自增 4。

CPU100%

top –c,显示进程运行信息列表,找到最耗CPU的进程。

按数字1,显示多核CPU信息。

键入P,进程按照CPU使用率排序。

按M按照内存占用进行排序。

top -Hp 【PID】,显示一个进程的线程运行信息列表,找到最耗CPU的线程。

printf %x\n 【线程pid】,转换多个线程数字为十六进制。

jstack 【进程PID】| grep 【线程转换后十六进制】-A10 , 使用JStack获取进程PID堆栈,利用Grep定位线程ID,打印后续10行信息。

如果VM Thread os_prio=0 tid=0x00007f871806e000 nid=0xa runnable,第一个是线程名,如果是VM Thread就是虚拟机GC回收线程了。

jstack 【进程PID】> 【文件】,将JStack堆栈信息存储到文件。

CPU负载情况

top 
15:52:00 up 42:35, 1 user, load average: 0.15, 0.05, 0.01

5:52:00 指的是当前时间

up 42:35 指的是机器已经运行了多长时间

1 user 指的是当前机器有1个用户在使用

load average: 0.15, 0.05, 0.01 的是CPU在1分钟、5分钟、15分钟内的负载情况

假设是一个4核的CPU,此时如果你的CPU负载是0.15,这就说明,4核CPU中连一个核都没用满,4核CPU基本都很空闲。

如果CPU负载是1,那说明4核CPU中有一个核已经被使用的比较繁忙了,另外3个核还是比较空闲一些。

要是CPU负载是1.5,说明有一个核被使用繁忙,另外一个核也在使用,但是没那么繁忙,还有2个核可能还是空闲的。

如果你的CPU负载是4,那说明4核CPU都被跑满了,如果你的CPU负载是6,那说明4核CPU被繁忙的使用还不够处理当前的任务,很多进程可能一直在等待CPU去执行自己的任务。

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值