cpu 使用率是怎么回事呢?
前言
简单介绍一下cpu的使用率是怎么来的。
正文
cpu 使用大概有这几方面:
public class CpuTimeBreakdown
{
// 1. 用户时间 (User Time)
// - 应用程序在用户态执行的时间
// - 包括:业务逻辑、算法计算、数据处理
public long UserTime { get; set; }
// 2. 系统时间 (System Time)
// - 应用程序在内核态执行的时间
// - 包括:系统调用、内存管理、进程调度
public long SystemTime { get; set; }
// 3. 空闲时间 (Idle Time)
// - CPU真正空闲的时间
// - 包括:HALT指令、节能模式、等待中断
public long IdleTime { get; set; }
// 4. I/O等待时间 (I/O Wait Time)
// - CPU等待I/O设备的时间
// - 包括:磁盘I/O、网络I/O、设备I/O
public long IoWaitTime { get; set; }
// 5. 中断时间 (IRQ Time)
// - 处理硬件中断的时间
// - 包括:网络中断、磁盘中断、定时器中断
public long IrqTime { get; set; }
// 6. 软中断时间 (Soft IRQ Time)
// - 处理软中断的时间
// - 包括:网络包处理、定时器处理、调度器中断
public long SoftIrqTime { get; set; }
// 7. 虚拟化时间 (Steal Time)
// - 被虚拟机监控器占用的时间
public long StealTime { get; set; }
}
然后计算公式如下:
public class CpuUsageCalculator
{
public static CpuUsageBreakdown CalculateCpuUsage(CpuTimeSnapshot current, CpuTimeSnapshot previous)
{
var totalDiff = current.TotalTime - previous.TotalTime;
return new CpuUsageBreakdown
{
// 总CPU使用率 = (总时间 - 空闲时间 - I/O等待时间) / 总时间
TotalUsage = (double)(totalDiff - current.IdleTime + previous.IdleTime -
current.IoWaitTime + previous.IoWaitTime) / totalDiff * 100,
// 用户态使用率
UserUsage = (double)(current.UserTime - previous.UserTime) / totalDiff * 100,
// 系统态使用率
SystemUsage = (double)(current.SystemTime - previous.SystemTime) / totalDiff * 100,
// 中断使用率
IrqUsage = (double)(current.IrqTime + current.SoftIrqTime -
previous.IrqTime - previous.SoftIrqTime) / totalDiff * 100,
// I/O等待率
IoWaitUsage = (double)(current.IoWaitTime - previous.IoWaitTime) / totalDiff * 100,
// 空闲率
IdleUsage = (double)(current.IdleTime - previous.IdleTime) / totalDiff * 100
};
}
}
这里就有人问了,cpu 为啥会有空闲时间呢? cpu 应该是一直会运行的,那么这个空闲时间是什么呢?
public class IdleTimeExplanation
{
public static void ExplainIdleTime()
{
Console.WriteLine("CPU空闲时间包括:");
Console.WriteLine("1. HALT指令执行时间 - CPU进入低功耗状态");
Console.WriteLine("2. 等待中断的时间 - 没有可执行的任务");
Console.WriteLine("3. 节能模式时间 - CPU降频或休眠");
Console.WriteLine("4. 调度器空闲时间 - 没有就绪进程");
Console.WriteLine("5. 内核空闲循环 - 执行idle进程");
}
}
在操作系统重,会执行idle 进程,不让cpu空闲下来,这个是为了让cpu不脱离操作系统的控制。
// Linux内核中的空闲进程
public class IdleProcess
{
public static void IdleLoop()
{
while (true)
{
// 1. 检查是否有就绪进程
if (HasReadyProcess())
{
ScheduleNextProcess();
break;
}
// 2. 执行HALT指令,进入低功耗状态
HaltCpu();
// 3. 等待中断唤醒
WaitForInterrupt();
// 4. 处理中断
HandleInterrupt();
}
}
private static void HaltCpu()
{
// 执行HLT指令,CPU进入低功耗状态
// 但CPU仍然可以响应中断
}
}
那么这里的软中断和硬中断是怎么占用cpu的时间的?
按照我们的思维上来说,处理软中断和硬中断呢? 理论上cpu只是会找到特定的程序的位置,但是处理算是系统处理,应该算在系统上吧。
那么我们看一下定义:系统时间是什么定义:
系统时间是应用程序在内核态执行的时间。
那么这里看到,其实内核调用呢?分为两个部分一个是系统时间,一个是中断时间。
public class InterruptVsSystemTime
{
public static void ExplainDifference()
{
Console.WriteLine("中断时间 vs 系统时间的区别:");
Console.WriteLine();
Console.WriteLine("系统时间 (System Time):");
Console.WriteLine("- 应用程序主动发起的系统调用");
Console.WriteLine("- 进程在用户态主动调用内核函数");
Console.WriteLine("- 例如:read(), write(), malloc()");
Console.WriteLine("- 进程知道何时开始和结束");
Console.WriteLine();
Console.WriteLine("中断时间 (IRQ Time):");
Console.WriteLine("- 硬件设备强制中断当前执行");
Console.WriteLine("- CPU被动响应中断信号");
Console.WriteLine("- 例如:网卡中断、磁盘中断");
Console.WriteLine("- 进程不知道何时发生中断");
Console.WriteLine("- 中断处理程序在内核态执行");
}
}
执行的上下文也不同 :
public class ExecutionContext
{
public static void DemonstrateContext()
{
Console.WriteLine("执行上下文对比:");
Console.WriteLine();
Console.WriteLine("系统调用上下文:");
Console.WriteLine("用户进程 -> 系统调用 -> 内核函数 -> 返回用户进程");
Console.WriteLine("主动发起,可预测,进程控制");
Console.WriteLine();
Console.WriteLine("中断处理上下文:");
Console.WriteLine("用户进程 -> 硬件中断 -> 中断处理程序 -> 返回用户进程");
Console.WriteLine("被动响应,不可预测,硬件控制");
}
}
那么同样有人会疑问了,硬中断是硬件中断的,软中断是软件中断的? 真的是这个概念吗? 如果是这样,系统调用不也是软中断嘛?
那么来看一下系统调用和软中断的区别:
1.1 基本概念对比
public class SystemCallVsSoftInterrupt
{
public static void ExplainDifference()
{
Console.WriteLine("系统调用 vs 软中断:");
Console.WriteLine();
Console.WriteLine("系统调用 (System Call):");
Console.WriteLine("- 用户程序主动发起的请求");
Console.WriteLine("- 通过特定的指令序列触发");
Console.WriteLine("- 进程知道何时发生");
Console.WriteLine("- 同步执行");
Console.WriteLine("- 例如:read(), write(), malloc()");
Console.WriteLine();
Console.WriteLine("软中断 (Soft IRQ):");
Console.WriteLine("- 内核或硬件触发的异步事件");
Console.WriteLine("- 通过设置标志位触发");
Console.WriteLine("- 进程不知道何时发生");
Console.WriteLine("- 异步执行");
Console.WriteLine("- 例如:网络包处理、定时器处理");
}
}
1.2 触发机制的不同
; 系统调用的触发机制
system_call:
; 1. 用户程序执行syscall指令
syscall
; 2. CPU自动切换到内核态
; 3. 跳转到系统调用处理程序
; 4. 执行系统调用逻辑
; 5. 返回用户态
; 软中断的触发机制
soft_irq:
; 1. 内核或硬件设置软中断标志
set_bit(SOFTIRQ_BIT, &softirq_pending)
; 2. 在适当时机检查软中断标志
; 3. 执行软中断处理程序
; 4. 清除软中断标志
软中断流程:
public class SoftInterruptFlow
{
public static void DemonstrateSoftInterrupt()
{
Console.WriteLine("软中断执行流程:");
Console.WriteLine();
Console.WriteLine("触发阶段:");
Console.WriteLine("1. 硬件中断处理程序设置软中断标志");
Console.WriteLine("2. 硬件中断处理完成");
Console.WriteLine("3. 返回被中断的进程");
Console.WriteLine();
Console.WriteLine("执行阶段:");
Console.WriteLine("4. 内核在适当时机检查软中断标志");
Console.WriteLine("5. 发现有待处理的软中断");
Console.WriteLine("6. 执行软中断处理程序");
Console.WriteLine("7. 清除软中断标志");
Console.WriteLine();
Console.WriteLine("特点:异步、不可预测、内核控制");
}
}
软中断的实现:
// Linux内核软中断实现
void do_softirq(void)
{
unsigned long pending;
// 获取待处理的软中断
pending = local_softirq_pending();
if (pending) {
// 执行软中断处理程序
if (pending & (1 << NET_RX_SOFTIRQ))
net_rx_action();
if (pending & (1 << TIMER_SOFTIRQ))
run_timer_softirq();
if (pending & (1 << TASKLET_SOFTIRQ))
tasklet_action();
}
}
那么由此可见,并不是说系统调用是软中断,软中断应该是软件设置标志位为软中断,内核在指定的时机进行处理。
这里我们来复习一下,什么是软中断什么是硬中断:
让我详细解释硬中断和软中断的概念:
一、硬中断 (Hard IRQ)
public class HardInterrupt
{
public static void ExplainHardInterrupt()
{
Console.WriteLine("硬中断 (Hard IRQ):");
Console.WriteLine();
Console.WriteLine("定义:");
Console.WriteLine("- 由硬件设备直接发送到CPU的中断信号");
Console.WriteLine("- CPU必须立即响应的紧急事件");
Console.WriteLine("- 具有最高优先级");
Console.WriteLine("- 可以打断任何正在执行的代码");
Console.WriteLine();
Console.WriteLine("特点:");
Console.WriteLine("- 硬件触发");
Console.WriteLine("- 立即响应");
Console.WriteLine("- 不可屏蔽(某些情况下)");
Console.WriteLine("- 执行时间短");
}
}
1.2 硬中断的触发机制
; 硬中断的处理流程
hardware_interrupt:
; 1. 硬件设备发送中断信号
; 2. CPU立即停止当前执行
; 3. 保存当前CPU状态
push rax
push rbx
push rcx
; ... 保存所有寄存器
; 4. 跳转到中断处理程序
call interrupt_handler
; 5. 恢复CPU状态
pop rcx
pop rbx
pop rax
; 6. 返回原任务
iret
1.3 常见的硬中断
public class CommonHardInterrupts
{
public static void ListHardInterrupts()
{
Console.WriteLine("常见的硬中断:");
Console.WriteLine();
Console.WriteLine("1. 时钟中断 (Timer Interrupt):");
Console.WriteLine(" - 系统时钟定期发送");
Console.WriteLine(" - 用于时间片调度");
Console.WriteLine(" - 频率:通常100Hz或1000Hz");
Console.WriteLine();
Console.WriteLine("2. 网络中断 (Network Interrupt):");
Console.WriteLine(" - 网卡接收到数据包时发送");
Console.WriteLine(" - 通知CPU有新数据到达");
Console.WriteLine(" - 频率:取决于网络流量");
Console.WriteLine();
Console.WriteLine("3. 磁盘中断 (Disk Interrupt):");
Console.WriteLine(" - 磁盘I/O完成时发送");
Console.WriteLine(" - 通知CPUI/O操作完成");
Console.WriteLine(" - 频率:取决于磁盘活动");
Console.WriteLine();
Console.WriteLine("4. 键盘中断 (Keyboard Interrupt):");
Console.WriteLine(" - 用户按键时发送");
Console.WriteLine(" - 通知CPU有用户输入");
Console.WriteLine(" - 频率:取决于用户输入");
Console.WriteLine();
Console.WriteLine("5. 鼠标中断 (Mouse Interrupt):");
Console.WriteLine(" - 鼠标移动或点击时发送");
Console.WriteLine(" - 通知CPU有鼠标事件");
Console.WriteLine(" - 频率:取决于鼠标活动");
}
}
二、软中断 (Soft IRQ)
2.1 软中断的定义
public class SoftInterrupt
{
public static void ExplainSoftInterrupt()
{
Console.WriteLine("软中断 (Soft IRQ):");
Console.WriteLine();
Console.WriteLine("定义:");
Console.WriteLine("- 由软件设置的中断标志");
Console.WriteLine("- 在适当时机由内核处理");
Console.WriteLine("- 优先级低于硬中断");
Console.WriteLine("- 可以被打断,但不会被阻塞");
Console.WriteLine();
Console.WriteLine("特点:");
Console.WriteLine("- 软件触发");
Console.WriteLine("- 延迟执行");
Console.WriteLine("- 可以屏蔽");
Console.WriteLine("- 执行时间较长");
}
}
对比:
public class InterruptComparison
{
public static void CompareInterrupts()
{
Console.WriteLine("硬中断 vs 软中断对比:");
Console.WriteLine();
Console.WriteLine("触发方式:");
Console.WriteLine(" 硬中断:硬件设备直接发送信号");
Console.WriteLine(" 软中断:软件设置标志位");
Console.WriteLine();
Console.WriteLine("响应时间:");
Console.WriteLine(" 硬中断:立即响应");
Console.WriteLine(" 软中断:延迟执行");
Console.WriteLine();
Console.WriteLine("优先级:");
Console.WriteLine(" 硬中断:最高优先级");
Console.WriteLine(" 软中断:较低优先级");
Console.WriteLine();
Console.WriteLine("执行时间:");
Console.WriteLine(" 硬中断:很短(微秒级)");
Console.WriteLine(" 软中断:较长(毫秒级)");
Console.WriteLine();
Console.WriteLine("可屏蔽性:");
Console.WriteLine(" 硬中断:部分可屏蔽");
Console.WriteLine(" 软中断:完全可屏蔽");
}
}
执行上下文对比:
public class ExecutionContext
{
public static void CompareExecutionContext()
{
Console.WriteLine("执行上下文对比:");
Console.WriteLine();
Console.WriteLine("硬中断上下文:");
Console.WriteLine("- 在中断上下文中执行");
Console.WriteLine("- 不能睡眠");
Console.WriteLine("- 不能调用可能睡眠的函数");
Console.WriteLine("- 执行时间必须很短");
Console.WriteLine("- 通常只做简单处理");
Console.WriteLine();
Console.WriteLine("软中断上下文:");
Console.WriteLine("- 在软中断上下文中执行");
Console.WriteLine("- 不能睡眠");
Console.WriteLine("- 可以执行较复杂的处理");
Console.WriteLine("- 可以批量处理数据");
Console.WriteLine("- 可以调用内核函数");
}
}
软中断更像是一种异步处理,一般配合硬中断, 给一个网络的例子:
public class NetworkPacketProcessing
{
public static void DemonstrateNetworkProcessing()
{
Console.WriteLine("网络包处理流程:");
Console.WriteLine();
Console.WriteLine("1. 网卡接收到数据包");
Console.WriteLine("2. 网卡发送硬中断到CPU");
Console.WriteLine("3. CPU立即停止当前执行");
Console.WriteLine("4. 执行硬中断处理程序:");
Console.WriteLine(" - 快速读取数据包");
Console.WriteLine(" - 设置软中断标志");
Console.WriteLine(" - 返回原任务");
Console.WriteLine("5. 内核在适当时机检查软中断");
Console.WriteLine("6. 执行软中断处理程序:");
Console.WriteLine(" - 批量处理网络包");
Console.WriteLine(" - 协议栈处理");
Console.WriteLine(" - 路由查找");
Console.WriteLine(" - 转发或本地处理");
Console.WriteLine();
Console.WriteLine("硬中断:快速响应,简单处理");
Console.WriteLine("软中断:详细处理,批量操作");
}
}
伪代码:
硬中断:
// 网络中断处理示例
public class NetworkInterruptHandler
{
public static void HandleNetworkInterrupt()
{
// 1. 保存CPU状态(由硬件自动完成)
// 2. 读取网络包数据
var packetData = ReadNetworkPacket();
// 3. 解析网络包
var packet = ParsePacket(packetData);
// 4. 更新网络统计
UpdateNetworkStats(packet);
// 5. 将包放入处理队列
EnqueuePacket(packet);
// 6. 恢复CPU状态(由硬件自动完成)
}
private static byte[] ReadNetworkPacket()
{
// 从网卡缓冲区读取数据
// 这个过程需要CPU时间
return new byte[1500]; // 典型以太网包大小
}
private static NetworkPacket ParsePacket(byte[] data)
{
// 解析IP头、TCP头等
// 这个过程需要CPU时间
return new NetworkPacket();
}
}
软中断:
// 网络软中断处理示例
public class NetworkSoftIrqHandler
{
public static void HandleNetworkSoftIrq()
{
// 1. 批量处理网络包
var packets = GetPendingPackets();
foreach (var packet in packets)
{
// 2. 协议栈处理
ProcessProtocolStack(packet);
// 3. 路由查找
var route = FindRoute(packet);
// 4. 防火墙检查
if (CheckFirewall(packet))
{
// 5. 转发或本地处理
ForwardOrDeliver(packet, route);
}
}
// 6. 更新统计信息
UpdateNetworkStats();
}
private static void ProcessProtocolStack(NetworkPacket packet)
{
// 处理IP层
ProcessIPLayer(packet);
// 处理传输层(TCP/UDP)
ProcessTransportLayer(packet);
// 处理应用层
ProcessApplicationLayer(packet);
}
}
网络包的流量大但是情况比较简单,也就是一个口子读入,一个口子输出。
那么磁盘有点不一样:
public class DiskIoProcessing
{
public static void DemonstrateDiskProcessing()
{
Console.WriteLine("磁盘I/O处理流程:");
Console.WriteLine();
Console.WriteLine("1. 应用程序发起磁盘I/O请求");
Console.WriteLine("2. 内核将请求发送给磁盘控制器");
Console.WriteLine("3. 磁盘控制器执行I/O操作");
Console.WriteLine("4. I/O完成后,磁盘控制器发送硬中断");
Console.WriteLine("5. CPU执行硬中断处理程序:");
Console.WriteLine(" - 检查I/O状态");
Console.WriteLine(" - 设置软中断标志");
Console.WriteLine(" - 返回原任务");
Console.WriteLine("6. 内核执行软中断处理程序:");
Console.WriteLine(" - 更新I/O统计");
Console.WriteLine(" - 唤醒等待的进程");
Console.WriteLine(" - 处理下一个I/O请求");
Console.WriteLine();
Console.WriteLine("硬中断:快速确认I/O完成");
Console.WriteLine("软中断:详细处理I/O结果");
}
}
因为磁盘读取的时候,可能应用程序发起了对十几个文件的请求。
那么这个在磁盘可以并发处理的情况下,磁盘的软中断只有一位,如何区分是哪个完成了呢?
这个就是对并发软中断的处理:
磁盘机制:
public class DiskQueueMechanism
{
public static void ExplainDiskQueue()
{
Console.WriteLine("磁盘队列机制:");
Console.WriteLine();
Console.WriteLine("1. 磁盘控制器维护命令队列:");
Console.WriteLine(" - 可以同时接收多个I/O命令");
Console.WriteLine(" - 队列长度通常为32-256个命令");
Console.WriteLine(" - 支持命令重排序和优化");
Console.WriteLine();
Console.WriteLine("2. CPU可以连续发送多个命令:");
Console.WriteLine(" - 不需要等待前一个命令完成");
Console.WriteLine(" - 磁盘控制器会排队处理");
Console.WriteLine(" - 支持异步I/O操作");
Console.WriteLine();
Console.WriteLine("3. 磁盘控制器独立处理:");
Console.WriteLine(" - 磁盘控制器有自己的处理器");
Console.WriteLine(" - 可以并行处理多个命令");
Console.WriteLine(" - 支持命令重排序优化");
}
}
异步I/O机制
public class AsyncIoExample
{
public static async Task DemonstrateAsyncIo()
{
Console.WriteLine("异步I/O示例:");
Console.WriteLine();
// 同时发起多个磁盘I/O操作
var tasks = new List<Task<byte[]>>();
for (int i = 0; i < 10; i++)
{
tasks.Add(File.ReadAllBytesAsync($"file{i}.dat"));
}
// 等待所有I/O操作完成
var results = await Task.WhenAll(tasks);
Console.WriteLine($"同时发起了 {tasks.Count} 个磁盘I/O操作");
Console.WriteLine("CPU不需要等待每个操作完成");
}
}
内核I/O调度器
public class IoScheduler
{
public static void ExplainIoScheduler()
{
Console.WriteLine("内核I/O调度器:");
Console.WriteLine();
Console.WriteLine("1. 请求队列:");
Console.WriteLine(" - 内核维护I/O请求队列");
Console.WriteLine(" - 可以同时接收多个请求");
Console.WriteLine(" - 支持请求重排序");
Console.WriteLine();
Console.WriteLine("2. 调度算法:");
Console.WriteLine(" - CFQ (Completely Fair Queuing)");
Console.WriteLine(" - NOOP (No Operation)");
Console.WriteLine(" - Deadline");
Console.WriteLine(" - BFQ (Budget Fair Queuing)");
Console.WriteLine();
Console.WriteLine("3. 并发处理:");
Console.WriteLine(" - 多个进程可以同时发起I/O");
Console.WriteLine(" - 内核会合并和优化请求");
Console.WriteLine(" - 支持异步I/O和I/O多路复用");
}
}
中间通过队列来完成:
// 磁盘中断处理程序
irqreturn_t disk_interrupt_handler(int irq, void *dev_id)
{
struct request_queue *q = (struct request_queue *)dev_id;
struct request *req;
// 1. 检查磁盘状态
if (disk_operation_completed()) {
// 2. 获取完成的请求
req = get_completed_request(q);
// 3. 标记请求为完成状态
req->flags |= REQ_DONE;
// 4. 设置软中断标志
raise_softirq(BLOCK_SOFTIRQ);
}
return IRQ_HANDLED;
}
// 块设备软中断处理程序
static void blk_done_softirq(struct softirq_action *h)
{
struct request_queue *q;
struct request *req;
// 遍历所有请求队列
list_for_each_entry(q, &all_request_queues, list) {
// 检查队列中的每个请求
list_for_each_entry(req, &q->queue_head, queuelist) {
if (req->flags & REQ_DONE) {
// 处理完成的请求
complete_request(req);
}
}
}
}
大概就是这样一个过程:
public class RequestIdentification
{
public static void ExplainRequestIdentification()
{
Console.WriteLine("请求标识和跟踪机制:");
Console.WriteLine();
Console.WriteLine("1. 请求标识:");
Console.WriteLine(" - 每个I/O请求都有唯一的ID");
Console.WriteLine(" - 包含设备ID、扇区号、长度等信息");
Console.WriteLine(" - 用于跟踪请求的状态");
Console.WriteLine();
Console.WriteLine("2. 请求队列:");
Console.WriteLine(" - 内核维护请求队列");
Console.WriteLine(" - 每个设备有独立的队列");
Console.WriteLine(" - 队列中的请求按顺序处理");
Console.WriteLine();
Console.WriteLine("3. 完成跟踪:");
Console.WriteLine(" - 硬中断处理程序标记完成的请求");
Console.WriteLine(" - 软中断处理程序检查所有请求");
Console.WriteLine(" - 找到标记为完成的请求");
Console.WriteLine(" - 唤醒对应的等待进程");
}
}
结
这里介绍了cpu的使用率是怎么回事,里面介绍了应用时间、系统时间、中断时间、空闲时间的概念,和他们之间的去呗,更有利于排查cpu高的问题,或者cpu不高,但是系统很慢,了解这些概念后,就容易根据这几个方面去排查。
浙公网安备 33010602011771号