Linux内核中的互斥与同步机制

共有这么几大类
1.自旋锁
2.信号量
3.互斥锁
4.RCU
5.原子变量
6.完成量

文章列举了各个互斥机制所要用的api以及在什么情况下用哪种互斥,并未对内核中的互斥和同步机制详细分析,
只供今后写代码时查阅,如果想了解详细机制可参考LKD或<<深入Linux设备驱动程序内核机制>>等书.

自旋锁
spin_lock/spin_unlock
因为只禁止抢占,并未对中断做处理,所以不能在中断上下文用,所以便有了以下变体

spin_lock_irq/spin_unlock_irq
禁止抢占,禁止中断,可以在中断上下文用

spin_lock_irqsave/spin_unlock_irqrestore
禁止抢占,禁止中断的同时保存中断前处理器FLAGS寄存器的状态,在ARM上是保存CPSR寄存器

spin_lock_bh/spin_unlock_bh
相对于spin_lock_irq来说,spin_lock_bh关闭的是softirq

非阻塞
spin_trylock
spin_trylock_irq
spin_trylock_irqsave
spin_trylock_bh

读写者自旋锁
如果系统中有大量对共享资源的读操作,但并不会改写其内容,那么用spin_lock就会大大降低系统性能
所以便有了读写者自旋锁rwlock.唯一与自旋锁不同的是可以允许多个读者同时访问,如果有写操作参与那么得互斥

读取者
read_lock/read_unlock
read_lock_irq/read_unlock_irq
read_lock_irqsave/read_unlock_irqrestore

写入者   
write_lock/write_unlock
write_lock_irq/write_unlock_irq
write_lock_irqsave/write_unlock_irqrestore

顺序锁
typedef struct {
   unsigned sequence;
   spinlock_t lock;
} seqlock_t;

顺序锁seqlock的设计思想是写加锁,读不加
为了保证读取数据的过程中不会由写入者参与,便设置了一个sequence值,读取者在开始读取前读取该值,
读取操作完成后再读取该值,看两值是否一致,如果不一致,说明数据被更新,读取操作无效.因此写入者在
开始写入时要更新sequence值

同样,静态初始化
#define DEFINE_SEQLOCK(x) \
       seqlock_t x = __SEQLOCK_UNLOCKED(x)

动态
seqlock_init

例子
//定义一个顺序锁变量demo_seqlock
DEFINE_SEQLOCK(demo_seqlock)

//写入者代码
write_seqlock(&demo_seqlock);    //实际写之前调用write_seqlock获取自旋锁,同时更新sequence的值
do_write();            //实际的写入操作
write_unseqlock(&demo_seqlock);    //写入结束,释放自旋锁

//读取者代码
unsigned start;
do {
   //读取操作前先得到sequence的值赋给start,用以在读操作结束后判断是否发生更新
   //注意读操作无需获取锁,但是如果有写操作在进行那么会一直循环读取sequence的值,直到写操作结束
   //read_seqbegin是通过判断sequence的最低位实现的,写操作完成那么sequence&0返回0,否则返回1
   start = read_seqbegin(&demo_seqlock);

   do_read();    //实际的读操作

} while (read_seqretry(&demo_seqlock, start));    //如果有数据更新,再重新读取

如果考虑到中断安全问题,可以用
write_seqlock_irq/write_sequnlock_irq
write_seqlock_irqsave/write_sequnlock_irqrestore
write_seqlock_bh/write_sequnlock_bh

read_seqbegin_irqsave
read_seqretry_irqrestore

顺序锁seqlock和之前的读写者自旋锁rwlock其实是相同的,者是读写互斥,写写互斥,读读不互斥

信号量
struct semaphore {
raw_spinlock_t          lock;
unsigned int            count;
struct list_head        wait_list;
};
相对于自旋锁来讲,信号量最大的特点就是允许调用它的线程睡眠
定义信号量    struct semaphore
初始化信号量    sema_init(struct semaphore *sem, int val)

信号量主要是DOWN/UP操作,DOWN操作有,不过驱动使用最频繁的是down_interruptible
down
down_interruptible
down_killable
down_trylock
down_timeout

UP操作只有一个
up

即使不是信号量的拥有者也可以调用up函数来释放一个信号量,这一点是与mutex不同的

其实信号量常见的用途就是实现互斥机制,也就是信号量的count为1,也就是任意时刻只允许一个进程进入临界区,用法是
#define DECLARE_MUTEX(name)

以免与mutex产生混淆,Thomas Gleixner在2010年9月7号改为了
#define DEFINE_SEMAPHORE(name)  \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

所以现在我们用DEFINE_SEMAPHORE

读写者信号量
与读写自旋锁一个意思,者是为了提高系统性能
/*
* the rw-semaphore definition
* - if activity is 0 then there are no active readers or writers
* - if activity is +ve then that is the number of active readers
* - if activity is -1 then there is one active writer
* - if wait_list is not empty, then there are processes waiting for the semaphore
*/
struct rw_semaphore {
   __s32                   activity;
   raw_spinlock_t          wait_lock;
   struct list_head        wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
   struct lockdep_map dep_map;
#endif
};

定义
#define DECLARE_RWSEM(name) \
   struct rw_semaphore name = __RWSEM_INITIALIZER(name)

初始化
init_rwsem

DOWN操作
down_read
down_read_trylock
down_write
down_write_trylock

UP操作
up_read
up_write

互斥锁
用信号量实现互斥不是Linux中最经典的用法,于是便有了mutex.
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t        count;
spinlock_t        wait_lock;
struct list_head    wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct    *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char         *name;
void            *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map    dep_map;
#endif
};

定义并初始化,静态
#define DEFINE_MUTEX(mutexname) \
struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

动态
struct mutex
mutex_init

DOWN/UP操作,得不到锁就去等待队列睡眠
mutex_lock/mutex_unlock

RCU
Read-Copy_Update,即读/写-复制-更新.Linux提供了很多互斥机制,RCU与其他不同的是它是免锁的.
RCU的应用场景也是读取者/写入者,不同的是RCU不用考虑读/写的互斥问题.

简单原理是,将读取者和写入者要访问的共享数据放在指针p指向的区域,读取者通过p来访问数据,而写入者通过
修改这个区域来更新数据.在具体实现上读取者没有太多的事要做,大量的工作都在写入者一方.免锁的实现双方
必须同时遵守一定的规则.

读取者所做的工作是
禁止抢占,对p指针的引用只能在临界区中

写入者做做的工作是
1.分配新的空间ptr
2.更新数据
3.用新指针ptr替换老指针p
4.调用call_rcu释放老指针所指向的空间

注意第4步释放空间时必须确保没有读取者对老指针的引用,内核是通过判断处理器是否发生进程切换来实现的.因为
读取者是禁止抢占的,所以在临界区不会发生进程切换(就单核而言),如果发生进程切换那么就说明不在临界区了

例子
//假设struct shared_data是读者和写者要共同访问的共享数据
struct shared_data {
int a;
int b;
struct rcu_head rcu;
};

//读取者代码
//读取者调用rcu_read_lock/rcu_read_unlock构建它的读取临界区,所有对指向被保护资源指针的引用都应该只出现在临界区中,
//而且临界区中的代码不能睡眠
static void demo_reader(struct shared_data *ptr)
{
struct shared_data *p = NULL;

rcu_read_lock();

p = rcu_dereference(ptr);    //调用rcu_dereference获得指向共享数据的指针
if (p)
   do_something...

rcu_read_unlock();
}

//写入者代码
//写入者提供的回调函数,用于释放老指针
static void demo_del_oldptr(struct rcu_head *rh)
{
struct shared_data *p = container_of(rh, struct shared_data, rcu);

kfree(p);
}

static void demo_writer(struct shared_data *ptr)
{
struct shared_data *new_ptr = kmalloc(...);
...
new_ptr->a = 30;
new_ptr->b = 40;

rcu_assign_pointer(ptr, new_ptr);    //用新指针更新老指针

call_rcu(ptr->rcu, demo_del_oldptr);    //调用call_rcu让内核在确保所有对老指针ptr的引用都结束后回调demo_del_oldptr释放老指针所指向的区域
}

和call_rcu类似的还有一个synchronize_rcu,不过后者会阻塞,它会等待所有对老指针的引用都消失后才执行,所以在中断上下文要用call_rcu

原子变量
如果需要保护的数据只是一个简单的整型变量,那么可以用原子变量

typedef struct {
int counter;
} atomic_t;

例子
atomic_t flag = ATOMIC_INIT(0);

//Task A
void add_flag()
{
atomic_inc(&flag);
}

//Task B
void add_flag()
{
atomic_inc(&flag);
}

完成量
struct completion {
unsigned int done;
wait_queue_head_t wait;
};

静态初始化
#define DECLARE_COMPLETION(work) \
struct completion work = COMPLETION_INITIALIZER(work)

动态
init_completion

等待完成所调用的API
wait_for_completion
wait_for_completion_interruptible
wait_for_completion_timeout
wait_for_completion_interruptible_timeout

完成
complete
complete_all

猜你喜欢

转载自q16964777.iteye.com/blog/2229039