Linux 内核同步机制

Linux内核同步机制,挺复杂的一个东西,常用的有自旋锁,信号量,互斥体,原子操作,顺序锁,RCU,内存屏障等。这里就说说它们的特点和基本用法。

自旋锁 :通用的 和读写的

特点:
1. 处理的时间很短。
2. 尝试获取锁时,不能睡眠,但是有trylock接口可以直接退出。
3. 多用在中断中。
4. 任何时候只有一个保持者能够访问临界区。
5. 可以被中断打断的(硬件和软件的)
6. 获取自旋锁后首先就是关闭了抢占

spin_lock使用接口:

Rwlock: 读写自旋锁基本特点和通用自旋锁一样,但是有时候多线程频繁读取临界区如果同时只能一个那么效率会很低,它的特点就是在读的时候获取读锁,可以同时有N个线程同时读,在写时需要获得写锁(不能有读和写锁)。

在读操作时,写操作必须等待;写操作时,读操作也需要的等待。这样虽然避免了数据的不一致,但是某些操作要等待,后面还会出现顺序锁,是对读写锁的优化,把写的优先级调高了

使用接口:

而关于自旋锁的缺点?这里找到ibm一个文章

信号量(semaphore):通用的 和读写的

相对于自旋锁,它最大的特点就是允许调用它的线程进入睡眠

void sema_init(struct semaphore *sem, int val); // val值代表了同时多少个线程可以进入临界区,一般为1 即作为互斥体使用;当然>1 时,并发操作同一资源会引发什么呢?
down_interruptible(struct semaphore *sem); // 获取信号量 ,它是可以中断的。
up(struct semaphore *sem); // 释放信号量,一般配对使用,当然也可以在别的线程里释放它。

读写信号量:rwsem 它和读写自旋锁类似 除了线程可以睡眠

互斥体(mutex):和count=1的信号量几乎没有区别,当然拥有互斥锁的进程总是尽可能的在短时间内释放

原子操作(atomic)(和架构相关,就是多条指令相当于一条指令执行,多用于计数)

组要是在smp上有意义,防止多条指令被多cpu执行。也是为了实现互斥。

顺序锁(sequence)

特点:

和读写自旋锁锁类似,但是它的写不会等待。写的时候持有自旋锁。首先读者的代码应该尽可能短且写者不能频繁获得锁,其次被保护的数据结构不包括被写修改的指针或被读间接引用的指针。当要保护的资源很小很简单,会很频繁被访问并且写入操作很少发生且必须快速时,就可以用seqlock。

RCU:read-copy-update

在linux提供的所有内核互斥设施当中属于一种免锁机制。Rcu无需考虑读和写的互斥问题。

它实际上是rwlock的一种优化。读取者不必关心写入者。所以RCU可以让多个读取者与写入者同时工作。写入者的操作比例在10%以上,需要考虑其他互斥方法。并且必须要以指针的方式来访问被保护资源。

Rcu_read_lock //仅仅是关闭抢占
Rcu_read_unlock //打开抢占
Rcu_assign_pointer(ptr,new_ptr)
//等待队列:它并不是一种互斥机制。它辅助comletion。
//它主要用来实现进程的睡眠等待。
//操作接口:wait/ wake_up

完成接口(completion) :该机制被用来在多个执行路径间作同步使用,即协调多个执行路径的执行顺序。如果没有完成体,则睡眠在wait_list上。这里usb 在提交urb时会用到。

如果驱动程序要在执行后面操作之前等待某个过程的完成,它可以调用wait_for_completion,以要完成的事件为参数:

Completion机制是线程间通信的一种轻量级机制:允许一个线程告诉另一个线程工作已经完成

内存屏障

内存屏障主要有:读屏障、写屏障、通用屏障、优化屏障

内存屏障主要解决了两个问题:单处理器下的乱序问题和多处理器下的内存同步问题

编译器优化以保证程序上下文因果关系为前提。

以 读屏障为例,它用于保证读操作有序。屏障之前的读操作一定会先于屏障之后的读操作完成,写操作不受影响,同属于屏障的某一侧的读操作也不受影响。类似的, 写屏障用于限制写操作。而通用屏障则对读写操作都有作用。而优化屏障则用于限制编译器的指令重排,不区分读写。前三种屏障都隐含了优化屏障的功能。比如:
tmp = ttt; *addr = 5; mb(); val = *data;

有了内存屏障就了确保先设置地址端口,再读数据端口。而至于设置地址端口与tmp的赋值孰先孰后,屏障则不做干预。有了内存屏障,就可以在隐式因果关系的场景中,保证因果关系逻辑正确。

在Linux中,优化屏障就是barrier()宏,它展开为asm volatile(“”:::”memory”)
smp_rmb(); // 读屏障
smp_wmb(); //写屏障
smp_mb(); // 通用屏障

Blk:大内核锁

BKL(大内核锁)是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过度到细粒度加锁机制。它终将退出历史舞台。

BKL的特性:
持有BKL的任务仍然可以睡眠 。因为当任务无法调度时,所加的锁会自动被抛弃;当任务被调度时,锁又会被重新获得。当然,并不是说,当任务持有BKL时,睡眠是安全的,紧急是可以这样做,因为睡眠不会造成任务死锁。

BKL是一种递归锁。一个进程可以多次请求一个锁,并不会像自旋锁那么产生死锁。BKL可以在进程上下文中。

BKL是有害的:
在内核中不鼓励使用BKL。一个执行线程可以递归的请求锁lock_kernel(),但是释放锁时也必须调用同样次数的unlock_kernel()操作,在最后一个解锁操作完成之后,锁才会被释放。BKL在被持有时同样会禁止内核抢占。多数情况下,BKL更像是保护代码而不是保护数据.

备注:单核不可抢占内核 唯一的异步事件就是硬件中断 ,所以想要同步即关闭中断即可。对于单核可抢占和多核可抢占的 ,除了中断 还有进程调度(即优先级高的进程抢占cpu资源),而上述所有这些机制都是为了防止并发。

参考书籍《linux内核设计与实现》 ,《深入linux设备驱动内核机制》等。
参考代码 linux3.18.3

1 5 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部