linux top

man 5 proc
iowait
https://man7.org/linux/man-pages/man5/proc.5.html
fs/proc/stat.c
https://elixir.bootlin.com/linux/latest/source/fs/proc/stat.c#L135
https://blog.csdn.net/Roland_Sun/article/details/106412763
https://elixir.bootlin.com/linux/latest/source/kernel/time/tick-sched.h#L55

fs/proc/stat.c
show_stat
-> get_iowait_time
-> get_cpu_iowait_time_us

question:
1. char const *write_error = _(“write error”); 这是个啥子表达?

load average
/proc/loadavg
man proc find loadavg

fs/proc/loadavg.c

nr代表number
calc_load_fold_active()这个函数读取rq中nr_running也就是当前cpu运行队列中task的数量复制给nr_active,然后读取rq->nr_uninterruptible也就是不可中断的task个数,所以nr_active等于当前cpu运行队列中task个数与当前cpu不可中断的task个数,如果nr_active与rq->cal_load_active值不相等就计算差值,然后更新rq->cal_load_active的值为nr_active

重要!!!

首先,找到fs/proc/loadavg.c
loadavg_proc_show
-> get_avenrun() 读取avenrun数组的值
-> print

这个avenrun是一个数组,定义在kernel/sched/loadavg.c中
在该文件中搜索这个数组看看他是在哪里被写入的:
-> calc_global_nohz
-> calc_global_load

在calc_global_load中,首先读取calc_load_update的值,这个也是定义在该文件中,用于记录下一次更新avenrun的jiffies。
如果jiffies 小于 calc_load_update + 10 这个函数直接就返回。也就是没到calc_load_update + 10这个时间点就不执行该函数。如果达到了就
delta = calc_load_nohz_read();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
这个暂不分析
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;

avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);

读取calc_load_tasks的值,这个也定义在该文件中,是一个原子变量,因为每个cpu都会修改该值,所以必须保证每次修改操作的原子性。读取以后讲active左移11位传入calc_load中计算
WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);
写入下一次更细avenrun的jiffies时间点,然后执行
calc_global_nohz(); 这个也暂不分析。

那么问题来了,当前这个函数在哪里执行的。换句话说,在哪里改变的avenrun这个数组的值的。
calc_global_load这个函数是在每一个tick中断中调用的,所以每次更新jiffies时会调用这个函数,并且达到calc_load_update + 10 时间点的时候才会更新avenrun。
下一个问题是计算avenrun的calc_load_task是从哪来的?

在该文件中搜索calc_load_task会发现该值在
-> calc_global_load 与no_hz有关暂不分析
-> calc_global_load_tick
中被修改。

calc_global_load_tick 这个函数会在scheduler_tick函数中调用,而scheduler_tick在每个cpu上都会被调用,换句话说就是每个cpu都会调用calc_global_load_tick这个函数。
if (time_before(jiffies, this_rq->calc_load_update))
return;

delta  = calc_load_fold_active(this_rq, 0);
if (delta)
	atomic_long_add(delta, &calc_load_tasks);

this_rq->calc_load_update += LOAD_FREQ;

这个函数中首先判断一下是不是到了需要更新的时间点,如果是就调用calc_load_fold_active函数

calc_load_fold_active这个函数的作用是读取当前cpu的rq的nr_running + nr_uninterruptible的值,然后计算这个值与rq-> calc_load_active的差值,相同就返回0,不同就返回差值并且讲新的值设置给rq->calc_load_active

所以calc_load_fold_active这个函数返回的是当前cpu 这一次的nr_running + nr_uninterruptible task与上一次 的差值。
如果有插值当然就需要加到calc_load_task上。每个cpu都会进行这样的操作。

问题来了,为什么是这样做的呢?
如果你单独的去读取每个cpu的nr_running + nr_uninterruptible 的值,你需要频繁的切换cpu,对cpu数量较多的机器来说增加了太多的开销,所以准备了一个全局变量calc_load_task用于记录所有cpu上的nr_running + nr_uninterruptible 和,每次的变化都累加到这个变量上。

问题又又又来了?
为啥calc_global_load函数中
sample_window = READ_ONCE(calc_load_update);
if (time_before(jiffies, sample_window + 10))
return;
有一个10 ticks。
为了同步每个cpu,因为cpu调用scheduler_tick的时机会有差距并且还存在原子操作等同步机制造成的延时,但是10个ticks已经是非常充足的时间来同步cpu完成更新calc_load_task的操作,所以等这个10个ticks就是为了让所有的cpu都能完成更新calc_load_task的操作。当所有的cpu都更新完成一个我们再进行计算算出所有cpu的负载

https://baijiahao.baidu.com/s?id=1719292352009888378&wfr=spider&for=pc

tick_handle_periodic
-> tick_periodic
-> do_timer
-> cala_global_load

关于计算load_average的算法:指数加权移动平均法(EMA)
传统的加权平均算法虽然计算精确但是有个严重的缺点就是需要记录每个数值,这对内核来说是灾难性的,需要记录的内容太多了。所以这里采用了指数加权移动平均法(EMA),优点是这一次的值只与上一次的数值有关,不需要记录很久以前的数值。
计算方法 :
a1 = a0 * e + a * (1 - e)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值