Operations 12 min read

How Linux’s top Command Calculates CPU Usage: Inside the Kernel

This article explains the meaning of each CPU usage field shown by the top command, dives into the Linux kernel source that records these metrics via clock interrupts, and shows how the percentages are computed from /proc/stat data.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How Linux’s top Command Calculates CPU Usage: Inside the Kernel

When we run the top command we can see many different CPU usage percentages. The article first lists the meaning of each field shown in the top output: us: user time – CPU time spent executing user processes (including nice time). sy: system time – CPU time spent in kernel code, IRQ and soft‑IRQ. ni: nice time – CPU time for user processes with a positive nice value (lower priority). id: idle time – CPU waiting for work. wa: waiting time – CPU time waiting for I/O completion. hi: hard IRQ time – time spent handling hardware interrupts. si: soft IRQ time – time spent handling software interrupts. st: steal time – time the virtual CPU is involuntarily waiting while the hypervisor serves another vCPU.

Understanding these fields is easier when we look at the kernel source that updates them during the clock interrupt . A clock interrupt is a hardware timer interrupt that, after the current instruction finishes, invokes the clock‑interrupt handler to update the system time and run periodic tasks.

The handler records per‑CPU statistics in a cpu_usage_stat structure, whose definition directly maps to the fields shown by top:

struct cpu_usage_stat {
    cputime64_t user;
    cputime64_t nice;
    cputime64_t system;
    cputime64_t softirq;
    cputime64_t irq;
    cputime64_t idle;
    cputime64_t iowait;
    cputime64_t steal;
    cputime64_t guest;
};

Each CPU gets its own cpu_usage_stat at kernel initialization, and the clock‑interrupt handler updates it via the function account_process_tick:

void account_process_tick(struct task_struct *p, int user_tick) {
    cputime_t one_jiffy_scaled = cputime_to_scaled(cputime_one_jiffy);
    struct rq *rq = this_rq();
    // 1. user process → account_user_time
    if (user_tick) {
        account_user_time(p, cputime_one_jiffy, one_jiffy_scaled);
    }
    // 2. kernel code → account_system_time
    else if ((p != rq->idle) || (irq_count() != HARDIRQ_OFFSET)) {
        account_system_time(p, HARDIRQ_OFFSET, cputime_one_jiffy, one_jiffy_scaled);
    }
    // 3. idle process → account_idle_time
    else {
        account_idle_time(cputime_one_jiffy);
    }
}

The function distinguishes three situations:

If the CPU is executing a user program, account_user_time is called.

If the CPU is executing kernel code, account_system_time is called.

Otherwise the CPU is running the idle task and account_idle_time is called.

1. Accounting user time

account_user_time

adds the current jiffy to either the nice or user field depending on the process’s nice value:

void account_user_time(struct task_struct *p, cputime_t cputime, cputime_t cputime_scaled) {
    struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
    cputime64_t tmp;
    // ...
    if (TASK_NICE(p) > 0)
        cpustat->nice = cputime64_add(cpustat->nice, tmp);
    else
        cpustat->user = cputime64_add(cpustat->user, tmp);
    // ...
}

Thus both user and nice represent CPU time spent in user space; the nice field is used for low‑priority processes.

2. Accounting kernel time

account_system_time

records time spent in hard IRQs, soft IRQs, or generic kernel execution:

void account_system_time(struct task_struct *p, int hardirq_offset, cputime_t cputime, cputime_t cputime_scaled) {
    struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
    cputime64_t tmp;
    // 1. hard IRQ → irq field
    if (hardirq_count() - hardirq_offset)
        cpustat->irq = cputime64_add(cpustat->irq, tmp);
    // 2. soft IRQ → softirq field
    else if (softirq_count())
        cpustat->softirq = cputime64_add(cpustat->softirq, tmp);
    // 3. other kernel code → system field
    else
        cpustat->system = cputime64_add(cpustat->system, tmp);
    // ...
}

Both irq and softirq are considered part of kernel execution time.

3. Accounting idle time

The idle task’s time is recorded by account_idle_time. If any process is waiting for I/O, the time is added to iowait; otherwise it goes to idle:

void account_idle_time(cputime_t cputime) {
    struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
    cputime64_t cputime64 = cputime_to_cputime64(cputime);
    struct rq *rq = this_rq();
    if (atomic_read(&rq->nr_iowait) > 0)
        cpustat->iowait = cputime64_add(cpustat->iowait, cputime64);
    else
        cpustat->idle = cputime64_add(cpustat->idle, cputime64);
}

Thus iowait is a subset of idle time representing processes blocked on I/O.

How top computes the percentages

The top command reads /proc/stat to obtain the raw counters. A typical line looks like:

[vagrant@localhost ~]$ cat /proc/stat
cpu  245 10 1142 1097923 95 0 28 0 0 0
cpu0 245 10 1142 1097923 95 0 28 0 0 0
...

The numbers correspond to user, nice, system, idle, iowait, irq, softirq, steal, etc. The total CPU time is the sum of all fields:

CPU_total = user + nice + system + idle + iowait + irq + softirq + steal

Each percentage is then calculated as the field value divided by the total, e.g.:

%us = user / CPU_total
%ni = nice / CPU_total
%sy = system / CPU_total
%id = idle / CPU_total
%wa = iowait / CPU_total
%hi = irq / CPU_total
%si = softirq / CPU_total
%st = steal / CPU_total

In summary, the article walks through the kernel structures and functions that populate the counters shown by top, and explains how those raw numbers are turned into the familiar CPU usage percentages.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

LinuxCPUtop
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.