Linux内核的percpu变量如何精确取值?

我选择signed int类型的percpu变量作为Linux内核中的半连接数量的统计值,我没有选择atomic类型的全局变量,我更是没有采用spinlock去保护一个一般的unsigned int类型的全局变量。

之所以选择signed int类型的percpu变量是因为要考虑到连接的处理在不同CPU之间的迁移。

到此为止,percpu变量的一切都很完美,轻量且无锁的操作是它作为统计计数器的最大优势。作为统计计数器,我们在读数的时候是允许一定的误差的,所以我们一般采用无锁的方式去读数:

for_each_possible_cpu(i) {
	res += per_cpu_ptr(ptr, i);
}

我们知道,以上语句的执行是需要时间的,无论遍历所有CPU有多快,但也是需要时间的,在这段时间内,percpu变量就可能发生遍历累加逻辑在执行期间无法捕捉的变化。

幸亏这些统计值是给人来读的,而人读这些统计值的目的是通过数值去导出一些规律,通常对数据分辨率的要求不会达到毫秒以下。

然而,如果内核需要这些值怎么办?

比如内核依照这些变量的值来执行是否释放一个数据对象的逻辑:

for_each_possible_cpu(i) {
	res += per_cpu_ptr(ptr, i);
}
if (res == 0) 
	free_something(...);

以上的代码显然是危险⚠️的,那么怎么办?

很简单,一把读写锁即可。但是和通常的读写锁的用法正好相反:

  • 更新percpu变量时拿read锁。
  • 读取percpu变量时拿write锁。

更新逻辑如下:

signed int *per_cpu_counter = per_cpu_ptr(..., this_cpu);
...
read_lock(&percpu_wrlock);
*per_cpu_counter ++;
read_unlock(&percpu_wrlock);

读取逻辑如下:

write_lock(&percpu_wrlock);

for_each_possible_cpu(i) {
	res += per_cpu_ptr(ptr, i);
}
if (res == 0) 
	free_something(...);

write_unlock(&percpu_wrlock);

虽然说,read/write lock期间会禁用抢占,并且会插入屏障,但具体到特定的体系结构以及具体的Linux内核平台,这些看似会 影响性能 的操作大多数都是可以忽略的,比方说,大部分的服务器内核都是关闭内核抢占选项来编译的。


如果从总体上看而不是抠细节的话,遍历所有CPU并取出percpu变量累加的过程是非常快的,至少这是一个O(1)O(1)操作,更何况,绝大多数机器的CPU基本也就是3位数量级以下,所以这个O(1)O(1)的常数值本身也是非常小的。

经理不待见trick,经理也不待见遍历,哪怕for一个20也不行。

2007年夏天,经理说第一次慢,经理不管DNS,所以一个工人就把北京一个机房的服务器IP写死在代码里了。后来这个服务器搬迁了,这个工人也离职了,事情到了我这里,我查到了这个硬编码,但是我很认同这种做法,虽然我更倾向于用配置文件而不是代码的宏定义…

我在配置文件里写死了20个常用的IP地址,前10个是服务器的IP,后10个是我首选的10个DNS,然后就上线了,效果真不错!

但是经理要看代码,经理看见了一个for,for了20个IP,经理说这个循环是耗时的,经理非要让我改,我百口难辩,于是,我改成了下面的样子:

if (是IP1)
	...
else if (是IP2)
	...
else if (是IP3)
	...
else if (是IP4)
	...
else if (是IP5)
	...
else if (是IP6)
	...
	...
else if (是IP20)
	...
else 
	...

OK,消除了循环,增加了代码量,大家唱着歌,下班了。


浙江温州皮鞋湿,下雨进水不会胖。

发布了1586 篇原创文章 · 获赞 5151 · 访问量 1117万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览