内核同步方法及其比较

本文通过一个死锁实例分析了自旋锁与互斥体结合使用时可能出现的问题。自旋锁在持有锁时禁止抢占,若在自旋锁保护的区域内休眠,可能导致在同一CPU上的其他任务无法获取锁而死锁。信号量允许任务休眠,适用于可能长时间占用的锁。互斥体是一种强制互斥的休眠锁,持有者必须解锁,且不能在中断上下文使用。了解这些机制有助于避免内核同步中的死锁问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前些天在处理一个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内核中实现的自旋锁不能递归使用,尝试获取自己已经持有的自旋锁,会导致被自己锁死。

自旋锁可以在不能休眠的代码中使用,比如中断处理程序。而且拥有自旋锁的代码必须是原子的,绝对不能休眠,否则其它线程试图获得同一个锁,可能会导致死锁。

自旋锁的操作

void spin_lock_init(spinlock_t *lock)

void spin_lock(spinlock_t *lock)
void spin_unlock(spinlock_t *lock)


void spin_lock_irq(spinlock_t *lock)
void spin_unlock_irq(spinlock_t *lock)

void spin_lock_irqsave(spinlock_t *lock)
void spin_unlock_irqrestore(spinlock_t *lock)

int spin_trylock(spinlock_t *lock)
int spin_is_locked(spinlock_t *lock)

信号量

信号量是一种休眠锁,一个任务在获取一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其休眠。当信号量被释放时,原先休眠的任务会被唤醒。

只能在进程上下文中获取信号量,不能在中断上下文中获取信号量,因为中断上下文中进程不能被调度成休眠状态。

自旋锁和信号量不能同时占有,因为在等待信号量的时候进城可能被调度成休眠状态,而在持有自旋锁时是不允许休眠的。

信号量不同于自旋锁,它不会禁止内核抢占,所以持有信号量的代码可以被抢占。

如果代码可以休眠,则只能使用信号量,不能自旋锁。 如果锁可能会被占用较长时间,使用信号量,否则使用自旋锁。

信号量一般情况下应该使用可中断的版本。

信号量的操作

void sema_init(struct semaphore *sem, int val)
extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

互斥体

互斥体是指任何可以休眠的强制互斥锁。互斥体是一种互斥信号。

互斥体使用上相对于信号量更加严格。最重要的一条是:上锁者必须负责解锁(),当持有互斥体锁时进程不能退出。另外,同信号量一样,互斥体不能在中断内部或者中断的下半部中使用。

互斥体的操作

struct mutex
void mutex_init(struct mutex *)
void mutex_lock(struct mutex *)
void mutex_unlock(struct mutex *)
int mutex_trylock(struct mutex *)
int mutex_is_locked(struct mutex *)



以下内容转自: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






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YasinLeeX

再来一杯西湖龙井。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值