跳转至

文章背景与核心概要

在 Unix 与类 Unix 操作系统的运维监控中,“平均负载 (load average)”一直是最核心却也最容易被误读的性能指标。早在十年前,作者就曾对各类开源 Unix 系统的负载统计口径进行过深入梳理;如今时隔十年,作者结合最新的内核源码再度带来现代视角的权威解读。调查表明,几乎所有的现代类 Unix 阵营成员 (包括 NetBSD、OpenBSD、FreeBSD 和 Illumos) 都高度一致地将平均负载严格定义为“运行中与可运行进程”的数量;唯独 Linux 独树一帜,将处于不可中断等待状态 (TASK_UNINTERRUPTIBLE,如磁盘 I/O 或硬件驱动阻塞) 的任务一并纳入计算。本文不仅带你穿透各家内核源码的具体实现差异,更追溯了 Linux 这一特殊设计背后的历史渊源与技术权衡。


2026 年各类 Unix 系统的平均负载机制深度剖析

The Many Load Averages of Unix(es) in 2026

概要

Summary

在对各大开源 Unix 系统中“平均负载 (load average)”定义差异展开首次调查的整整十年后,本文结合最新的内核源码链接,对这一经典主题进行了全新的梳理。核心发现与十年前依然保持一致:几乎所有的 Unix 系统都严格将平均负载定义为“正在运行中与处于就绪态的可运行进程”的总数;唯独 Linux 独树一帜,将处于不可中断等待状态 (例如正在等待磁盘 I/O 或特定硬件驱动响应) 的进程同样计入了负载之中。

A decade after investigating the divergent definitions of "load average" across various free Unixes, this article provides a modern refresher with current kernel source links. The core finding remains consistent: while virtually all Unix systems define the load average strictly by the count of running and runnable processes, Linux stands apart by also including processes in uninterruptible states (such as those waiting on disk I/O and specific hardware drivers).



引言

Introduction

十年前,我曾写过一篇题为《Unix 家族各自为政的平均负载》的文章,探讨不同开源 Unix 系统对“平均负载”的定义差异,并揭示了它们之间惊人的分歧。时光荏苒,许多系统的底层实现已然演进——再加上我也意识到十年前自己对某些 BSD 系统的机制理解可能存在偏差——是时候结合当前最新的内核源码链接来做一次全新的知识更新了。本次重点聚焦于 Linux 以及各大 *BSDs 系统。

A decade ago, I wrote The many load averages of Unix(es) to explore how different free Unixes defined "load average," which revealed significant divergence. Since things have changed—and acknowledging potential misunderstandings of the BSDs a decade ago—it is time for a refresher complete with current kernel source links, focusing primarily on Linux and the *BSDs.

一句话概括核心结论:除了 Linux 以外的所有现代类 Unix 系统,大体上都一致认同平均负载只统计可运行的进程;唯有 Linux 会额外将等待某些特定资源的进程 (尤其是等待磁盘 I/O 的任务) 一并计算在内。

To summarize: everyone except Linux agrees that the load average counts only runnable processes (more or less); only Linux also includes processes waiting on certain sorts of things, especially disk I/O.

(关于 BSD 家族的这段演进历史,可参见十年前的第一篇文章。)

(For the historical path of the BSDs, see the first entry.)



NetBSD

NetBSD

在所有系统中,NetBSD 的实现最为一目了然,因为它的平均负载计算逻辑高度集中在单一源码文件中:sys/kern/kern_sync.c

NetBSD offers the most accessible situation because its load average calculation is consolidated in a single place: sys/kern/kern_sync.c.

NetBSD 的平均负载统计涵盖: * 所有正在运行或处于就绪态的可运行进程; * 正在通过 fork 系统调用创建中的子进程; * 在当前时钟滴答 (tick) 周期内刚进入睡眠状态的进程 (这是由于 sched_lwp_stats() 恰好在负载计算函数检查之前递增了 l->l_slptime 计数)。

NetBSD's load average counts: * All processes that are running or runnable * Processes being created via a fork * Processes that went to sleep during the current tick (because sched_lwp_stats() increments l->l_slptime right before the load average calculation checks it).

尽管 NetBSD 的 getloadavg(3) 手册页并未显式写明第三条这一技术细节,但这也合情合理:毕竟在当前滴答周期内刚休眠的进程,在被统计之前的大部分时间里很可能都在保持运行。

While the NetBSD getloadavg(3) manual page doesn't explicitly document this nuance, it is forgivable; processes that went to sleep this tick were presumably running earlier in the tick before being counted.



OpenBSD

OpenBSD

OpenBSD 中,平均负载是根据分配给各个 CPU 的可运行任务数量汇总计算而来的 (源码参考 1)。每当进程进入或离开运行队列时,该计数都会被动态更新 (源码参考 2)。

In OpenBSD, the load average is computed from the number of runnable tasks assigned to each CPU (cf). This count is dynamically updated as processes enter and leave the run queue (cf).

OpenBSD 的 getloadavg(3) 手册页明确记录了这一定义。虽然早期 OpenBSD 曾在历史上把某些休眠线程也视作运行态,但该机制已于 2017 年被彻底移除

The OpenBSD getloadavg(3) explicitly documents this definition. Although OpenBSD historically considered some sleeping threads to be running, this behavior was removed in 2017.

(与我十年前的认知相反,我现在确信 NetBSD 和 OpenBSD 中的 slptime 字段统计的是时钟滴答数,而非实际秒数。)

(Contrary to my belief a decade ago, I now believe the slptime fields in NetBSD and OpenBSD count ticks rather than seconds.)



FreeBSD

FreeBSD

在 FreeBSD 中,平均负载依赖于一组封装略显晦涩的函数所维护的运行计数值 (源码参考 1源码参考 2)。据我所知,它主要是追踪进程 (线程) 进出运行队列的动态,这与 FreeBSD 的 getloadavg(3) 手册中给出的常规描述完全吻合。

In FreeBSD, the load average relies on a running count managed via a somewhat opaque set of functions (cf, also). As far as I can tell, this tracks processes (threads) as they enter and exit the run queues, aligning with the generic claims of the FreeBSD getloadavg(3) manual.



Illumos

Illumos

理解 Illumos 内核的具体实现需要花费不少精力,但从现有代码来看,它似乎同样完全依赖于正在运行以及就绪可运行的进程数量 (源码参考)。这既契合了 Illumos 的 getloadavg(3) 手册说明,也印证了我十年前所描述的情况

Understanding the Illumos kernel's implementation requires more effort than I am willing to spend here, but it appears to rely exclusively on running and runnable processes (cf). This matches the behavior claimed by the Illumos getloadavg(3) and mirrors the situation I described a decade ago.



Linux:独树一帜的特例

Linux: The Odd One Out

Linux 是整个家族中的绝对特例。尽管 Linux 的 getloadavg(3) 手册宣称平均负载仅统计就绪可运行的进程,但 proc_loadavg(5) 手册在技术层面上更为准确,而内核源码更是将这一点阐释得清清楚楚。

Linux is the outlier. While the Linux getloadavg(3) claims that the load average only counts runnable processes, proc_loadavg(5) is technically correct, and the kernel source code makes it explicit.

kernel/sched/loadavg.c 中的注释和代码均证实:Linux 的平均负载在计算可运行任务之外,还会将不可中断睡眠 (Uninterruptible) 的进程 (任务) 一并纳入。系统中有大量的子系统 (尤其是各种硬件驱动程序内部) 会将任务标记为 TASK_UNINTERRUPTIBLE 状态。因此,当这类任务在 kernel/sched/core.c 中发生阻塞时,它们便会为系统负载做出“贡献”,且其负载增减在任务阻塞时任务重新激活时都会被精准追踪。

Both the comments and code in kernel/sched/loadavg.c confirm that Linux's load average includes uninterruptible processes (tasks) alongside running or runnable ones. A wide variety of subsystems—often within hardware drivers—set tasks to TASK_UNINTERRUPTIBLE. Consequently, when such a task blocks in kernel/sched/core.c, it contributes to the system load, with adjustments tracked on blocking and on reactivation.

关于 Linux 具体实现的更多技术细节,我在更早前的一篇文章中曾进行过详尽解析。

I covered the specifics of the Linux implementation in detail in an earlier entry.



历史溯源

Historical Context

将等待磁盘 I/O 或处于不可中断等待状态的进程计入负载的做法,最早可以追溯到 3BSD 的原始行为。后来,源于 4BSD 的各类商业 Unix 系统 (例如 SunOS) 也沿袭了这一惯例 (在 SunOS 中,等待 NFS 远程“I/O”的进程同样会推高平均负载,哪怕仅仅是因为远端 NFS 服务器卡死无响应)。

Counting processes waiting for disk I/O or stuck in uninterruptible waits traces back to original 3BSD behavior. This practice was adopted by commercial Unixes derived from 4BSD, such as SunOS (where processes waiting on NFS "I/O" also contributed to the load average, even if delayed by an unresponsive NFS server).

正如 Brendan Gregg 曾经撰文探讨过的,极早期的 Linux 版本其实也只统计可运行进程。然而在 1993 年 10 月,Linux 引入修改,将不可中断状态的进程也囊括了进来。颇具讽刺意味的是,正当其他 Unix 变种纷纷着手废弃这一古老机制的时候,Linux 却通过这次改动在事实上倒向了 4BSD 的历史设计。

As Brendan Gregg has covered, very early Linux versions counted only runnable processes. However, in October 1993, this was extended to include uninterruptible processes. Ironically, this change aligned Linux with historical 4BSD behavior right around the time other Unix variants were phasing it out.



后记:关于 Linux 进程状态的一点技术补充

PostScript: A Technical Note on Linux States

Linux 的 proc_loadavg(5) 在严谨的技术层面是准确的:内核向外汇报的所有处于 TASK_UNINTERRUPTIBLE 状态的进程,其状态标识均为 'D' (源码参考 1源码参考 2)。

Linux's proc_loadavg(5) is narrowly technically correct: all processes in TASK_UNINTERRUPTIBLE are reported by the kernel as state 'D' (cf, also).

但需要注意的是,这并不意味着所有处于该状态的进程都在等待磁盘 I/O;系统中的各种组件——包括特定的 GPU 和以太网驱动——也会出于其他完全无关的等待场景而将进程置于该状态。

However, this does not mean all such processes are waiting for disk I/O; various components—including certain GPU and Ethernet drivers—utilize this status for unrelated waiting states.