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不高,但是系统很慢,了解这些概念后,就容易根据这几个方面去排查。

posted @ 2025-08-04 17:10  敖毛毛  阅读(45)  评论(0)    收藏  举报