`
音频数据
  • 浏览: 35438 次
文章分类
社区版块
存档分类
最新评论

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

分享到:
评论

相关推荐

    Linux内核中的同步和互斥分析报告

    本文为大家介绍了Linux内核中的同步和互斥分析报告。

    linux内核知识系列:同步与互斥

    linux内核知识系列:同步与互斥 华嵌智能提供 www.embedded-cn.com http://embedded-cn.taobao.com

    project1 同步互斥和Linux内核模块1

    实验指导Linux线程创建和同步的编程参考资料:操作系统原理教材“Operating System Concepts”,第4章的4.3.1节,第6章的“Proj

    同步互斥和 Linux 内核模块之C语言【100012226】

    实验二:编写一个 Linux 的内核模块,其功能是遍历操作系统所有进程。该内核模块输出系统中每个进程的:名字、进程 pid、进程的状态、父进程的名字等;以及统计系统中进程个数,包括统计系统中 TASK_RUNNING、TASK_...

    同步与互斥1

    第一章 同步与互斥1.1 内联汇编要深入理解Linux内核中的同步与互斥的实现,需要先了解一下内联汇编:在C函数中使用汇编代码。现代编译器已经足够优秀,大部分的

    Linux系统内核的同步机制-自旋锁

    自旋锁最多只能被一个可执行线程持有。自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有...由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

    OSlab1 同步互斥和Linux内核模块1

    1.1 实验内容和预备知识 3 1.2 实验设计 5 1.3 程序运行结果 6 1.4 结果分析 8 2.1 实验内容与预备知识 9 2.2 实验总体设计思路

    Linux内核同步机制

    Linux内核同步机制,挺复杂的一个东西,常用的有自旋锁,信号量,互斥体,原子操作,顺序锁,RCU,内存屏障等。这里说说它们的特点和基本用法。  自旋锁 :通用的 和读写的  特点:  1. 处理的时间很短。  2...

    Arm 培训教材-Linux操作系统部分!

    并发控制:互斥与同步 2.2.2.3. 并发控制:死锁处理 2.2.2.4. 中断及中断处理 2.2.2.5. Linux 的进程与中断管理机制 2.2.3. 调度机制 2.2.3.1. 调度类型 2.2.3.2. 单处理器调度 2.2.3.3. 多处理器调度 2.2.3.4. ...

    Linux内核学习起步

    从入门开始,介绍了诸如中断、系统调用、虚拟文件系统、同步与互斥、 内存管理、进程控制等方面,内容比较浅显易懂,是入门的好书

    深入浅出Linux驱动编程

     (3)Linux设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现bug;  (4)由于属于内核的一部分,Linux设备驱动的调试也相当复杂。 深入浅出Linux设备驱动编程之内核模块  Linux设备驱动属于内核的...

    Linux内核源码深度解析与开发实战视频.zip

    13:内核同步_rec 14:第一份作业.txt 15:第一周答疑视频_rec 16:不可睡眠锁:自旋锁spinlock编码示例_rec 17:不可睡眠锁:RCUread-copy-update_rec 18:可睡眠锁-互斥量mutex_rec 19:可睡眠锁:信号量semaphore...

    LINUX高级程序设计(中文第二版)杨宗德 (1)

    本书以linux操作系统(内核为2.6版本)为开发平台、gcc 4.0/gdb 6.3为开发调试环境,详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、...

    LINUX高级程序设计(中文第二版)杨宗德 (2)end

    本书以linux操作系统(内核为2.6版本)为开发平台、gcc 4.0/gdb 6.3为开发调试环境,详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、...

    操作系统课程实验.rar

    Linux 提供的模块机制能动态扩充 linux 功能而无需重新编译内核,已经广泛应用在 linux 内核的许多功能的实现中。在本实验中将学习模块的基本概念、原理及实现技术,然后利 用内核模块编程访问进程的基本信息,从而...

    linux驱动程序设计入门

    (3)Linux 设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现 bug; (4)由于属于内核的一部分,Linux 设备驱动的调试也相当复杂。 目前,市面上的 Linux 设备驱动程序参考书籍非常稀缺,少有的...

    Android驱动开发权威指南

    第4章Linux内核编程与内核模块 4.1 Linux内核源代码目录结构 4.2 Linux内核的编译与启动 4.3 Linux内核的C编程 4.4 Linux内核模块基础与骨架 4.5 Linux模块的加载与卸载 4.6 Linux模块的参数与导出符号 4.7 Linux...

Global site tag (gtag.js) - Google Analytics