之前写过一段代码,正好同事问起,深入学习分享一下。
背景
问题是这样,底层网络驱动相关的32bit寄存器记录了一个流量统计的数据,要在web上展示出来,之前的方案是直接通过ioctl读取走该值。但这时问题就出现了:在长时间的发流或大流量环境下,流量超过4G(2的32次方),寄存器统计就会发生翻转(从0开始统计)。
我的解决方案:
1、定义了一个15s的定时器,并定义了一个long long 类型数据,每次检查到数据翻转时,给该值累加4G的大小
2、加spinlock自旋锁turn_lock保护数据,由于定时器会访问并回写该值,web(用户态进程)会不定时的读取该值,可能会导致web拿到的并非最新数据,所以加锁保护。
PS:
spin_lock_irqsave(&turn_lock, flags);
.//保护区(临界区)
.
.
spin_unlock_irqrestore(&turn_lock, flags);
1、由于实际上不止一个数据,包括了收发、错误、丢弃等数据,所以使用锁来保护
2、web获取数据,需要通过中断(异常)陷入内核,最好使用spin_lock_irqsave函数来保护数据。原因:中断优先级高,可能会在定时器先获取到锁的情况下,同样获得锁,造成死锁;spin_lock_irqsave会1、保存中断状态2、关中断3、获取锁。之后spin_unlock_irqrestore恢复现场。(如果这里使用互斥锁,容易造成死锁)
原子操作
当下我们使用的PC应该都是多核,所以对于多个线程到底是并发还是并行,不是那么容易确定,因为这时操作系统分配的,但无论是并发还是并行,都会涉及到资源抢夺的问题。所以我们必须对需要保护的资源进行保护。
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。
我们可以通过代码来看下,在多线程产生条件竞争时的情况。
通过声明两个线程,分别对变量进行递增操作:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/types.h>
int b = 0;//声明全局变量b
void *fun1(){
int i;
for(i=0;i<1000000;i++){
b++;
}
printf("%d\n",b);
}
int main(){
int i;
pthread_t thid[2];
for(i =0;i<2;i++){
pthread_create(&thid[i],NULL,(void*)fun1,NULL);
}
pthread_join(thid[0],NULL);
pthread_join(thid[1],NULL);
return b;
}
运行下(注意编译时添加编译参数-lpthread
):
代码声明两个线程,分别对全局变量b进行递增一百万次递增操作,按照正常,这里第二个线程运行结束,应该输出20,000,000
但输出却小于二百万,这里原因就是产生了竞争。
过程如下:
**线程1** **线程2**
获得b=5 ——
增加b(5->6) 获得b=5
写回b(6) 增加b(5->6)
—— 写回b(6)
致使同一个变量被同时访问,最后输出结果小于我们预期。
这时引入原子操作。
将上述代码中的b从新定义为:_Atomic int b = 0;
运行结果如下:
这时第二次打印结果为20,000,000
,(前一打印是因为在第一个线程运行期间,第二个线程已经起来),毫无疑问这时正确的结果,因为两个原子操作绝不可能并发的访问同一个变量。
另外,我们也可以通过汇编代码,看下原子量的操作,和普通变量有何不同。
参考
http://blog.chinaunix.net/uid-10747583-id-2920900.html
https://www.zhihu.com/search?type=content&q=%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C