1. 原子操作
内核提供两组原子操作的接口,一组是对整数进行操作,一组是对单独的位进行操作。
现实中,每个临界区不仅仅是对变量的增加、减少,可能临界区域甚至跨越几个函数,而这些都需要保证原子性,因此引入各种锁机制。
2. 自旋锁
linux内核中最常见的锁是自旋锁。自旋锁最多只能被一个可执行线程持有。
注意:
(1) 自旋锁不能递归加锁,否则产生死锁。
(2) 自旋锁适用于加锁时间短,执行命令少的场合。
(3) 在单处理器的系统中,编译时并不会加入自旋锁,它仅仅被设置成一个设置内核抢占机制是否被启动的开关。
(4) 不能再可以引起休眠的地方占用自旋锁,休眠意味着让其CPU,这样将永远没有机会对持有的自旋锁解锁。
3. 信号量
Linux中的信号量是一种可以睡眠的锁。当一个任务试图获取一个不可用(已经被抢占)的信号量时,信号量将它放入等待队列,让其睡眠。等持有的信号量被释放时,处于等待队列的任务将被唤醒,并获得该信号量。
3.1 计数信号量
信号量可以同时允许任意数量的锁持有者,而自旋锁只能在一个时刻最多允许有一个任务持有它。
3.2 二值信号量
信号量中的计数指定为1,也叫互斥信号量。
P操作:通过对信号量的计数减一来获取信号量,当返回值等于或者大于零时,获取信号量,否则将它放入等待队列。
V操作:通过对计数加一释放信号量,当信号量的等待队列中的任务不为零,该任务将被唤醒并获得信号量。
注意:
(1) 因为竞争信号量的进程在等待信号量时会进入睡眠,所有信号量适用于被长时间持有的情况。
(2) 由于执行线程在锁被争取的时候会引起睡眠,所以只能在进程上下文中获取信号量,因为在中断上下文中是不能被调度的。
(3) 在占用信号量时不能占用自旋锁,因为在等待信号量时可能引起休眠,而持有自旋锁时不能休眠。
4. 互斥体
"互斥体(mutex)"这个称谓所指是任何可以睡眠的强制互斥锁,比如使用计数是1的信号量。
注意:
(1) 任何时刻中只有一个任务可以持有mutex,mutex的使用计数永远1。
(2) 给mutex上锁者必须负责其再解锁----你不能在一个上下文中锁定一个mutex,而在另一个上下文给它解锁。
(3) 不能递归上锁和解锁。
(4) mutex不能再中断或者下半部使用。
对比:
信号量和互斥体:
除非互斥体的某个约束妨碍你的使用,否则优先使用mutex。
自旋锁与互斥体:
自旋锁:低开销加锁、短时间持有、中断上下文中使用
互斥体:长时间持有、持有的锁需要睡眠