在Linux中提供了一些机制用来避免竞争条件,当一个临界区的数据在多个函数之间被调用时,为了保护数据不被破坏,可以采用一定的机制来保护临界区的数据,主要有自旋锁spinlock、信号量,互斥锁。
第一种:首先说自旋锁spinlock:
在linux中定义spinlock的方法很简单,与普通的结构体定义方式是一样的
spinlock_t spinlock = SPIN_LOCK_UNLOCKED
一个自旋锁必须初始化才能被使用,可以通过在编译阶段通过宏定义来实现,比如上面的SPIN_LOCK_UNLOCKED,这个表示一个没有锁定的自旋锁。
同样在运行阶段可以使用spin_lock_init () 函数动态地初始化一个自旋锁,其函数原型如下:
spinlock_t spin_lock_init(spinlock_t lock);
进入临界区之前,需要使用spin_lock宏定义来锁定自旋锁,spin_lock宏定义的代码如下:#define spin_lock(lock) _spin_lock(lock)
这个宏用来获得lock的自旋锁,如果能够立即获得自旋锁,则宏立刻返回,否则,这个宏一直等待下去,直到被其它线程释放为止。
退出临界区之前,需要使用spin_unlock宏定义来释放自旋锁。spin_unlock宏定义的代码如下:#define spin_unlock(lock) _spin_unlock(lock)
这个宏用来释放lock的自旋锁,当调用该宏后,自旋锁立刻被释放。
在驱动程序中,有些设备只允许打开一次,那么就需要一个自旋锁保护表示设备打开或者关闭的状态的一个变量flag,此处的flag为一个临界资源,如果不对其进行保护,当设备频繁的打开时,就有可能出现错误的flag的状态,所以必须对flag进行保护,其代码如下:
int flag;
spinlock_t spinlock;
int dev_init(void)
{
……
spin_lock_init(&spinlock);
……
}
int dev_open(struct inode *inode, struct file *filp)
{
……
spin_lock(&spinlock);
if(flag)
{
spin_unlock(&spinlock);
return -EBUSY;
}
flag++; //标识为“正在被使用”
spin_unlock(&spinlock);
……
}//end of dev_open()
int dev_release(struct inode *inode, struct file *filp)
{
……
spin_lock(&spinlock);
flag--;
spin_unlock(&spinlock);
……
}
当条件不满足时,spinlock会一直不断的循环判断条件是否满足,如果满足就解锁,运行之后的代码。会影响linux的系统的性能。所以自旋锁不应长时间持有。它适合于短时间的轻量级的加锁机制。
自旋锁不能递归使用,它被设计成在不同进程或者函数之间同步。
第二种:信号量
第三种互斥锁