内核锁的使用

October 6th, 2014 by JasonLe's Tech Leave a reply »

内核锁有三种形态:

  • 原子操作
  • 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来说是极大的浪费。因此只有当由自旋锁保护的临界区执行时间很短时,使用自旋锁才比较合理。

spinlock.c
sharelist.c