Linux 内核的并发和竞态

1. 并发的原因

并发可能会导致竞态(race condition),竞态会导致对共享资源的非控制访问, 产生非预期的结果。Linux的并发来源于下面几个方面:

  1. 正在运行的多个用户进程以无法预知的方式访问驱动程序代码

  2. 外部设备的中断异步的发生,导致正在运行的进程或者驱动代码被中断

  3. linux的软件抽象(如timer, tasklet, workqueue)也在异步运行着

  4. 现在SMP的处理器架构,导致驱动程序可能会在不同的CPU上运行

  5. 可抢占的内核调度算法,可能导致驱动代码随时被抢占

因此在内核中,并发不可避免。竞态是因为对资源的共享访问造成的,因此设计程序时,应该尽量避免资源的共享,避免申明全局变量。没有共享访问,就没有竞态的发生。当共享资源不可避免时,必须要保证对共享资源的互斥访问,共享资源在访问期间如果能保证一次只有一个执行者访问的话,也没有竞态的发生。

2. Linux互斥机制

2.1 信号量和互斥体

在操作系统概念中,一个信号量本质上是一个整数值,代表系统中可用资源的数量,它和一对函数联合使用,P和V。在希望进入临界区时,在信号量上执行P操作,如果信号量的值大于0,则该值会减1,进程获得信号量继续执行。当信号量的值等于0(或者更小),进程必须等待直到其他进程释放信号量。当进程准备退出临界区时,对信号量的执行V操作,增加信号量的值,如果有进程正在等待该信号量的话,唤醒其中一个来执行。

当信号量用于互斥时,信号量的值应该初始化为1,这样的信号量也叫互斥体。Linux内核中几乎所有的信号量均用于互斥。

信号量可用在可能会休眠的上下文。

semaphore结构的定义在include/linux/semaphore.h文件,从注释可以知道,外部代码不能直接访问semaphore结构内部的成员,只能通过API来访问,semaphore内部的结构本身是由spinlock保护的。

/* Please don't access any members of this structure directly */
struct semaphore {
    spinlock_t      lock;
    unsigned int        count;
    struct list_head    wait_list;
};

静态定义semaphore变量的宏:

#define DEFINE_SEMAPHORE(name)  \
    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

#define DECLARE_MUTEX(name) \
    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

初始化semaphore变量:

static inline void sema_init(struct semaphore *sem, int val)
#define init_MUTEX(sem)     sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem)  sema_init(sem, 0)

P操作:

/**
 * down - acquire the semaphore
 * @sem: the semaphore to be acquired
 *
 * Acquires the semaphore.  If no more tasks are allowed to acquire the
 * semaphore, calling this function will put the task to sleep until the
 * semaphore is released.
 *
 * Use of this function is deprecated, please use down_interruptible() or
 * down_killable() instead.
 */
void down(struct semaphore *sem);


/**
 * down函数的可中断版本
 * 如果该函数被signal中断的话,返回-EINTR.
 * 成功申请到semaphore的话,返回0
 */
int __must_check down_interruptible(struct semaphore *sem);


/**
 * down函数的可中断版本
 * 如果该函数被fatal signal中断的话,返回-EINTR.
 * 成功申请到semaphore的话,返回0。
 */
int __must_check down_killable(struct semaphore *sem);


/**
 * down函数的非阻塞版本
 * 如果该函数成功申请到semaphore的话,返回0。
 * 否则的话,该函数立即返回1,不会阻塞调用。
 */ 
int __must_check down_trylock(struct semaphore *sem);


/**
 * 如果该函数在指定的时间内未申请到semaphore的话,返回-ETIME.
 * 成功申请到semaphore的话,返回0。
 */
int __must_check down_timeout(struct semaphore *sem, long jiffies);

V操作:

/**
 * 释放指定的sem
 */
void up(struct semaphore *sem);

2.2 读写信号量rw_semaphore

信号量对所有的调用者都执行互斥访问。rw_semaphore允许一个writer或者无限多个reader拥有该信号量。当writer试图进入临界区时,在所有writer完成其工作之前,不会允许其他writer/reader获得访问。当reader试图进入临界区时,只有reader访问者或者无访问者信号量,该reader才能访问。也就是说writer比reader具有更高的优先级。当系统中有大量的writer竞争该rw_semaphore时,会导致reader长时间无法获得访问。所以rwsem只有在很少需要写访问且写访问的时间非常短,大部分是读访问的情况下才适用。

静态定义rw_semaphore变量的宏:

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

初始化rw_semaphore变量:

init_rwsem(sem) 

reader申请和释放rw_semaphore

/*
 * lock for reading
 */
extern void down_read(struct rw_semaphore *sem);


/*
 * trylock for reading -- returns 1 if successful, 0 if contention
 */
extern int down_read_trylock(struct rw_semaphore *sem);


/*
 * release a read lock
 */
extern void up_read(struct rw_semaphore *sem);

writer申请和释放rw_semaphore

/*
 * lock for writing
 */
extern void down_write(struct rw_semaphore *sem);


/*
 * trylock for writing -- returns 1 if successful, 0 if contention
 */
extern int down_write_trylock(struct rw_semaphore *sem);


/*
 * release a write lock
 */
extern void up_write(struct rw_semaphore *sem);


/*
 * downgrade write lock to read lock
 */
extern void downgrade_write(struct rw_semaphore *sem);

2.3 自旋锁

在概念上,一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁。它通常实现为某个整数值中的某个位。希望获得某特定锁的代码测试相关的位。如果锁可用,则锁定位被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止。

使用自旋锁的几个规则:
1. 任何拥有自旋锁的代码都必须是原子的。它不能休眠。
2. 在任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止
3. 如果中断处理程序也要申请自旋锁,那么必须禁止本地CPU上的中断,自旋锁提供了禁止中断的自旋锁函数
4. 拥有自旋锁的代码必须在最短的时间内执行完,然后释放锁

自旋锁的类型是spinlock_t,定义在include/linux/spinlock_types.h。其中,include/linux/spinlock.h定义了自旋锁常用的API.

自旋锁的定义和初始化:

#define DEFINE_SPINLOCK(x)      spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

#define spin_lock_init(_lock)               \
do {                            \
    spinlock_check(_lock);              \
    raw_spin_lock_init(&(_lock)->rlock);        \
} while (0)

自旋锁的锁定

/* 一旦调用该函数,在获得锁之前将一直处于自旋状态,所有的自旋锁等待在本质上都是不可中断的。 */
static inline void spin_lock(spinlock_t *lock)


/*在获得锁之前,禁止本地CPU上的中断,适合在中断处理程序中需要申请自旋锁的情况下使用*/
static inline void spin_lock_irq(spinlock_t *lock)

/*在获得锁之前,禁止本地CPU上的中断,同时保存中断标志,使用配对的unlock函数恢复中断标志,适合在中断处理程序中需要申请自旋锁的情况下使用*/
#define spin_lock_irqsave(lock, flags)


/*在获得锁之前,禁止软件中断,但是会让硬件中断保持打开,适合在软件中断上下文使用,如tasklet */
static inline void spin_lock_bh(spinlock_t *lock)

自旋锁的非阻塞锁定

/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock(spinlock_t *lock)


/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock_irq(spinlock_t *lock)
#define spin_trylock_irqsave(lock, flags)


/*在成功获得自旋锁时返回非零值,否则返回0*/
static inline int spin_trylock_bh(spinlock_t *lock)

自旋锁的解锁

static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)


/*一直等待直到自旋锁unlock*/
static inline void spin_unlock_wait(spinlock_t *lock)

猜你喜欢

转载自blog.csdn.net/rex_nie/article/details/73250907
今日推荐