并发原因
中断:中断可以在任何时候发生,因此可以随时打断当前正在执行的代码 软中断和tasklet:内核能在任何时刻唤醒或调度软中断和tasklet,打断正在运行的代码 内核抢占:内核的任务可能被另一任务抢占 睡眠与用户空间同步:在内核执行的进程可能会睡眠,从而导致调度一个新的用户进程执行 对称多处理:两个或多个处理器可以同时执行代码
内核同步方法
原子操作
原子整数操作
#include <linux/types.h>
typedef struct {
volatile int counter;
} atomic_t;
#include <asm/atomic.h>
ATOMIC_INIT ( int i) ;
int atomic_read ( atomic_t * v) ;
void atomic_set ( atomic_t * v, int i) ;
void atomic_add ( int i, atomic_t * v) ;
void atomic_sub ( int i, atomic_t * v) ;
void atomic_inc ( atomic_t * v) ;
void atomic_dec ( atomic_t * v) ;
int atomic_sub_and_test ( int i, atomic_t * v) ;
int atomic_add_negative ( int i, atomic_t * v) ;
int atomic_add_return ( int i, atomic_t * v) ;
int atomic_inc_return ( atomic_t * v) ;
int atomic_sub_return ( int i, atomic_t * v) ;
int atomic_dec_return ( atomic_t * v) ;
int atomic_dec_and_test ( atomic_t * v) ;
int atomic_inc_and_test ( atomic_t * v) ;
64位原子操作
typedef struct {
volatile long counter;
} atomic64_t;
ATOMIC64_INIT ( int i) ;
int atomic64_read ( atomic64_t * v) ;
void atomic64_set ( atomic64_t * v, int i) ;
void atomic64_add ( int i, atomic64_t * v) ;
void atomic64_sub ( int i, atomic64_t * v) ;
void atomic64_inc ( atomic64_t * v) ;
void atomic64_dec ( atomic64_t * v) ;
int atomic64_sub_and_test ( int i, atomic64_t * v) ;
int atomic64_add_negative ( int i, atomic64_t * v) ;
int atomic64_add_return ( int i, atomic64_t * v) ;
int atomic64_inc_return ( atomic64_t * v) ;
int atomic64_sub_return ( int i, atomic64_t * v) ;
int atomic64_dec_return ( atomic64_t * v) ;
int atomic64_dec_and_test ( atomic64_t * v) ;
int atomic64_inc_and_test ( atomic64_t * v) ;
原子位操作
位操作函数是对普通内存地址进行操作的,参数是一个指针和一个位号 原子位操作多数情况下是对一个字长的内存进行访问,因而位号应该位于0~31(64位机器是0~43),但是对位号的范围并没有限制
#include <asm/bitops.h>
void set_bit ( int nr, void * addr) ;
void clear_bit ( int nr, void * addr) ;
void change_bit ( int nr, void * addr) ;
int test_and_set_bit ( int nr, void * addr) ;
int test_and_clear_bit ( int nr, void * addr) ;
int test_and_change_bit ( int nr, void * addr) ;
int test_bit ( int nr, void * addr) ;
内核还提供操作对应的非原子位函数,只是不保证原子性,其操作函数的名字前缀多两个下划线,如test_bit()对应的非原子位操作函数位__test_bit() 内核还提供从指定的地址开始搜索第一个被设置(或未被设置)的位
int find_first_bit ( unsigned long * addr, unsigned int size) ;
int find_fisrt_zero_bit ( unsigned long * addr, unsigned int size) ;
自旋锁
自旋锁在同一时刻至多被一个执行线程持有 如果一个线程试图获取已被持有的自旋锁,那么该线程一直进行忙循环-旋转-等待锁可用状态 自旋锁可以使用在中断处理程序中,在中断处理程序使用自旋锁,一定获取锁之前,首先禁止本地中断,否则中断处理程序就会打断正持有锁的内核代码 保护数据而不是代码
自旋锁方法
#include <asm/spinlock.h>
#include <linux/spinlock.h>
spinlock_t lock;
DEFINE_SPINLOCK ( lock) ;
spin_lock ( & lock) ;
spin_unlock ( & lock) ;
unsigned long flags;
spin_lock_irqsave ( & lock, flags) ;
spin_unlock_irqrestore ( & lock, flags) ;
方法 描述 spin_lock() 获取指定的自旋锁 spin_lock_irq() 禁止本地中断并获取指定的锁,不建议使用 spin_lock_irqsave() 保存本地中断的当前状态,禁止本地中断,并获取指定的锁 spi_unlock() 释放指定的锁 spin_unlock_irq() 释放指定的锁,并激活本地中断 spin_unlock_irqrestore() 释放指定的锁,并让本地中断恢复到以前状态 spin_lock_init() 动态初始化指定的spinlock_t spin_trylock() 试图获取指定的锁,如果未获得,返回非0,而不等待自旋锁被释放 spin_is_lock() 如果指定的锁当前已被占用,返回非0,值作为判断,实际不占用自旋锁
自旋锁和下半部
spin_lock_bh ( ) ;
spin_unlock_bh ( ) ;
如果下半部和进程上下文共享数据,必须对进程上下文的共享数据进行保护 因中断处理程序可以抢占下半部,所有如果中断处理程序和下半部共享数据时,就必须获取锁的同时禁止中断 同类的tasklet不可能同时运行,所以对同类的tasklet的共享数据不需要保护,但是当数据被两个不同种类的tasklet共享时,就需要访问下半部的数据前获取自旋锁 对于软中断,无论是否同种类型,如果数据被软中断共享,就必须获取锁的保护,但是不需要禁止下半部执行
读-写自旋锁
一个或多个读任务可以并发持有读自旋锁 用于写自旋锁最多只能被一个写任务持有,而且此时不能有并发的读操作 当读自旋锁被持有时,写操作为了互斥访问只能自旋等待 读自旋锁和写自旋锁位于完全分割开的代码分支中 如果读和写不能清晰分开,请使用一般的自旋锁
rwlock_t rwlock;
DEFINE_RWLOCK ( rwlock) ;
read_lock ( & rwlock) ;
read_unloc ( & relock) ;
write_lock ( & rwlock) ;
write_unloc ( & relock) ;
方法 描述 read_lock() 获取指定的读锁 read_lock_irq() 禁止本地中断并获取指定的读锁 read_lock_irqsave() 存储本地中断的当前状态,禁止本地中断并获取指定的读锁 read_unlock() 释放指定的锁 read_unlock_irq() 释放指定的读锁并激活本地中断 read_unlock_irqrestore() 释放指定的读锁并将本地中断恢复到之前状态 write_lock() 获取指定的写锁 write_lock_irq() 禁止本地中断并获取指定的写锁 write_lock_irqsave() 存储本地中断的当前状态,禁止本地中断并获取指定的写锁 write_unlock() 释放指定的写锁 write_unlock_irq() 释放指定的写锁并激活本地中断 write_unlock_irqrestore() 释放指定的写锁并将本地中断恢复到之前状态 write_trylock() 试图获取指定的写锁,如果写锁不可用,返回非0值 rwlock_init() 初始化指定的rwlock_t
信号量
Linux的信号量是一种睡眠锁,如果一个任务试图获取一个不可用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠 当持有的信号量可用(被释放)后,处于等待队列中的任务将被唤醒,并获得该信号量 在同一时刻仅允许一个锁持有者,此时信号量计数等于1,被称为互斥信号量 如果信号量初始化时被设计大于1的非零值,被称为计数信号量
创建和初始化信号量
#include <asm/semaphore.h>
struct semaphore name;
static DECLARE_MUTEX ( name) ;
sema_init ( & name, count) ;
init_MUTEX ( name) ;
使用信号量
函数down_interruptible()试图获取指定的信号量,如果信号量不可用,将调用进程设置成TASK_INTERRUPTIBLE状态——进入睡眠,意味该进程可以被信号唤醒。如果进程等待回去信号量的时候接收到信号,那么该进程被唤醒,返回-EINTR down()函数会让进程在TASK_UNINTERRUPTIBLE状态下睡眠,进程在等到信号量的时候就不会再响应信号 down_trylock()函数,尝试以堵塞方式获取指定的信号量,在信号量被占用时,立刻返回非0值,否则返回0,并且成功持有信号量
static DECLARE_MUTEX ( sem) ;
if ( down_interruptible ( & sem) {
}
up ( & sem) ;
方法 描述 sema_init(struct semaphore * sem, int value) 以指定的计数值初始化动态创建的信号量 init_MUTEX(struct semaphore * sem) 以计数值1初始化动态创建的信号量 init_MUTEX_LOCKED(struct semaphore * sem) 以计数值0初始化动态创建的信号量(初始为加锁状态) down_interruptible(struct semaphore * sem) 试图获取指定的信号量,如果信号量已被占用,则进入可中断睡眠状态 down(struct semaphore * sem) 试图获取指定的信号量,如果信号量已被占用,则进入不可中断睡眠状态 down_trylock(struct semaphore * sem) 试图获取指定的信号量,如果信号量已被占用,则立刻返回非0值 up(struct semaphore * sem) 释放指定的信号量,如果睡眠队列不为空,则唤醒其中一个任务
读-写信号量
读写信号量在内核由rw_semaphore结构表示,定义在文件<linux/rwsem.h> 所有的读写信号量都是互斥量,引用计数等于1 并发持有读锁的读取进程数量不限,只有唯一的写锁的写入进程(没有读取进程占用读写锁)可以获取写锁 所有读写锁的睡眠都不会被信号打断,所有只有一个版本的down()操作 读写锁必须使用在不同的分支代码,如果无法在分支代码中使用,请使用普通的互斥信号量
static DECLARE_RWSEM ( name)
init_rwsem ( struct rw_semaphore * sem) ;
static DECLARE_RWSEM ( rwsem)
down_read ( & rwsem) ;
up_read ( & rwsem) ;
down_write ( & rwsem) ;
up_write ( & rwsem) ;
down_read_trylock()和down_write_trylock(),如果成功获取信号量,则返回非0值,如果信号量被占用,则返回0(与普通信号量返回值相反) downgrade_write()函数可以动态将获取的写锁转换为读锁
互斥体
任何时刻只有一个任务可以持有mutex 给mutex上锁者必须负责其解锁——不能再一个上下文中锁定一个mutex,而在另一个上下文中解锁 递归地上锁和解锁是不允许的 当持有一个mutex时,进程不可以退出 mutex不能在中断或者下半部中使用,即使使用mutex_trylock()也不行 mutex只能通过API管理,不可被拷贝、手动初始化或重复初始化
DEFINE_MUTEX ( name) ;
mutex_init ( & mutex) ;
mutex_lock ( & mutex) ;
mutex_unlock ( & mutex) ;
方法 描述 mutex_lock(struct mutex *name) 给指定的mutex上锁,如果锁不可用,则睡眠 mutex_unlock(struct mutex *name) 为指定的mutex解锁 mutex_trylock(struct mutex *name) 试图获取指定的mutex,如果成功则返回1,否则锁被获取,返回值为0 mutex_is_locked(struct mutex *name) 如果锁已经被占用,则返回1,否则返回0
completion
一个任务进入睡眠状态等待另一个任务完成指定的动作后唤醒 通用做法,将completion作为数据结构成员
#include <linux/completion.h>
DECLARE_COMPLETION ( comp) ;
init_completion ( & comp) ;
方法 藐视 init_completion(struct completion *cmp) 初始化指定的动态创建的completion wait_for_completion(struct completion *cmp) 等待指定的completion信号 complete(struct completion *cmp) 发信号唤醒任何等待任务
BLK:大内核锁
持有BKL的任务仍然可以睡眠,因为当前任务无法被调度时,加锁会自动被丢弃,当任务被调度时,锁又会被重新获得 BLK是一种递归锁,一个进程可以多次请求一个锁,并不会产生死锁情况 BLK只可以用在进程上下文中 新用户不可使用BLK BLK被持有时禁止内核抢占
lock_kernel ( ) ;
unlock_kernel ( )
函数 描述 lock_kernel() 获取BLK unlock_kernel() 释放BLK kernel_locked() 如果锁被持有,则返回非0值,否则返回0
顺序锁
用于读写共享数据,依靠一个序列计数器 当有意义的数据被写入时,会得到一个锁,并且序列值会增加 在读取数据之前和之后,序列号都被读取,如果读取的序列号相同,说明读取操作过程进行的过程中没有被写操作打断 如果读取的值时偶数,那么表明写操作没有发生(因为锁的初值为0,写锁会使值成奇数,释放的时候变成偶数) 只有没有其他写入操作,写锁总是能成功获取,读取操作不影响写锁
seqlock_t seq_lock = DEFINE_SEQLOCK ( seq_lock) ;
write_seqlock ( & seq_lock) ;
write_sequnlock ( & seq_lock) ;
unsigned long seq;
do {
seq = read_seqbegin ( & seq_lock) ;
} while ( read_seqretry ( & seq_lock, seq) ) ;
案例
u64 get_jiffies_64 ( void )
{
unsigned long seq;
u64 ret = 0 ;
do {
seq = read_seqbegin ( & xtime_lock) ;
ret = jiffies_64
} while ( read_seqretry ( & xtime_lock, seq) ) ;
return ret;
}
write_seqlock ( & xtime_lock) ;
jiffies_64 + = 1 ;
write_sequnlock ( & xtime_lock) ;
禁止抢占
内核抢占代码使用自旋锁作为非抢占区域的标志 preempt_disable()禁止内核抢占,可以嵌套调用,每次调用都必须有一个相应的preempt_enable()调用
preempt_disbale ( ) ;
preempt_enable ( ) ;
函数 描述 preempt_disbale() 增加抢占计数值,从而禁止内核抢占 preempt_enable() 减少抢占计数,并当该值降为0时检查和执行被挂起的需要调度的任务 preemt_enable_no_resched() 激活内核抢占但不再检查任何被挂起的需要调度任务 preempt_count() 返回抢占计数
顺序和屏障
rmb()提供一个“读”内存屏障 wmb()提供一个“写”内存屏障 mb()提供一个“读”内存屏障,也提供一个“写”内存屏障 宏smp_rmb()、smp_wmb()、smp_mb()和smp_read_barrier_depends()提供一个有用的优化,在SMP内核中被定义成常用的内存屏障,而在单核处理器内核中,被定义编译器的屏障 barrier()方法可以防止编译器跨越对载入或存储操作进行优化 | 方法 | 描述 | |–|--| | rmb() | 阻止跨越屏障的载入动作发生重排序 | | read_barrier_depends() | 阻止跨越屏障的具有数据依赖关系的载入动作重排序 | | wmb() | 阻止跨越屏障的存储动作发生重排序 | | mb() | 阻止跨越屏障的载入和存储动作重排序 | | smp_rmb() | 在SMP上提供rmb()功能,在UP上提供barrier()功能 | | smp_read_barrier_depends() | 在SMP上提供read_barrier_depends()功能,在UP上提供barrier()功能 | | smp_wmb() | 在SMP上提供wmb()功能,在UP上提供barrier()功能 | | smp_mb() | 在SMP上提供mb()功能,在UP上提供barrier()功能 | | barrier() | 阻止编译器跨越屏障对载入或存储操作进行优化 |