操作系统之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负载情况

1
2
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去执行自己的任务。