内核并发处理

随着硬件的发展,SMP(对称多处理器)已经很普遍,如果内核的调度机制是可抢占的,那么SMP和内核抢占是多线程执行的两种场景。当多个线程同时访问内核的数据结构时,我们就需要对其做串行化处理。

自旋锁和互斥体

访问共享资源的代码区称为临界区。自旋锁(spinlock)和互斥体(mutex, mutual exclusion)是保护内核临界区的两种基本机制。

自旋锁可以确定在同时只有一个线程进入临界区。其他想进入临界区域的线程(执行线程)必须不停的在原地打转,知道第一个线程释放的自旋锁。下面是spinlock的用法

#include <linux/spinlock.h>

/*初始化*/
spinlock_t  _lock = SPIN_LOCK_UNLOCKED;

/*上锁*/
spin_lock(&_lock);

/*临界区代码*/


/*释放锁*/
spin_unlock(&_lock);
互斥体与自旋锁不一样,当在临界区外等待时,它不会不停的打转,而是使当前的线程进入睡眠状态。如果执行临界区需要很长的时间,那么互斥体更适合自旋锁,这样可是更好的利用cpu,而不是长时间在哪里打转。

那对于这两个怎么取舍?

1.如果临界区需要睡眠,只能使用互斥体,因为在获得自旋锁后进行调度、抢占以及在等待队列上睡眠都是非法的。

2.由于互斥体会在面临竞争的情况下将当前的线程置于睡眠状态,因此,在中断处理函数中只能使用自旋锁。

下面是互斥体的使用方法

#include <linux/mutex.h>

static DEFINE_MUTEX(_mutex);


mutex_lock(&_mutex);

/*  临界区*/

mutex_unlock(&_mutex)

下面从四个案例中,讨论内核并发保护。

1.进程上下文,单cpu,非抢占式内核

这种是最简单的,不需要加锁

2.进程和中断上下文,单cpu,非抢占式内核

这种情况下,在保护临界区的时候,仅仅只需要对中断禁止。单cpu和非抢占式内核决定进程不会切换,可是在临界区时,进程可能被中断打断,所以需要禁止中断,也就是在进入临界区时保存中断状态,离开临界区后恢复中断状态。

3.进程和中断上下文,单cpu,抢占式内核

这种情况与2相比,内核可抢占,那么就需要对临界区加锁来禁止内核的抢占,中断还是和2一样。

4.进程和中断上下文,SMP,抢占式内核

现在执行在SMP机器上,而且你的内核配置了CONFIG_SMP和CONFIG_PREEMPT。在SMP系统中,获得自旋锁时,仅仅本CPU的中断被禁止,然后进程上下文和中断处理函数可以在不同的CPU上进行,所以非本CPU的中断处理函数需要等待本CPU上的进程上下文代码推出临界区。中断上下文中需要使用spin_lock和spin_unlock。

原子操作

原子操作用于执行轻量级的,仅执行一次的操作,例如修改定时器、有条件的增加值,设置位等。原子操作可是实现操作的串行化,也就不许要加锁处理。原子操作的时间取决了体系结构。

读写锁

自旋锁的变体--读写锁。如果每个执行单元在访问临界区的时候要么是读要么是写数据结构,但是他们都不会同时进行读写操作,那么读写锁很适合。读写锁允许同一时候多个线程进入临界区,但如果一个写线程进入临界区,那么其他的读写进程都必须等待。读写锁使用方法:

rwlock_t _rwlock = RW_LOCK_UNLOCKED;


read_lock(&_rwlock);

/* 临界区代码*/

read_unlock(&_rwlock);


--------------------------------------

rwlock_t _rwlock = RW_LOCK_UNLOCKED;


wirte_lock(&_rwlock);

/* 临界区代码*/

wirte_unlock(&_rwlock);


你可能感兴趣的:(内核并发处理)