前些天在处理一个bug时发现了一些问题。
static void headmicbias_power_on(struct device *dev, int on)
{
unsigned long spin_lock_flags;
static int current_power_state = 0;
struct sprd_headset *ht = &headset;
spin_lock_irqsave(&headmic_bias_lock, spin_lock_flags);
if (1 == on) {
if (0 == current_power_state) {
if(NULL != ht->platform_data->external_headmicbias_power_on)
ht->platform_data->external_headmicbias_power_on(1);
sprd_headset_headmic_bias_control(dev, 1);
current_power_state = 1;
}
} else {
if (1 == current_power_state) {
if(NULL != ht->platform_data->external_headmicbias_power_on)
ht->platform_data->external_headmicbias_power_on(0);
sprd_headset_headmic_bias_control(dev, 0);
current_power_state = 0;
}
}
spin_unlock_irqrestore(&headmic_bias_lock, spin_lock_flags);
return;
}
以上函数中用自旋锁做了保护,但是出现了死锁现象。死锁时的调用栈如下:
-000|mspin_lock(
| lock = 0xCF57D34C,
| node = 0xCC5C5E44)
| size = 4
| ptr = 0xCF57D34C
| x = 3428605508
|
-001|__mutex_lock_slowpath(
| lock_count = 0xCF57D338)
| lock = 0xCF57D338
| state = 2
| lock = 0xCF57D338 -> (
| count = (counter = -1),
| wait_lock = (rlock = (raw_lock = (slock = 0, tickets = (owner = 0, next
| wait_list = (next = 0xCF57D340, prev = 0xCF57D340),
| owner = 0x0,
| spin_mlock = 0x0)
| task = 0xCBFD4980
| waiter = (list = (next = 0x0, prev = 0x0), task = 0xCEFE0000)
| lock = 0xCF57D338
| node = (next = 0x0, locked = 0)
| flag = 1
|
|
-002|mutex_lock( //在调用regulator_enable的函数中又有一个互斥锁,而互斥锁是允许休眠的。
| lock = 0xCF57D338)
| count = 0xCF57D338
| i = 1
| v = 0xCF57D338
| lock = 0xCF57D338
| sp = 3428605552
|
|
-003|regulator_enable(
| ?)
| rdev = 0xCF57D300
| rdev = 0xCF57D300
|
|
-004|regulator_enable(
| ?)
| rdev = 0xCF57E140
|
|
-005|regulator_enable(
| ?)
| rdev = 0xCF5F3900
|
|
-006|sprd_headset_headmic_bias_control.isra.11(
| ?,
| ?)
|
|
-007|headmicbias_power_on.isra.12( //进入该函数后拿到一个自旋锁
| on = 1,
| ?)
| spin_lock_flags = 537854227
| current_power_state = 0
|
|
-008|headset_detect_work_func(
| ?)
| pdata = 0xCEFC7B00
| __func__ = (104, 101, 97, 100, 115, 101, 116, 95, 100, 101, 116, 101, 99,
|
|
-009|process_one_work(
| worker = 0xCB504780,
| work = 0xC08916C4)
| pwq = 0xCEFE0000
| pool = 0xCF4042C0
| cpu_intensive = FALSE
| work_color = 0
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| val = 3230209732
| n = 0xCB504780
| work = 0xC08916C4
| __warned = FALSE
| __warned = FALSE
| lock = 0xCF4042C0
| work = 0xC08916C4
| worker = 0xCB504780
| sp = 3428605712
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
|
|
-010|worker_thread(
| __worker = 0xCB504780)
| worker = 0xCB504780
| pool = 0xCF4042C0
| worker = 0xCB504780
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| lock = 0xCF4042C0
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
| pool = 0xCF4042C0
| __warned = FALSE
| __warned = FALSE
| worker = 0xCB504780
| pool = 0xCF4042C0
| oflags = 137
| __warned = FALSE
| __warned = FALSE
| __warned = FALSE
|
|
-011|kthread(
| _create = 0xC1AB9EB0)
| create = 0xC1AB9EB0
| threadfn = 0xC0051040
| data = 0xCB504780
| self = (flags = 0, cpu = 327685, data = 0xCB504780, parked = (done = 0, w
| __key = ()
| __key = ()
| __key = ()
| __key = ()
| __key = ()
| nr = 1
|
|
-012|ret_from_fork(asm)
|
|
-->|exception
---|end of frame
N _ R0 CF57D34C R8 0 SP> 00000001
Z _ R1 CC5C5E44 R9 0 +04 00000000
C C R2 CC5C5E44 R10 0 +08 00000000
V _ R3 0 R11 89 +0C CEFE0000
Q _ R4 CF57D338 R12 0 +10 CF57EAC0
R5 CBFD4980 R13 CC5C5E40 +14 CF57D338
0 _ R6 CF57D34C R14 C0587174 +18 00000000
1 _ R7 CC5C4000 PC C0058864 +1C 200F0113
2 _ SPSR 600F0113 CPSR 200F0193 +20 CF57D338
3 _ +24 CEFE0000
4 _ USR: FIQ: +28 00000000
R8 0 R8 F42CAA39 +2C C0587368
R9 0 R9 D4A5F9B3 +30 CF57D300
I I R10 0 R10 DBFAB7D9 +34 C0243794
F _ R11 89 R11 6E7DE52B +38 00000000
R12 0 R12 F4B05BA7 +3C CF57E140
T _ R13 B2664C38 R13 AA0ACFD1 +40 00000001
J _ R14 B6EB5023 R14 DC2FBFFB +44 200F0113
svc SPSR 960CC314 +48 C08A4F99
sec +4C CEFE0000
SVC: IRQ: +50 00000000
A A R13 CC5C5E40 R13 C08BB380 +54 C02437A8
E _ R14 C0587174 R14 C000F000 +58 00000000
SPSR 600F0113 SPSR 600F0193 +5C CF5F3900
0 G +60 00000001
1 G UND: ABT: +64 200F0113
2 G R13 C08BB398 R13 C08BB38C +68 C08A4F99
3 G R14 C000F220 R14 C000F180 +6C CEFE0000
SPSR A0070093 SPSR 600F0193 +70 00000000
+74 C02437A8
MON: HYP: +78 00000001
R13 BCC5850B R13 BCC5850B +7C C096C140
R14 1C5087E7 ELR 970C5F65 +80 00000001
SPSR 200CB402 SPSR 200CB402 +84 200F0113
+88 C08A4F99
+8C CEFE0000
________address|________0________4________8________C_0123456789ABCDEF
EZAHB:8F57D330| CF57D32C 00000000>00000001 00000000 ,.W.............
EZAHB:8F57D340| CF57D340 CF57D340 00000000 00000000 @.W.@.W.........
EZAHB:8F57D350| 00000000 00000000 C089A660 CF5CEDC0 ........`.....\.
EZAHB:8F57D360| CF5D7100 CF57E664 CF57EB24 CF5D7E00 .q].d.W.$.W..~].
EZAHB:8F57D370| CF4D0E00 C0887E78 CF5D6960 00000003 ..M.x~..`i].....
//========================================================================
以上代码就是一个典型自选所包裹互斥锁的例子。因为互斥锁允许休眠,所以可以理解为在自旋锁所管辖的临界区发生了休眠。
我们知道,持有自旋锁的代码段要尽量简短且不能有睡眠动作。但是,如果我们做一个不合适的实验:让一个持有自旋锁的代码段进入休眠状态,在此期间如果没有其他进程请求该锁,则不会发生问题,如果请求该锁的进程运行(自旋)在另一个cpu,这也不会出事。但是假如请求该锁的进程和持有该锁的进程运行在同一个cpu,则会出现死锁。
更重要的原因是自旋锁自旋时是被设设为禁止抢占的,所以,如果一个如果进入自旋状态时,在单核情况下就无法再进行调度。自然死锁了!
上述代码中用自旋锁包裹了互斥锁,在大部分情况下都没有发生死锁,为什么只在小概率下发生死锁呢?
原因是中断被屏蔽了,spin_lock_irqsave调用会屏蔽本cpu上的中断。如果接下来的互斥锁的临界区内再发生了休眠,这就会导致任务调度停止,因为没有中断了,而任务调度的基础就是时钟中断!大部分时候,临界区内的代码都没有发生休眠,所以我们看到的是小概率的死锁现象。另外再注意到一点,就是这个死锁发生在休眠唤醒流程中,因为这个过程是单核运行的。在正常的多核条件下,也不容易发生死锁,因为其他cpu上的调度还可以正常进行。
进程死锁:
1. 两个进程互相等待对方持有的互斥锁时会导致二者都进入休眠状态,永远得不到执行。发生死锁。
2. 一个进程第二次申请一个已经被自己持有的互斥锁时会导致自己进入休眠状态且无法醒过来,发生死锁。
以下内容转自:http://funexploit.github.io/sources/kernel_sync.html
原子操作
包括32位、64位原子整数操作和原子位操作
自旋锁
内核中最常见的锁机制就是自旋锁。
自旋锁的目的是在短期内进行轻量级加锁,因为自旋锁不应该被长时间持有。
Linux内核中实现的自旋锁不能递归使用,尝试获取自己已经持有的自旋锁,会导致被自己锁死。
自旋锁可以在不能休眠的代码中使用,比如中断处理程序。而且拥有自旋锁的代码必须是原子的,绝对不能休眠,否则其它线程试图获得同一个锁,可能会导致死锁。
自旋锁的操作
信号量
信号量是一种休眠锁,一个任务在获取一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其休眠。当信号量被释放时,原先休眠的任务会被唤醒。
只能在进程上下文中获取信号量,不能在中断上下文中获取信号量,因为中断上下文中进程不能被调度成休眠状态。
自旋锁和信号量不能同时占有,因为在等待信号量的时候进城可能被调度成休眠状态,而在持有自旋锁时是不允许休眠的。
信号量不同于自旋锁,它不会禁止内核抢占,所以持有信号量的代码可以被抢占。
如果代码可以休眠,则只能使用信号量,不能自旋锁。 如果锁可能会被占用较长时间,使用信号量,否则使用自旋锁。
信号量一般情况下应该使用可中断的版本。
信号量的操作
互斥体
互斥体是指任何可以休眠的强制互斥锁。互斥体是一种互斥信号。
互斥体使用上相对于信号量更加严格。最重要的一条是:上锁者必须负责解锁(),当持有互斥体锁时进程不能退出。另外,同信号量一样,互斥体不能在中断内部或者中断的下半部中使用。
互斥体的操作
以下内容转自:http://blog.youkuaiyun.com/vividonly/article/details/6594195
最近在内核频繁使用了自旋锁,自旋锁如果使用不当,极易引起死锁,在此总结一下。
自旋锁是一个互斥设备,它只有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的某个位。希望获得某个特定锁得代码测试相关的位。如果锁可用,则“锁定”被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环(而不是休眠,这也是自旋锁和一般锁的区别)并重复检查这个锁,直到该锁可用为止,这就是自旋的过程。“测试并设置位”的操作必须是原子的,这样,即使多个线程在给定时间自旋,也只有一个线程可获得该锁。
自旋锁最初是为了在多处理器系统(SMP)使用而设计的,但是只要考虑到并发问题,单处理器在运行可抢占内核时其行为就类似于SMP。因此,自旋锁对于SMP和单处理器可抢占内核都适用。可以想象,当一个处理器处于自旋状态时,它做不了任何有用的工作,因此自旋锁对于单处理器不可抢占内核没有意义,实际上,非抢占式的单处理器系统上自旋锁被实现为空操作,不做任何事情。
自旋锁有几个重要的特性:1、被自旋锁保护的临界区代码执行时不能进入休眠。2、被自旋锁保护的临界区代码执行时是不能被被其他中断中断。3、被自旋锁保护的临界区代码执行时,内核不能被抢占。从这几个特性可以归纳出一个共性:被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。
考虑上面第一种情况,想象你的内核代码请求到一个自旋锁并且在它的临界区里做它的事情,在中间某处,你的代码失去了处理器。或许它已调用了一个函数(copy_from_user,假设)使进程进入睡眠。也或许,内核抢占发威,一个更高优先级的进程将你的代码推到了一边。此时,正好某个别的线程想获取同一个锁,如果这个线程运行在和你的内核代码不同的处理器上(幸运的情况),那么它可能要自旋等待一段时间(可能很长),当你的代码从休眠中唤醒或者重新得到处理器并释放锁,它就能得到锁。而最坏的情况是,那个想获取锁得线程刚好和你的代码运行在同一个处理器上,这时它将一直持有CPU进行自旋操作,而你的代码是永远不可能有任何机会来获得CPU释放这个锁了,这就是悲催的死锁。
考虑上面第二种情况,和上面第一种情况类似。假设我们的驱动程序正在运行,并且已经获取了一个自旋锁,这个锁控制着对设备的访问。在拥有这个锁得时候,设备产生了一个中断,它导致中断处理例程被调用,而中断处理例程在访问设备之前,也要获得这个锁。当中断处理例程和我们的驱动程序代码在同一个处理器上运行时,由于中断处理例程持有CPU不断自旋,我们的代码将得不到机会释放锁,这也将导致死锁。
因此,如果我们有一个自旋锁,它可以被运行在(硬件或软件)中断上下文中的代码获得,则必须使用某个禁用中断的spin_lock形式的锁来禁用本地中断(注意,只是禁用本地CPU的中断,不能禁用别的处理器的中断),使用其他的锁定函数迟早会导致系统死锁(导致死锁的时间可能不定,但是发生上述死锁情况的概率肯定是有的,看处理器怎么调度了)。如果我们不会在硬中断处理例程中访问自旋锁,但可能在软中断(例如,以tasklet的形式运行的代码)中访问,则应该使用spin_lock_bh,以便在安全避免死锁的同时还能服务硬件中断。
补充:
锁定一个自旋锁的函数有四个:
void spin_lock(spinlock_t *lock);
最基本得自旋锁函数,它不失效本地中断。
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
在获得自旋锁之前禁用硬中断(只在本地处理器上),而先前的中断状态保存在flags中
void spin_lockirq(spinlock_t *lock);
在获得自旋锁之前禁用硬中断(只在本地处理器上),不保存中断状态
void spin_lock_bh(spinlock_t *lock);
在获得锁前禁用软中断,保持硬中断打开状态
还可以参考:http://blog.chinaunix.net/uid-26990992-id-3264808.html
http://www.embedu.org/Column/Column25.htm