[特殊字符]‍[特殊字符]Linux驱动开发入门 | 并发与互斥机制详解

👨‍💻Linux驱动开发入门 | 并发与互斥机制详解

📌为什么驱动中需要并发和互斥控制?

我们知道,在多线程或多任务并行执行的操作系统中,比如Linux内核,多个执行单元(线程或中断)可能同时访问共享资源(如全局变量、设备寄存器、缓冲区等),这就带来了“竞态条件(Race Condition)”的风险。

举个简单的例子:
假如两个线程A和B几乎在同一时间读取同一个计数变量x的值为10,然后各自+1并写回。你期望x变为12,但结果可能还是11。

这类问题就需要“互斥”机制来保护——确保同一时刻只能有一个执行单元访问共享资源,其访问的代码区域称为临界区(Critical Section)


💡常见的并发控制机制

Linux驱动开发中,常见的互斥控制方式有以下几种:

互斥机制特点场景
中断屏蔽禁止中断上下文干扰适用于简单、快速完成的临界区
原子操作使用CPU原子指令保证变量操作完整操作变量极少时
自旋锁(spinlock)自旋等待,适合短时间锁定中断/进程上下文
信号量(semaphore)可睡眠等待,适合长时间持锁进程上下文,驱动任务中常用
互斥锁(mutex)是信号量的简化版本一般用于用户态/驱动模块

🔐自旋锁和信号量通俗理解

🌀自旋锁(Spinlock)——“厕所排队锁”

把共享资源想象成一个单人厕所。

  • 线程A进入厕所,并锁门(获取锁);
  • 线程B也想用厕所,只能在门口一直转圈圈(不停检查锁状态);
  • A出来后释放锁,B才能进去。

自旋锁适合锁定时间非常短的临界区,因为等待期间线程一直占用CPU,不睡觉!

✅ 优点:

  • 实时性好(适合中断上下文)
  • 实现简单

❌ 缺点:

  • CPU占用率高,锁持有久了会浪费资源
  • 不可在临界区使用可能睡眠的代码!

🚦信号量(Semaphore)——“停车场智能显示器”

假设一个停车场有100个车位,信号量就相当于入口处的电子屏:

  • 显示“当前车位:20”,车还能进;
  • 显示“满”,车就得等;
  • 有车离开,车位更新,通知其他等车入场。

信号量适合临界区操作时间较长、可能会阻塞的场景

✅ 优点:

  • 可睡眠等待,不占CPU
  • 适合处理资源池问题,如连接池、缓存池等

❌ 缺点:

  • 实时性差,不可用于中断处理
  • 实现复杂,需考虑死锁
  • 锁被短时间持有时,使用信号量就不太适宜了,因为睡眠引起的耗时可能比锁被占用的全部时间还要长。

🆚 自旋锁 vs 信号量

对比项自旋锁信号量
是否睡眠❌ 不可睡眠✅ 可睡眠等待
适用上下文中断上下文进程上下文
临界区时长极短可长
是否允许抢占❌ 不允许(禁抢)✅ 允许抢占
用于中断中✅ 可以❌ 禁止
是否可重入❌ 否✅ 是(看实现)

在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。


读写锁是什么?

当临界区的一个文件可以被同时读取,但是并不能被同时读和写。如果一个线程在读,另一个线程在写,那么很可能会读取到错误的不完整的数据。读写自旋锁是可以允许对临界区的共享资源进行并发读操作的。但是并不允许多个线程并发读写操作

🚨死锁问题和解决策略

在操作系统或并发编程中,**死锁(Deadlock)**是一个经典问题。本文将带你由浅入深地了解死锁的处理方式,主要包括四种:预防死锁、避免死锁、检测死锁以及解除死锁。


一、预防死锁

死锁产生的四个必要条件是:互斥、不可剥夺、请求与保持、循环等待。

为了预防死锁,我们可以通过破坏其中一个或多个条件来避免死锁的发生。

1. 资源一次性分配(破坏请求与保持条件)

当一个进程申请资源时,必须一次性申请它执行所需的所有资源。如果一次申请不到,就什么也不分配,避免持有部分资源再申请其他资源。

2. 可剥夺资源(破坏不可剥夺条件)

允许系统在资源不足时,强行从某些进程中回收已分配的资源,重新分配给其他更需要的进程。

3. 资源有序分配法(破坏循环等待条件)

为所有资源编号,进程必须按编号递增的顺序申请资源。释放时则按编号递减顺序释放。这样可以避免资源请求形成闭环。


二、避免死锁

相比预防死锁,避免死锁不要求完全避免死锁条件的成立,而是在每次资源分配时判断是否安全。

银行家算法

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。
这是最经典的死锁避免算法。

  • 系统在每次资源分配前,模拟本次资源分配是否会导致系统进入不安全状态。
  • 如果安全,则分配资源;否则让进程等待。

三、死锁检测

死锁检测是允许死锁发生,但系统会定期检查是否有死锁存在,一旦检测到就进行处理。

步骤如下:
  1. 系统记录所有进程与资源的指定一个唯一的号码,构建资源分配图或等待图。
  2. 检查是否存在环路(循环等待)结构。
  3. 若有环路,即可判定发生了死锁。

四、解除死锁

当检测到死锁后,需要采取措施解除死锁状态。常见方法如下:

1. 剥夺资源

从非死锁进程中剥夺资源分配给死锁进程,让后者能继续运行,释放资源。

2. 撤消进程
  • 终止死锁进程或一些代价较小的进程,释放资源。
  • 代价可以依据优先级、运行时间、完成率、业务重要性来评估。

五、避免死锁的编程实践

在多线程编程中(如Java、C++),我们还可以通过一些实际的编程技巧避免死锁:

1. 加锁顺序(Lock Ordering)

确保所有线程在获取多个锁时,始终按照固定顺序获取。例如:线程要获取锁A和锁B,必须先获取编号小的锁A,再获取锁B。

// Thread 1:
synchronized(lockA) {
    synchronized(lockB) {
        // do something
    }
}
// Thread 2: 也必须先获取lockA,再获取lockB

按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁,并对这些锁做适当的排序),但总有些时候是无法预知的。

2. 加锁时限(Try Lock with Timeout)

设置锁获取的超时时间,如果无法在一定时间内获取到锁,就放弃。

if(lock.tryLock(500, TimeUnit.MILLISECONDS)) {
    try {
        // do something
    } finally {
        lock.unlock();
    }
} else {
    // 获取锁失败,执行其他逻辑或重试
}

这种方式可以有效避免长时间等待。

3. 死锁检测机制

针对上面两种不适用的场景。那些不可能实现按序加锁并且锁超时也不可行的场景
使用数据结构记录线程和资源的持有与请求状态,在失败时主动检查是否形成了等待环。

当检测到环路时:

  • 某些线程主动释放锁虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁,原因同超时类似,不能从根本上减轻竞争
  • 或者优先级较低的线程撤退一段时间再重试

这种方式适合无法提前安排加锁顺序的复杂应用场景。


总结

方法是否允许死锁发生是否易于实现是否影响性能
预防死锁较简单
避免死锁
死锁检测
解除死锁复杂低(只在死锁发生时影响)

🧪真实驱动例子:互斥访问设备寄存器

假设我们要编写一个字符设备驱动,多个进程可能并发调用 read() 操作,访问同一片寄存器区域。

临界区:

static DEFINE_SPINLOCK(my_lock);

ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off) {
    unsigned long flags;
    spin_lock_irqsave(&my_lock, flags);
    
    // 临界区:访问共享寄存器
    data = ioread32(dev->reg_base);
    
    spin_unlock_irqrestore(&my_lock, flags);
    return 0;
}

注意:用 spin_lock_irqsave 是因为中断中也可能调用,必须禁止中断防止死锁!


🧠Q&A 常见问题

Q:单核CPU还需要加锁吗?
A:需要!因为即使单核,操作系统依然可以通过抢占调度让线程切换,导致共享变量被多个线程交叉访问。

Q:信号量可以用在中断中吗?
A:不能!因为信号量可能会休眠,而中断处理函数不能休眠,否则整个中断系统会挂死。

Q:spin_lock能不能睡眠?
A:不能!因为它禁止抢占,如果睡眠,系统可能无法调度其他任务,导致死锁。


✅总结

  • 多线程 + 共享资源 = 必须互斥
  • 自旋锁适合临界区非常短的场景;信号量适合长时间、可睡眠的场景
  • 死锁问题复杂,要尽量规避:统一加锁顺序、设置超时、图算法检测
  • 在驱动中使用锁时要特别考虑上下文(中断/进程)和是否可休眠
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tt555555555555

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值