date: 2021-03-07title: linux基础巩固 #标题
tags: linux #标签
categories: linux大杂烩 # 分类
CPU性能篇
什么是CPU平均负载?
相信大多数人和我之前一样,就是通过uptime
值来判断当前CPU的一个负载状态,那么那三个值,就真的是CPU的一个负载状态么?对,但不全对。首先,那三个值表示的是当前系统最近1、5、15分钟的平均活跃进程数,它和CPU使用率并没有直接关系。
所谓的平均负载表示系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,那么可运行状态进程,就是我们通过top
指令查看到状态为R
(Running 或 Runnable)的进程,所谓可运行状态进程,就是正在使用CPU或者等待CPU的进程,不可中断进程则表示正在处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的就是等待硬件设备的I/O响应,也就是我们在top
命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。
比如,当一个进程向磁盘写数据时,为了保证数据的一致性,在得到磁盘回复前是不能被其他进程打断或中断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就很容易出现磁盘数据与进程数据不一致的问题。
所以,不可中断状态,实际上也是系统对进程和硬件设备的一种保护机制。
因此,可以简单理解为,平均负载其实就是平均活跃进程数,那么既然是平均活跃进程数,那么这个数值为多少比较合理呢?最理想的就是每个CPU上刚好运行着一个进程,这样每个CPU都得到了充分的利用,比如当平均负载为2时,意味着什么呢?
- 在只有 2 个 CPU 的系统上,意味着所有的 CPU 都刚好被完全占用。
- 在 4 个 CPU 的系统上,意味着 CPU 有 50% 的空闲。
- 而在只有 1 个 CPU 的系统中,则意味着有一半的进程竞争不到 CPU。
平均负载与CPU使用率
之前呢,我经常容易把平均负载和CPU使用率搞混,所以这里也来聊聊这两个词的含义。
虽然平均负载代表的是活跃进程数,但平均负载高了,并不意味着CPU使用率也高。
我们还是要回到平均负载的含义上来,平均负载是指在单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包含了正在使用CPU的进程,还包括了正在等待CPU和等待I/O的进程。
而CPU使用率,是单位时间内CPU繁忙情况的统计,跟平均负载并不一定完全对应,比如:
- CPU密集型进程,使用大量CPU会导致平均负载升高,此时这两者是一致的。
- I/O密集型进程,等待I/O也会使平均负载升高,但CPU使用率不一定很高。
- 大量等待CPU的进程调度也会导致平均负载升高,此时的CPU使用率也会比较高。
总结:
平均负载提供了一个快速查看系统整体性能的手段,反映了整体的负载情况。但只看平均负载本省,并不能直接发现到底是哪里出了瓶颈。所以在理解平均负载时,也要注意:
- 平均负载高可能是CPU密集型进程导致的。
- 平均负载高并不一定代表CPU使用率高,还有可能是I/O更繁忙了。
- 当发现负载高的时候,可以使用top、mpstat、pidstat等工具,辅助分析负载的来源。
CPU上下文和CPU上下文切换
我们都知道linux是一个多任务操作系统,它支持远大于CPU数量的任务同时运行。当然,这些任务并不是真的在同时运行,而是因为系统在很短额时间内,将CPU轮流分配给它们,造成多任务同时运行的错觉。
而在每个任务运行前,CPU都需要任务从哪里加载、又从哪里开始运行,也就是说,需要系统预先帮它设置好CPU寄存器和程序计数器。
CPU寄存器,是CPU内置的容量小、但速度极快的内存。而程序计数器,则是用来存储CPU正在执行的指令位置、或者即将执行的下一条指令位置。它们都是CPU在运行任何任务前,必须的依赖环境,因此也被叫做CPU上下文。
知道了什么是CPU上下文,那么也就很容易理解CPU上下文切换了,CPU上下文切换,就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,然后再跳转到程序计数器所指的新位置,运行新任务。
而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行的。
根据任务的不同,CPU的上下文切换可以分为几个不同的场景,也就是进程上下文切换、线程上下文切换以及中断上下文切换。
进程上下文切换
linux按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中,CPU特权等级的ring 0 和ring 3。
- 内核空间(Ring 0)具有最高权限,可以直接访问所有资源;
- 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
换个角度看,也就是说,进程既可以在用户空间运行,又可以在内核空间中运行,进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。
从用户态到内核态的转变,需要通过系统调用来完成,比如当我们看文件内容时,就需要多次系统调用来完成:首先调用open()打开文件,然后调用read()读取文件内容,并调用write()将内容写到标准输出,最后再调用close()关闭文件。
那么,系统调用过程中有没有发生CPU上下文切换呢?答案是肯定有的。
CPU寄存器里原来用户态的指令位置,需要先保存起来,接着,为了执行内核态代码,CPU寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。
而系统调用结束后,CPU寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实发生了两次CPU上下文切换。
不过,需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟我们通常所说的进程上下文切换是不一样的:
- 进程上下文切换,是指从一个进程切换到另一个进程运行。
- 而系统调用过程中一直是同一个进程在运行。
所以,系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。
总结:
- CPU上下文切换,是保证linux系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
- 但过多的上下文切换,会把CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降。