临界区:访问和操作共享数据的代码段 竞争条件:多个执行线程处于同一临界区中同时执行 同步:避免并发和防止竞争条件
(1)中断:中断随时会发生,也就会随时打断当前执行的代码。 (2)软中断和tasklet:软中断和tasklet也会随时被内核唤醒执行,也会像中断一样打断正在执行的代码 (3)内核抢占:内核具有抢占性,发生抢占时,如果抢占的线程和被抢占的线程在相同的临界区,就产生了竞争条件 (4)睡眠及用户空间的同步:用户进程睡眠后,调度程序会唤醒一个新的用户进程,新的用户进程和睡眠的进程可能在同一个临界区中 (5)对称多处理:2个或多个处理器可以同时执行相同的代码 注意:要给数据加锁而不是给代码加锁。
(1)产生原因 一个或多个执行线程和一个或多个资源,每个线程都在等待其中一个资源,但所有资源已经都被占了。所有线程相互等待,但他们永远不会释放已经占有的资源。 (2)避免死锁 1)按顺序加锁,使用嵌套的锁时,必须保证以相同的顺序获取锁。 2)防止发生饥饿。即设置一个超时时间,防止一直等待下去。 3)不要重复请求同一个锁。 4)设计应力求简单。加锁的方案越复杂就越容易出现死锁。
atomic_t类型定义在<linux/types.h> 32位:atomic_t 64位:atomic64_t
自旋锁(spin lock),只能被一个线程持有,某个线程试图获取锁,该线程会忙-旋转-等待。 基本使用:
DEFINE_SPINLOCK(mr_lock); spin_lock(&mr_lock); //临界区 spin_unlock(&mr_lock);注意: 1)可用在中断处理程序中 2)在获取锁前,首先禁止当前处理器上的本地中断
多个读任务可以并发地持有锁,只能有一个任务可以持有写锁 使用
DEFINE_RWLOCK(mr_rwlock); read_lock(&mr_rwlock); /* 临界区(只读).... */ read_unlock(&mr_rwlock); write_lock(&mr_lock); /* 临界区(读写)... */ write_unlock(&mr_lock);是一种睡眠锁,当任务不能获取信号量时,睡眠。 适用于长时间持有的情况 两种:计数信号量、二值信号量
/* 定义并声明一个信号量,名字为mr_sem,用于信号量计数 */ static DECLARE_MUTEX(mr_sem); /* 试图获取信号量....*/ if(down_interruptible(&mr_sem)){ ... } /* 临界区 ... */ /* 释放给定的信号量 */ up(&mr_sem);与读写自旋锁类似。
类似二值信号量。 相对信号量,优先选择互斥体。 自旋锁和互斥量
需求建议的加锁方法低开销加锁优先使用自旋锁短期锁定优先使用自旋锁长期加锁优先使用互斥体中断上下文中加锁使用自旋锁持有锁需要睡眠使用互斥体一个任务执行工作,另一个任务在完成变量上等待,类似信号量。 方法
方法描述init_completion(struct completion *)初始化指定的动态创建的完成变量wait_for_completion(struct completion *)等待指定的完成变量接受信号complete(struct completion *)发信号唤醒任何等待任务不鼓励使用,部分代码中依然沿用。 特性: 1)持有BLK的任务可以睡眠 2)递归锁 3)只可以用在进程上下文中
读锁被获取时,写锁仍然可以被获取。依靠一个序列计数器,当有疑义的数据被写入时,序列值增加。 使用
//定义 seqlock_t mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock); //写 write_seqlock(&mr_seq_lock); /*写锁被获取*/ write_sequnlock(&mr_seq_lock); //读 unsigned long seq; do { seq = read_seqbegin(&mr_seq_lock); ... } while(read_seqretry(&mr_seq_lock, seq));适用:数据存在多个读者,写者很少,希望写优先于读,数据简单
对于SMP,某些情况不需要自旋锁,但仍需要关闭内核抢占。
preempt_disable() /*抢占被禁止*/ preempt_enable()目的:确保指令顺序执行,不被处理器重排序