一、自旋锁:解决复杂临界区的轻量级方案
原子操作仅能保护整型或位变量,但实际开发中,像设备结构体这类复杂数据,需要确保对其成员的操作具备原子性——自旋锁正是为此类需求设计的锁机制。
它的工作逻辑很直观:当线程要访问共享资源,必须先获取锁;同一把锁仅能被一个线程持有,只要持有者不释放,其他线程就无法获取。若锁已被占用,等待的线程不会进入休眠,而是进入“忙循环”状态,原地反复查询锁是否可用,直到锁被释放——这就是“自旋”的含义,类比为“电话亭前等人打完电话”,期间只能原地等待,不能做其他事。
不过自旋锁有个明显短板:等待线程持续占用CPU资源,会浪费处理器性能,因此它的持有时间必须极短,仅适用于短周期的轻量级加锁,长期持锁场景需搭配其他机制。
使用前需先定义锁变量,再借助内核提供的API函数操作锁。
二、自旋锁核心API与场景适配
基础线程同步:基础的自旋锁API适用于多核处理器或支持抢占的单核系统,专门用于线程间的并发访问。需牢记:被自旋锁保护的临界区,绝对不能调用会导致线程休眠或阻塞的函数,否则极易触发死锁。
应对中断与线程的并发:中断能使用自旋锁,但风险极高——若线程持有锁时被中断打断,中断函数尝试获取同一把锁,双方会互相等待,直接引发死锁。解决方式是:在获取锁前关闭本地中断,释放锁后再恢复中断。
推荐优先用能自动保存、恢复中断状态的API,这类函数能避免因难以判断实时中断状态引发的风险。通常,线程侧使用“保存中断状态+获取锁”的函数;中断服务程序则直接用基础的锁操作函数。
处理下半部的并发:下半部是内核中用于延迟执行任务的机制,若下半部需访问共享资源,需用专门的API——获取锁时关闭下半部,释放锁时开启下半部,以此保障资源访问的互斥性。
三、自旋锁的衍生类型
为适配不同业务场景,自旋锁衍生出多种专用锁,驱动开发中常用的有两种:
读写自旋锁:多读少写场景的最优解针对“可并发读取、但读写不能同时进行”的场景,比如存放学生信息的数据表——允许多人同时查询,但修改数据时必须保证无人读取。
读写自旋锁的核心优势是:多个读操作可并发执行,写操作却保持独占,既提升了读效率,又保障了数据安全。使用时,读操作和写操作对应不同的锁操作接口,根据实际场景区分调用即可。
顺序锁:允许读写并发的特殊方案顺序锁在读写锁基础上升级,允许读操作和写操作同时进行,仅禁止多个写操作并发。不过使用时有明确限制:被它保护的资源不能是指针,因为写操作过程中可能让指针失效,若此时读操作访问失效指针,会直接引发系统崩溃。
它的运作逻辑基于序列号:读者先获取当前顺序号,读取结束后检查顺序号是否变化,若变化说明期间有写操作,需重新读取,以此保证数据完整性;写操作则需先获取写锁,确保自身独占资源。
四、自旋锁的使用铁律
掌握以下几点,能避免多数使用风险,保障系统稳定:
持锁时间要极短:等待锁时线程处于自旋状态,会持续消耗CPU资源,大幅拉低系统性能,因此锁的持有时间必须控制在极短范围。临界区较大、运行时间较长时,建议换用信号量、互斥体等其他同步方式。
临界区禁止休眠:被自旋锁保护的临界区里,绝不能调用会导致线程休眠的函数,否则休眠的线程无法释放锁,等待的线程又持续自旋,最终必然陷入死锁。
拒绝递归加锁:不能用递归的方式重复申请自己已持有的自旋锁——此时线程既在自旋等待锁释放,又无法主动释放锁,相当于把自己锁死,程序会彻底卡顿。
按多核思维开发驱动:编写驱动时,无论硬件是单核还是多核芯片,都要按多核的逻辑设计,以此保证驱动的可移植性,适配不同硬件环境。
选对中断/下半部操作接口:中断、下半部访问共享资源时,必须搭配对应场景的锁操作接口,比如关闭中断或下半部的加锁方式,避免并发引发的冲突。
顺序锁避开指针资源:使用顺序锁时,被保护的资源不能包含指针,防止写操作导致指针失效,读操作引发系统崩溃。