内核线程同步之信号量、互斥量、自旋锁、原子量

本次主要是学习线程同步时保护数据的锁机制,对于多个线程同时对一个变量进行赋值操作时,会造成该变量的赋值不确定,出现了竞争状态,这时需要有个锁来保护下,即加个锁,同一时间只能有一个线程对其操作,当锁释放了,另一线程才能对其操作,而处在加锁和解锁之间的区域叫做临界区,对临界区的操作要保证其原子性,即一次性搞定,不能被中断,而内核也提供了原子量,用于轻量级操作。
接下来说说内核里相关的内容吧:
1.原子量
1).结构体
在include/linux/types.h文件中有如下结构体(分别对应32位和64位系统):
typedef struct {
        int counter;
} atomic_t;

#ifdef CONFIG_64BIT
typedef struct {
        long counter;
} atomic64_t;
#endif
2).整型原子操作
在include/asm-generic/atomic.h文件中有如下操作宏和函数定义:
a.创建及初始化
#define ATOMIC_INIT(i) { (i) }
#define atomic_set(v, i) (((v)->counter) = (i))
b.读取原子量
#define atomic_read(v) (*(volatile int *)&(v)->counter)
c.原子量常用运算(加、减、自加一、自减一)
static inline void atomic_add(int i, atomic_t *v) ;
static inline void atomic_sub(int i, atomic_t *v) ;
static inline void atomic_inc(atomic_t *v) ;
static inline void atomic_dec(atomic_t *v) ;
d.原子量运算并检测运算后是否等于0
#define atomic_sub_and_test(i, v) (atomic_sub_return((i), (v)) == 0)
#define atomic_dec_and_test(v) (atomic_dec_return(v) == 0)
#define atomic_inc_and_test(v) (atomic_inc_return(v) == 0)
3).位掩码原子操作 在上面同一文件里有如下函数:
static inline void atomic_clear_mask(unsigned long mask, atomic_t *v) ;
static inline void atomic_set_mask(unsigned int mask, atomic_t *v) ;
分别用于清除和设置指定位,即对mask对应为1的位进行清0和置1操作。
2.自旋锁
对于自旋锁,当其加锁时得不到锁,其会忙等,一直等到加锁成功后才执行后面的代码,期间不会睡眼。
1).结构体
在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;
2).创建及初始化
在include/linux/spinlock_types.h文件中有如下宏:
#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x)
在include/linux/spinlock.h文件中有如下宏:
#define spin_lock_init(_lock) \
do { \
        spinlock_check(_lock); \
        raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
3).加锁与解锁
在include/linux/spinlock.h文件中有如下内容:
a.加锁
static inline void spin_lock(spinlock_t *lock) ;
static inline int spin_trylock(spinlock_t *lock) ;
#define spin_lock_irqsave(lock, flags) \
do { \
        raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
b.解锁
static inline void spin_unlock(spinlock_t *lock) ;
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) ;
其中,spin_lock加锁时获取不到锁会一直等待,spin_trylock则获取不到时立即返回,而irqsave则会保存中断时的标志并且禁止中断,unlock对应前两种加锁,irqrestore对应最后一种,其会不愿中断标志并且开中断,还有其他函数请查阅头文件。
3.信号量
信号量与自旋锁一个明显区别是其会睡眼,互斥量是基于信号量机制产生的,当信号量的初始值为1的情况下就是互斥信号量,简称互斥量,而大于1时则是计数信号量。而信号量与互斥量也有自己的结构和操作。
1).结构体
在include/linux/semaphore.h文件中有如下结构体:
struct semaphore {
       raw_spinlock_t lock;
       unsigned int count;
       struct list_head wait_list;
};
2).创建及初始化
在include/linux/semaphore.h文件中有如下宏和函数:
#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);
}
3).操作
在include/linux/semaphore.h文件有如下相关操作函数头:
a.获取信号量
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);
b.释放信号量
extern void up(struct semaphore *sem);
4.互斥量
1).结构体
在include/linux/mutex.h文件中有如下结构体:
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_MUTEX_SPIN_ON_OWNER
        void *spin_mlock; /* Spinner MCS lock */
        #endif
        #ifdef CONFIG_DEBUG_MUTEXES
        const char *name;
        void *magic;
        #endif
        #ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map dep_map;
        #endif
};
2).创建及初始化
在include/linux/mutex.h文件中有如下宏定义和函数头:
#define DEFINE_MUTEX(mutexname) \
        struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

extern void __mutex_init(struct mutex *lock, const char *name,
        struct lock_class_key *key);
3).操作
在include/linux/mutex.h文件中有如下内容:
a.检测互斥锁状态
static inline int mutex_is_locked(struct mutex *lock)
{
        return atomic_read(&lock->count) != 1;
}
b.加锁
#define mutex_lock(lock) mutex_lock_nested(lock, 0)
#define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)
#define mutex_lock_killable(lock) mutex_lock_killable_nested(lock, 0)
extern int mutex_trylock(struct mutex *lock);
c.解锁
extern void mutex_unlock(struct mutex *lock);
关于其他相关操作在头文件中有更多定义和声明,可查阅相关头文件及C文件。
下面仍使用双线程来演示上面几种同步数据机制的使用过程:
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <linux/spinlock_types.h>
#include <linux/semaphore.h>
#include <linux/mutex.h>

static struct task_struct * slam1, * slam2;
static atomic_t slam_atomic;

static int slam_spinlock_value = 0;
DEFINE_SPINLOCK(slam_spinlock);

static int slam_semaphore_value = 1;
static struct semaphore slam_semaphore;

static int slam_mutex_value = 2;
DEFINE_MUTEX(slam_mutex);

extern unsigned long msleep_interruptible(unsigned int msecs);

static int slam1_func(void *data)
{
        do{
                printk("slam_atomic.counter=%d\n", atomic_read(&slam_atomic));

                spin_lock(&slam_spinlock);
                printk("slam1-1 slam_spinlock_value=%d\n", slam_spinlock_value);
                slam_spinlock_value=1;
                printk("slam1-2 slam_spinlock_value=%d\n", slam_spinlock_value);
                spin_unlock(&slam_spinlock);

                down(&slam_semaphore);
                printk("slam1-1 slam_semaphore_value=%d\n", slam_semaphore_value);
                slam_semaphore_value=1;
                printk("slam1-2 slam_semaphore_value=%d\n", slam_semaphore_value);
                up(&slam_semaphore);

                mutex_lock(&slam_mutex);
                printk("slam1-1 slam_mutex_value=%d\n",slam_mutex_value);
                slam_mutex_value=1;
                printk("slam1-2 slam_mutex_value=%d\n",slam_mutex_value);
                mutex_unlock(&slam_mutex);

                msleep_interruptible(500);
        }while(!kthread_should_stop());

        return 0;
}

static int slam2_func(void *data)
{
        do{
                atomic_inc(&slam_atomic);

                spin_lock(&slam_spinlock);
                printk("slam2-1 slam_spinlock_value=%d\n", slam_spinlock_value);
                slam_spinlock_value=2;
                printk("slam2-2 slam_spinlock_value=%d\n", slam_spinlock_value);
                spin_unlock(&slam_spinlock);

                down(&slam_semaphore);
                printk("slam2-1 slam_semaphore_value=%d\n", slam_semaphore_value);
                slam_semaphore_value=2;
                printk("slam2-2 slam_semaphore_value=%d\n", slam_semaphore_value);
                up(&slam_semaphore);

                mutex_lock(&slam_mutex);
                printk("slam2-1 slam_mutex_value=%d\n",slam_mutex_value);
                slam_mutex_value=2;
                printk("slam2-2 slam_mutex_value=%d\n",slam_mutex_value);
                mutex_unlock(&slam_mutex);

                msleep_interruptible(500);
        }while(!kthread_should_stop());

        return 0;
}

static __init int kthread_lock_init(void)
{
        atomic_set(&slam_atomic, 2);
        sema_init(&slam_semaphore, 2);

        slam1 = kthread_run(slam1_func, NULL, "slam1");
        if(IS_ERR(slam1))
        {
                printk("kthread_run slam1 failed!\n");
                return 1;
        }

        slam2 = kthread_run(slam2_func, NULL, "slam2");
        if(IS_ERR(slam2))
        {
                printk("kthread_run slam2 failed!\n");
                return 1;
        }

        return 0;
}

static __exit void kthread_lock_exit(void)
{
        if(!IS_ERR(slam1))
                kthread_stop(slam1);
        if(!IS_ERR(slam2))
                kthread_stop(slam2);
}

module_init(kthread_lock_init);
module_exit(kthread_lock_exit);
相应的Makefile文件内容如下:
obj-m += kthread_lock_example.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
对应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/kthread_lock_example/
├── kthread_lock_example.c
└── Makefile
现在就可以编译加载模块看看dmesg吧,还可以在源码的锁与锁之间加延时函数或对调锁之间的顺序再对比其执行步骤,但前提是加锁与解锁要配对,嵌套锁可尝试,但不建议过早玩嵌套,可先了解操作系统原理后再来玩。
参考网址:
http://edsionte.com/techblog/archives/1809
http://www.makelinux.net/books/lkd2/ch09lev1sec1
http://uliweb.clkg.org/tutorial/view_chapter/87
http://edsionte.com/techblog/archives/1820
http://www.ibm.com/developerworks/cn/linux/l-cn-spinlock/index.html
http://www.ibm.com/developerworks/cn/linux/l-linux-synchronization.html
http://maoxingbing.blog.163.com/blog/static/564665272013411104847515/
http://www.cnblogs.com/yezhaohui/p/3642507.html#top
http://feisky.42qu.com/14490879
http://oss.org.cn/kernel-book/ldd3/ch05s08.html
http://www.linuxjournal.com/article/5833?page=0,0
http://staff.ustc.edu.cn/~james/em2005/5.pdf

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file