内核锁有三种形态:
- 原子操作
- spinlock
- semaphore
1.原子结构
typedef struct { int counter; } atomic_t;
不能直接赋值,需要使用函数比如ATOMIC_INIT()等初始化等函数(arch/alpha/include/asm/atomic.h)
初始化宏的源码很明显的说明了如何初始化一个原子变量。我们在定义一个原子变量时可以这样的使用它:
atomic_t v=ATOMIC_INIT(0);
atomic_set函数可以原子的设置一个变量的值。
2.获取原子变量的值
23static inline int atomic_read(const atomic_t *v) 24{ 25 return (*(volatile int *)&(v)->counter); 26}
返回原子型变量v中的counter值。关键字volatile保证&(v->counter)的值固定不变,以确保这个函数每次读入的都是原始地址中的值。
3.原子变量的加与减
47static inline void atomic_add(int i, atomic_t *v) 48{ 49 asm volatile(LOCK_PREFIX "addl %1,%0" 50 : "+m" (v->counter) 51 : "ir" (i)); 52} 61static inline void atomic_sub(int i, atomic_t *v) 62{ 63 asm volatile(LOCK_PREFIX "subl %1,%0" 64 : "+m" (v->counter) 65 : "ir" (i)); 66}
加减操作中使用了内联汇编语句。linux中的汇编语句都采用AT&T指令格式。
2.自旋锁spinlock(include/linux/spinlock_types.h)
typedef struct spinlock { union { struct raw_spinlock rlock; #ifdef CONFIG_DEBUG_LOCK_ALLOC # define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map)) struct { u8 __padding[LOCK_PADSIZE]; struct lockdep_map dep_map; }; #endif }; } spinlock_t;
自旋锁的使用:
1.定义初始化自旋锁
使用下面的语句就可以先定一个自旋锁变量,再对其进行初始化:
spinlock_t lock; spin_lock_init(&lock);
2.获得自旋锁
void spin_lock(spinlock_t*); int spin_trylock(spinlock_t*);
使用spin_lock(&lock)这样的语句来获得自旋锁。如果一个线程可以获得自旋锁,它将马上返回;否则,它将自旋至自旋锁的保持者释放锁。
另外可以使用spin_trylock(&lock)来试图获得自旋锁。如果一个线程可以立即获得自旋锁,则返回真;否则,返回假,此时它并不自旋。
3.释放自旋锁
void spin_unlock(spinlock_t*);
使用spin_unlock(&lock)来释放一个已持有的自旋锁。注意这个函数必须和spin_lock或spin_trylock函数配套使用。
3.信号量semaphore(include/linux/semaphore.h)
struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; }; #define __SEMAPHORE_INITIALIZER(name, n) \ { \ .lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \ .count = n, \ .wait_list = LIST_HEAD_INIT((name).wait_list), \ } #define DEFINE_SEMAPHORE(name) \ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1) static inline void sema_init(struct semaphore *sem, int val) { static struct lock_class_key __key; *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val); lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0); } extern void down(struct semaphore *sem); extern int __must_check down_interruptible(struct semaphore *sem); extern int __must_check down_killable(struct semaphore *sem); extern int __must_check down_trylock(struct semaphore *sem); extern int __must_check down_timeout(struct semaphore *sem, long jiffies); extern void up(struct semaphore *sem);
信号量(semaphore)是保护临界区的一种常用方法。它的功能与自旋锁相同,只能在得到信号量的进程才能执行临界区的代码。但是和自旋锁不同的是,一个进程不能获得信号量时,它会进入睡眠状态而不是自旋。
semaphore的使用
1.定义初始化信号量
使用下面的代码可以定义并初始化信号量sem:
struct semaphore sem;
sema_init(&sem,val);
其中val即为信号量的初始值。
除上面的方法,可以使用下面的两个宏定义并初始化信号量:
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
其中name为变量名。
2.获得信号量
down(&sem);
进程使用该函数时,如果信号量值此时为0,则该进车会进入睡眠状态,因此该函数不能用于中断上下文中。
down_interruptibale(&sem);
该函数与down函数功能类似,只不过使用down而睡眠的进程不能被信号所打断,而使用down_interruptibale的函数则可以被信号打断。
如果想要在中断上下文使用信号量,则可以使用下面的函数:
dwon_try(&sem);
使用该函数时,如果进程可以获得信号量,则返回0;否则返回非0值,不会导致睡眠。
3.释放信号量
up(&sem);
该函数会释放信号量,如果此时等待队列中有进程,则唤醒一个。
三种比较常用的互斥方式
需求 | 建议加锁的方法 |
低开销加锁 | 优先使用自旋锁 |
短期加锁 | 优先使用自旋锁 |
长期加锁 | 优先使用信号量 |
中断上下文加锁 | 使用自旋锁 |
持有锁时需要睡眠 | 使用信号量 |
自旋锁是忙等锁。当其他线程持有自旋锁时,如果另有线程想获得该锁,那么就只能循环的等待。这样忙的功能对CPU来说是极大的浪费。因此只有当由自旋锁保护的临界区执行时间很短时,使用自旋锁才比较合理。