并发导致竟态,从而导致对共享数据的非控制访问,产生非预期结果,我们要避免竟态的发生。遵循以下原则:1,尽量避免资源共享;2,显示地管理对共享资源的访问。管理技术通常为“锁定”或者“互斥”,保证任何时刻只有一个执行线程可操作共享资源。
几个重要的概念:1,原子操作,顾名思义,就是说该操作是原子性的(原子是保持物质物理新性质的最小单位),不可分割的,亦即操作要么处于不间断地执行的状态,要么处于不执行的状态。2,临界区,临界区是这么一段代码,这段代码在任意给定时刻只能被一个线程执行。3,休眠,休眠是程序处于阻塞的状态,进程到达某个时间点,此时它不能进行任何处理(可能是等待某资源),它让出处理器给别的进程用,直到它能够完成自己的处理为止。相当于进程进入睡觉的状态,但它永远不知道要睡多长时间,也许它几天后才醒来,但是它会认为只是打了个盹。
1.自旋锁(盲等,一直占着cpu不放)
在单核并且内核进制抢占的情况无效
在单核并且内核支持抢占的情况下,操作等价于preempt_diable preempt_enable
在多核处理器尚自旋锁才有意义
自旋锁必须在可能的最短时间内拥有,也就是说自旋锁保护的临界区执行的越快越好。
自旋锁的数据结构:struct spinlock spinlock_t
自旋锁必须初始化:spin_lock_init(spinlock_t lock); (DEFINE_SPINLOCK(x))
注意所有的自旋锁等待时, 由于它们的特性, 不可中断的. 一旦你调用 spin_lock, 你将自旋直到锁变为可用.
为释放一个你已获得的锁要调用spin_unlock.
在进入一个临界区(进程间相互竞争的位置,多是全局变量)前, 你的代码必须获得需要的 lock
结束的时候必须释放锁。
关于锁的内核函数:
spin_lock(加锁)
spin_lock_bh(获得锁并终止中断下半部分)
spin_lock_irq
spin_lock_irqsave(加锁并保存中断状态)
非阻塞的自旋锁操作:
spin_trylock(返回1表示成功获得锁)---->arch_spin_trylock
int spin_trylock_bh
spin_unlock
spin_unlock_bh(解锁并开始下半部分中断)
spin_unlock_irq
spin_unlock_irqrestore(解锁并恢复中断)
注意:1.返回之前必须解锁。2.加锁之后绝对不能睡眠。3.中断上下文不能睡眠。4.函数中不能定义太大的局部变量。
注意:copy_to_user, kzalloc等能导致休眠的函数禁止出现在有spin_lock的临界区内.
2.读/写自旋锁(容易饿死写者)
使用场合:多用于读者远远多于写者
定义结构体:rwlock_t
相关函数:
rwlock_init
read_trylock
write_trylock
write_lock
read_lock
read_lock_irqsave
write_lock_irqsave
read_unlock
write_unlock
read_unlock_irqrestore
write_unlock_irqrestore
seqlock(写者优先)
typedef struct {
unsigned sequence;
spinlock_t lock;
} seqlock_t;
seqlock_init(x)
write_seqlock
write_sequnlock
read_seqbegin
read_seqretry
seqlock_t foo;
对于写者来说:
write_seqlock(&foo);
写操作
写操作
写操作
写操作
write_sequnlock(&foo);
对于读者来说:
do {
seq = read_seqbegin(&foo);
读操作
读操作
读操作
读操作
} while (read_seqretry(&foo, seq));
3.互斥量(mutex)(如果加锁不成功则睡眠等待,在中断上下文不能使用)
使用场合:可用于没有中断的竞争场合
它在include/linux/mutex.h中定义。
定义结构体:struct mutex
相关函数:
mutex_init 初始化
mutex_lock 如果获得不了互斥量,则以不可中断的方式睡眠
mutex_lock_interruptible 如果获得不了互斥量,则以可中断的方式睡眠
mutex_lock_killable 如果获得不了互斥量,则以可杀死的方式睡眠(能够被致命信号杀死)
mutex_trylock
mutex_unlock
4.信号量(semaphore)(如果获得不了信号量,则以睡眠的方式等待)
1.计数信号量:
旗标在计算机科学中是一个被很好理解的概念. 在它的核心, 一个旗标是一个单个整型值, 结合有一对函数, 典型地称为 P 和 V. 一个想进入临界区的进程将在相关旗标上调用 P; 如果旗标的值大于零, 这个值递减 1 并且进程继续. 相反, 如果旗标的值是 0 ( 或更小 ), 进程必须等待直到别人释放旗标. 解锁一个旗标通过调用 V 完成; 这个函数递增旗标的值, 并且, 如果需要, 唤醒等待的进程.
2.互斥信号量:(常用)
当旗标用作互斥 -- 阻止多个进程同时在同一个临界区内运行 -- 它们的值将初始化为 1. 这样的旗标在任何给定时间只能由一个单个进程或者线程持有. 以这种模式使用的旗标有时称为一个互斥锁, 就是, 当然, "互斥"的缩写. 几乎所有在 Linux 内核中发现的旗标都是用作互斥.
它在include/linux/semaphore.h中定义。
定义结构体:struct semaphore
相关函数:
sema_init
down (P)
down_interruptible
down_killable
down_trylock
down_timeout(&sem, HZ * 4)
up (V)
5.完成量(在等待完成的时候是睡眠等待)
首先需明确完成量表示为一个执行单元需要等待另一个执行单元完成某事后方可执行,它是一种轻量级机制。事实上,它即是为了完成进程间的同步而设计的,故而仅仅提供了代替同步信号量的一种解决方法,初值被初始化为0。
它在include\linux\completion.h定义。
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
static inline void init_completion(struct completion *x) //初始化
{
x->done = 0;
init_waitqueue_head(&x->wait);
}
//等待完成
wait_for_completion
wait_for_completion_interruptible
wait_for_completion_killable
wait_for_completion_timeout
其实wait**是让done--,如果done是0的话就不能减,然后就睡眠
//唤醒一个
complete---------->done++
//唤醒所有
complete_all ----->done = 2^32 - 1
注意死锁:
1.自私锁
2.ABBA死锁
3.带锁睡眠
阅读(1174) | 评论(0) | 转发(1) |