简析 Linux 的 CPU 时间

科技资讯 投稿 35200 0 评论

简析 Linux 的 CPU 时间

从 CPU 时间说起..

top 命令的界面,相信大家应该都不陌生。

top - 19:01:38 up 91 days, 23:06,  1 user,  load average: 0.00, 0.01, 0.05
Tasks: 151 total,   1 running, 149 sleeping,   1 stopped,   0 zombie
%Cpu(s:  0.0 us,  0.1 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8010420 total,  5803596 free,   341300 used,  1865524 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  6954384 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
13436 root      20   0 1382776  28040   5728 S   0.3  0.4 251:21.06 n9e-collector
    1 root      20   0   43184   3384   2212 S   0.0  0.0   5:15.64 systemd
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.28 kthreadd
    3 root      20   0       0      0      0 S   0.0  0.0   0:00.58 ksoftirqd/0
    5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
    7 root      rt   0       0      0      0 S   0.0  0.0   0:35.48 migration/0

%Cpu(s: 这一行表示的是 CPU 不同时间的占比,其中大家比较熟悉的应该是 system timeuser time

  • 正常情况下

  • user time
  • 占比应该最高,这是进程运行应用代码的的时间占比(CPU 密集)

  • system time 占用率高,则意味着存在频繁的系统调用(IO 密集)或者一些潜在的性能问题

内核态与用户态

操作系统的核心功能就是管理硬件资源,因此不可避免会使用到一些直接操作硬件的CPU指令,这类指令我们称之为特权指令。特权指令如果使用不当,将会导致整个系统的崩溃,因此操作系统提供了一组特殊的资源访问代码 —— 内核kernel 来负责执行这些指令。

  • 内核空间

  • kernel memotry
  • :存放内核代码和数据(进程间共享)

  • 用户空间user memotry:存放用户程序的代码和数据(相互隔离)

通过区分内核空间和用户空间的设计,隔离了操作系统代码与应用程序代码。即便是单个应用程序出现错误也不会影响到操作系统的稳定性,这样其它的程序还可以正常的运行。

系统调用system call。系统调用是操作系统的最小功能单位。

  • 执行用户空间的代码时,处于

  • 用户态
  • 执行内核空间的代码时(系统调用),处于内核态

每次执行系统调用时,都需要经历以下变化:

  • CPU 保存用户态指令,切换为内核态

  • 在内核态下访问系统资源

  • CPU 恢复用户态指令,切换回用户态

user time 与 system time 分别就是对应 CPU 在用户态与内核态的运行时间。

上下文切换

  • 等待系统资源分配

  • 调用sleep主动挂起

  • 被优先级更高的线程抢占

  • 发生硬件中断,跳转执行内核的中断服务程序

同个进程下的线程共享进程的用户态空间,因此当同个进程的线程发生切换时,都需要经历以下变化:

  • CPU 保存线程 A 用户态指令,切换为内核态

  • 保存线程 A 私有资源(栈、寄存器...)/li>

  • 加载线程 B 私有资源(栈、寄存器...)

  • CPU 恢复线程 B 用户态指令,切换回用户态

  • CPU 保存线程 A 用户态指令,切换为内核态

  • 保存线程 A 私有资源(栈、寄存器...)

  • 保存线程 A 用户态资源(虚拟内存、全局变量...)

  • 加载线程 B 用户态资源(虚拟内存、全局变量...)

  • 加载线程 B 私有资源(栈、寄存器...)

  • CPU 恢复线程 B 用户态指令,切换回用户态

每次保存和恢复上下文的过程,都是在系统态进行的,并且需要几十纳秒到数微秒的 CPU 时间。当切换次数较多时会耗费大量的 system time,进而大大缩短了真正运行进程的 user time

线程调度

Linux 中的线程是从父进程 fork 出的轻量进程,它们共享父进程的内存空间。

prirority的概念,并按照优先级高低分为两种:

  • 实时进程(优先级 0~99)

  • 普通进程(优先级 100~139)

runqueue,需要运行的线程会被加入到这个队列中。

每个队列可以进一步细分为 3 个队列以及 5 种调度策略:

    dl_rq
    • SCHED_DEADLINE 选择 deadline 距离当前时间点最近的任务执行

  • rt_rq —— 可以互相抢占的实时任务

    • SCHED_FIFO
    • 一旦抢占到 CPU 资源,就会一直运行直到退出,除非被高优先级抢占

  • SCHED_RR 当 CPU 时间片用完,内核会把它放到队列末尾,可以被高优先级抢占

  • cfs_rq —— 公平占用 CPU 时间的普通任务

    • SCHED_NORMAL
    • 普通进程

  • SCHED_BATCH 后台进程

dl_rq 里选择任务,然后从 rt_rq 里选择任务,最后从 cfs_rq 里选择任务。所以实时任务总是会比普通任务先得到执行。

nice 值

为了保证 cfs_rq 队列的公平性,Linux 采用完全公平调度算法 CFS Completely Fair Scheduler进行调度,保证每个普通进程都尽可能被调度到。

vruntime 作为衡量是否公平的依据:

    vruntime
  • 与任务占用的 CPU 时间成正比

  • vruntime 与任务优先级成反比(优先级越高vruntime增长越慢)

vruntime 较小,说明它以前占用 CPU 的时间较短,受到了不公平对待,因此该进程会被优先调度,从而到达所谓的公平性。

nice 值的概念。其的取值其范围是 -20 ~ +19,调整该值会改变进程的优先级:prirority += nice

vruntime 计算也会受到影响:

进程的 nice 值越小, 优先级越高, 所能分到的运行时间也越多

nice time 而不是 user time。简单来说,nice time 表示 CPU 花了多少时间用于运行低优先级的任务。

nice time 占比比较高时,通常是某些定时任务调度器导致的:它们会为后台任务进程设置一个较大的 nice 值,避免这些进程与其他线程争抢 CPU 资源。

软中断

CPU 接收到中断时,会切换到内核态执行特定的中断服务,并且期间不允许其他中断抢占(关中断)。
当中断服务需要执行较长时间时,可能会导致且其他的中断得不到及时的响应。

  • 上半部

  • top half
  • :在屏蔽中断的上下文中运行,用于完成关键性的处理动作

  • 下半部bottom half:不在中断服务上下文中执行,主要处理不那么急迫但耗时的任务

内核在处理完中断上半部后,可以延期执行下半部,该机制被称为软中断softirq
软中断处理的过程是不会关中断的,因此当有硬中断到来的时候,可以及时响应。

  • 注册: 软中断状态寄存器

  • irq_stat
  • 处理: 软中断向量表 softirq_vec

  • 触发: 软中断守护线程 daemon

  1. 调用open_softirq(将软中断服务程序注册到软中断向量表softirq_vec(可选)

  2. raise_softirq(触发软中断事务

  • 中断关闭的情况下,设置软中断状态位

  • irq_stat
  • 如果调用者不在中断上下文(普通进程调用),那么直接唤醒daemon线程

  • daemon线程被唤醒后会运行do_softirq(处理软中断

    • 检查

    • irq_stat
    • 是否存发生软中断事件

  • 调用 softirq_vec 中对应的软中断服务程序

  • 再次检查 irq_stat,如果发现新的软中断,就会唤醒ksoftrqd线程来处理

  • ksoftrqd 机制

    这意味着:

    • 一个软中断不会去抢占另一个软中断,只有硬件中断才可以抢占软中断

    • 如果软中断太过频繁,用户进程可能永远无法获得 CPU 时间

    ksoftrqd线程。如果所有的软中断在短时间内无法被处理完,内核就会唤醒ksoftrqd处理剩余的软中断。以下面这张图为例:

    • 网卡数据就绪,通过硬中断通知 CPU 进行处理

    • 硬中断服务程序调用raise_softirq(触发软中断,唤醒daemon

    • 硬中断服务程序退出后,daemon被唤醒开始处理软中断

    • 遍历过一遍向量表后,daemon发现仍有未处理的软中断,唤醒ksoftrqd

    • ksoftrqd获得 CPU 时间片后,继续处理未完成的软中断

    由于 ksoftrqd 其实是一个 nice 值为 0 的普通线程,会进入 cfs_rq 参与调度,可以和普通进程公平地使用 CPU。

    ksoftrirqd 长时间得不到 CPU,就会致使软中断的延迟变得很大,因此 ksoftirqd 的实时性是很难得到保障。

    ksoftirqd 处理,导致 ping 延迟变得很大。

    中断的影响

    interrupt time不会显著上升。

    关中断的操作在内核里随处可见,这反过来会给硬中断带来一些影响。比如,进程关中断时间太长会导致网络报文无法及时处理,进而引起业务性能抖动。

    softirq time 很大则很可能意味着用户线程会受到影响。

    softirq time 通常会比较高,可能存在网络连接数过多,或者网络流量过大的情况,

    ksoftirqd 的优先级与用户线程是一致的,因此,如果软中断处理函数是在 ksoftirqd 里执行的,那它可能会有一些延迟。

    时间窃取

    steal time定义为 “虚拟机管理进程 hypervisor 从 VM 窃取的时间”。 该概念是Xen,KVM,VMware 等社区或者厂商推广到Linux社区的。

    pCPU 时,会导致 VM 的虚拟 CPU vCPU 可用的处理器时间减少,从而造成 VM 性能下降。

    • 多个高负载 VM 的

    • vCPU
    • 的运行在同个

    • pCPU
    • 上(公有云的 CPU 超卖)

    • VM 的 vCPU  与线程绑定在了某个特定的 pCPU 上,导致虚拟主机 vhost 进程处理 I/O 时从这些 vCPU  上窃取时间

    • 虚拟机监控程序进程(例如监视,日志记录和I/O进程)与 VM 争抢 pCPU

    编程笔记 » 简析 Linux 的 CPU 时间

    赞同 (79) or 分享 (0)
    游客 发表我的评论   换个身份
    取消评论

    表情
    (0)个小伙伴在吐槽