有SMP,中断,调度,内核竞争产生同步和竞争问题,通过 atomic_t,spinlock_t,mutex_t,semaphore解决以上产生的问题。
atomic_t 原子操作
spinlock_t 自旋锁
mutex_t 互斥量
semaphore 信号量
*临界区(critical region)
访问要保护的变量的代码段,称为临界区。临界区中同一时间只能一个人进入。
临界区的代码可能分散在不同的函数中。如果要对临界区加锁,则必须保证在临界区的所有部分都加锁。如果有遗漏,则加锁是不可靠的。
*同步(synchronization)
其实就是加锁。同步是保证临界区只进一个人的机制。
*竞争(race condition)
如果有多个人同时进入临界区,就称为竞争。一般来说,认为出现竞争是错误的。
*加锁是自愿的
内核不强迫程序加锁,但一旦使用了锁,驱动设计人员应该保证正确地使用锁,并保证在临界区的每个部分都加上锁
*不要用锁来实现功能
锁就是用来避免对变量同时访问,是一种保护机制。
如果将加锁相关代码去掉,程序功能应该没有任何变化。
*应该在一开始设计代码时,就考虑如何加锁
一定不要代码设计完毕后再加锁,很容易出问题。最好在设计私有结构体时,就开始考虑锁的使用。锁一般会包括到私有结构体中,根据设备的复杂程度,有>可能需要多把锁。
常见死锁的情况:
1:自死锁常见情况
函数A里面加锁,然后调用B函数里面没有加锁,而函数B调用函数C,函数C里面有加锁,然后产生了自死锁。
A(lock(mutex)) --->B() ---->C(lock(mutex))
解决方法:只有借口函数里提供锁,在库里提供非加锁版本被借口函数调用
2:ABBA死锁
A B
lock(a) lock(b)
1:去执行B 这样a就永远加不上锁了
lock(b) lock(a)
解决方法:尝试加锁,失败则放弃之前加的锁
2.linux内核中的哪些机制导致必须要加锁
换句话说,如果没有这些机制,就可以不加锁了。
也就是说,如果当前内核不可能出现“同时”,则不必加锁。
可能导致同时访问变量的内核机制有:
(1)SMP
真同时,必须加锁
(2)中断
上锁关中断,在进程上下文中关中断,可以在中断上下文关中断
(3)内核抢占
2.4内核:内核态不抢占,用户态抢占
2.6内核:内核抢占机制,make menuconfig --> kernel__featch --> preemptible_kernel 开启或者关闭内核抢占
用户程序使用内核抢占则可以提高响应速度
服务器则不需要抢占,抢占反而消耗资源
(4)schedule()
在硬件中断函数里处理进程的调度
需要了解这些机制,才能够正确使用加锁函数。
atomic_t 原子操作:ARM是用汇编实现的,而x86是直接锁总线
ARM:
atomic_t i;
atomic_add(1,&i);
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result;
__asm__ __volatile__("@ atomic_add\n"
"1: ldrex %0, [%2]\n" 将counter放到result中
" add %0, %0, %3\n" 将result和i相加
" strex %1, %0, [%2]\n" 将result的值写入到counter中
" teq %1, #0\n" 然后检测tmp返回值
" bne 1b" 1b,就是back到1的位置,1f则向前跳转到1的位置,1为内部标签
如果是0,则说明没有产生冲突,如果不等于0,则就说明数据有丢弃,则返回1,继续计算
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
}
int i;
atomic_t j;
j = ATOMIC_INIT(1);
i = atomic_read(&j);
atomic_inc(&j); //j++;
atomic_dec(&j); //j --;
atomic_add(1,&j); //j += 1;
atomic_sub(1,&j); //j -= 1;
spinlock_t 自旋锁
spinlock_t,ns秒级,临界区内的代码不能睡眠。
1:SMP 适合使用
2:中断 加锁关中断,解锁开中断,适合使用
3:内核抢占: 加锁自动关闭内核抢占,解锁开启内核抢占,适合使用
4:schedle调度,不适用
如果临界去里要用到kzalloc(100,GFP_KERNEL),copy_to_user等函数,则不能使用spinlock_t
#include
spinlock_t mylock;
spin_lock_init(&mylock);
(1)如果临界区可能被中断处理函数打断,并影响到要保护的变量,则应该在加锁的同时关闭中断
//加锁同时关中断,将CPSR的当前值存储到flags中
//解锁时打开中断,并将flags的值恢复到CPSR中
unsigned long flags;
spin_lock_irqsave(&mylock, flags);
...//临界区
spin_unlock_irqrestore(&mylock, flags);
(2)普通的加解锁
spin_lock(&mylock);
...//临界区
spin_unlock(&mylock);
5. mutex互斥锁
mutex锁也是内核中非常重要的锁,属于重量级的锁,等待锁的时间常常为ms级
持有锁时可以睡眠(必须确保能被唤醒)
1:SMP 适合使用
2:中断 中断中不能睡眠,不适用
3:内核抢占: 不关闭内核抢占,出现程序切换,适用
4:schedle调度,适用
#include
//声明mutex锁
struct mutex mylock;
//用之前要先初始化
mutex_init(&mylock);
//加锁和解锁
mutex_lock(&mylock);
ret = mutex_lock_interruptible(&mylock); //睡眠可以接收到信号
if (ret)
return -ERESTARTSYS;
...//临界区
mutex_try_lock(&mylock); //尝试加锁失败返回
mutex_unlock(&mylock);
mutex_t
定义在中。2.6内核早期没有实现mutex锁,用semaphore来模拟mutex锁的功能。
在现在的内核中,基本上已经不用semaphore来保护临界区。如果内核中某些资源限定了访问人数(比如只允许3个人同时访问),这时候可以用semaphore进行保
护。
sempahore的使用例子如下:
#include
//声明信号量
struct semaphore mysem;
//按照资源的限定初始化信号量
sema_init(&mysem, 3);
down(&mysem);
down_interruptible(&mysem);
...//访问受限资源的代码
up(&mysem);
******make rmpropor 将编译的内核恢复到最初状态
kernel 2.6.19 : .config里面的宏会转成 include/linux/autoconf.h
after : 将在xxx.o.cmd 里面有信息
阅读(2435) | 评论(0) | 转发(0) |