内核基础设施——percpu_counter
Contents
内核里很多模块都需要对一些事件进行统计,有一个叫做percpu_counter
的基础设施,来完成该任务。本文简单介绍了其用法和内核中的实现。
注意:本文中引用的内核代码版本为
v4.4.128
。
对于单核
其数据结构定义 include/linux/percpu_counter.h(line 94) 如下:
|
|
对于单核来说,比较简单,其操作主要是对计数器count
进行操作。
对于多核
其数据结构定义 include/linux/percpu_counter.h(line 19) 如下:
|
|
相比较于单核,多核稍微复杂一些,其为了提高效率,引用了__percpu
变量counters
,另外为了考虑cpu
的热插拔,其有引入了字段list
,用来将所有的percpu_counter
链接到一起。
API接口
其接口如下:
- percpu_counter_init
- percpu_counter_set
- percpu_counter_destroy
- percpu_counter_inc
- percpu_counter_dec
- percpu_counter_add
- percpu_counter_sub
- percpu_counter_sum
- percpu_counter_sum_positive
- percpu_counter_read
- percpu_counter_read_positive
- percpu_counter_compare
- percpu_counter_initialized
对结构体percpu_counter
操作时,如果只访问s32 __percpu *counters;
则不需要加锁,如果访问s64 count;
则需要加锁,防止竞争。
内核为了尽可能少的加锁,使用了一些编程技巧,对计数器增加或者减少计数时,大多数情况下不用加锁,只修改每cpu
变量s32 __percpu *counters;
,当计数超过一个范围时[-batch, batch]
,则进行加锁,将每cpu变量s32 __percpu *counters;
中的计数累计到s64 count;
中。
-
percpu_counter_init
初始化percpu_counter
中成员count
特定的值,并分配每cpu
变量counters
; -
percpu_counter_set
设置percpu_counter
中成员count
特定的值,并修改每cpu变量counters
的值为0
; -
percpu_counter_destroy
释放percpu_counter_init
中分配的每cpu
变量counters
; -
percpu_counter_inc/percpu_counter_dec/percpu_counter_add/percpu_counter_sub
四个方法主要对计数器进行操作,修改其值。修改过程中,就是用上面提到的技巧,尽可能少的加锁。 lib/percpu_counter.c(line 75)
|
|
技巧:这里特别注意
__this_cpu_sub(*fbc->counters, count - amount)
,乍一看,这里就是清零,为什么要写这么复杂呢?因为在计算count
值到该行代码之间,该cpu
对应的percpu_counter
计数可能增加,所以只有这样写才是最正确的。
-
percpu_counter_sum_positive/percpu_counter_sum
计算该计数器的数值(精确值),需要加锁,区别是percpu_counter_sum_positive
返回值最小为0;; -
percpu_counter_read/percpu_counter_read_positive
读出该计数器的粗略的数值,不需要加锁, 区别是percpu_counter_read_positive
返回值最小为0; -
percpu_counter_compare
用来比较计数器的数值和给定的数值的大小,这里也用到了上面提到的编程技巧,尽可能少的加锁。先通过percpu_counter_read
计算计数器的粗略值,此时不需要加锁,如果可以判断结果的话,就直接返回;如果判断不了结果的话,就得通过percpu_counter_sum
来进一步加锁计算精确的计数值来进行比较,代码可参考: lib/percpu_counter.c(line 200)
|
|
batch大小
该基础设施全名叫Fast batching percpu counters
,其能够减少加锁,提高效率,是由于选定了一个batch
值,只有每cpu
变量中计数器的值超过该范围后,才会加锁进行处理。
注意:只有在多核的系统上,才需要batch这个机制。
那么该值如何选定呢?
内核中batch
的大小为cpu
个数的两倍,但最小值为32
, 具体逻辑请查阅如下代码 lib/percpu_counter.c(line 158) :
|
|
当然用户可以自己指定batch
的大小,比如BDI
中
|
|
那该值为什么选定(8*(1+ilog2(nr_cpu_ids)))
?
内核中使用到ilog2(nr_cpu_ids)
的地方如下:
|
|
对于#define WB_STAT_BATCH (8*(1+ilog2(nr_cpu_ids)))
, 我们可以得到CPU
个数和结果直接的关系如下:
|
|
从上面的列表可以看出:
cpu
个数为1
时,结果为8
- 此后如果
cpu
个数翻倍的话,结果递增8
hotplug
对于支持cpu热插拔
的系统,内核定义了静态的变量percpu_counters
,将所有的percpu_count
在链接到一起。
|
|
percpu_counters_lock
自旋锁用来保护链表percpu_counters
。
在cpu下线时,需要完成以下动作:
- 因为
batch
的大小跟cpu
个数有关,所以要重新计算一下batch
的大小。 - 将要下线
cpu
对应的计数器添加到总的计数中,并清零该cpu
对应的每cpu
变量的值。
详细的逻辑,请参考代码 lib/percpu_counters.c(line 168)
我的虚拟机器上,一共有379
多个percpu_count
。