CPU 负载¶
Linux 通过 /proc/stat 和 /proc/uptime 导出各种信息,供 top(1) 等用户态工具用于计算系统在特定状态下花费的平均时间,例如
$ iostat
Linux 2.6.18.3-exp (linmac) 02/20/2007
avg-cpu: %user %nice %system %iowait %steal %idle
10.01 0.00 2.92 5.44 0.00 81.63
...
在这里,系统认为在默认的采样周期内,系统有 10.01% 的时间在用户空间执行任务,2.92% 的时间在内核中执行,总体上有 81.63% 的时间处于空闲状态。
在大多数情况下,/proc/stat 提供的信息能相当准确地反映现实,然而,由于内核收集这些数据的本质方式/时机,有时它完全不可信。
那么这些信息是如何收集的呢?每当定时器中断触发时,内核会查看此时正在运行的是哪种任务,并增加对应于该任务类型/状态的计数器。这样做的问题在于,在两次定时器中断之间,系统可能已经在各种状态之间切换了多次,但计数器只针对最后一种状态进行了增加。
示例¶
如果我们设想一个系统,其中有一个任务按以下方式周期性地消耗 CPU 周期
time line between two timer interrupts
|--------------------------------------|
^ ^
|_ something begins working |
|_ something goes to sleep
(only to be awaken quite soon)
在上述情况下,根据 /proc/stat,系统的负载将为 0%(因为定时器中断总是在系统执行空闲处理程序时发生),但实际上负载接近 99%。
人们可以想象出更多内核的这种行为会导致 /proc/stat 中出现相当不准确信息的情况。
/* gcc -o hog smallhog.c */
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#define HIST 10
static volatile sig_atomic_t stop;
static void sighandler(int signr)
{
(void) signr;
stop = 1;
}
static unsigned long hog (unsigned long niters)
{
stop = 0;
while (!stop && --niters);
return niters;
}
int main (void)
{
int i;
struct itimerval it = {
.it_interval = { .tv_sec = 0, .tv_usec = 1 },
.it_value = { .tv_sec = 0, .tv_usec = 1 } };
sigset_t set;
unsigned long v[HIST];
double tmp = 0.0;
unsigned long n;
signal(SIGALRM, &sighandler);
setitimer(ITIMER_REAL, &it, NULL);
hog (ULONG_MAX);
for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog(ULONG_MAX);
for (i = 0; i < HIST; ++i) tmp += v[i];
tmp /= HIST;
n = tmp - (tmp / 3.0);
sigemptyset(&set);
sigaddset(&set, SIGALRM);
for (;;) {
hog(n);
sigwait(&set, &i);
}
return 0;
}
参考资料¶
致谢¶
Con Kolivas, Pavel Machek