# 无处不在的锁——学习Linux内核中的锁
“锁”的概念与我们生活中的物品“锁”的意义和属性是一致的。生活中的“锁”为什么要存在于我们的Linux内核中呢?都是保护某些资源不被别人私自,擅自使用。
那么要保护哪些资源?哪些数据呢?先简单总结一下:保护静态局部变量,全局变量,共享的数据结构,缴存,链表,红黑树等;对于资源,比如I2C0外设,Usart外设等。
为什么要保护这些资源与数据呢?我在编写C语言的STM32程序时,没有使用过“锁”呀?其实,我们今天讨论的“‘锁”的概念其应用范围是并发访问。并发访问包括线程或进程切换时,对共享资源的访问。
还有一个基础术语“临界区”。临界区是指访问和操作共享数据的代码段。其中的资源无法同时被多个执行线程访问,访问临界区的执行线程或代码路径称为并发源。开发者必须保证访问临界区的原子性。即在临界区内不能有多个并发源同时执行。整个临界区像一个不可分割的整体。
了解了临界区,那么如何实现临界区就依靠锁机制。简单讲,在临界区进入前”上锁“在出临界区后”开锁“。说得确实简单了一点儿!但使用单一形式的”锁“,在不同应用场景下,对系统性能影响是巨大的。所以,Linux内核大佬们研发设计出了多种锁。接下来,我们就看看这些锁适用在什么场景下。
## 自旋锁Spin Lock
自旋锁是Linux内核中最常见的锁机制之一,其在同一时刻只能被一个内核线程持有。当有另一个线程试图获取一个已经被持有的自旋锁,则该线程将一直忙等待,直到当前自旋锁持有者释放。自旋锁的特性如下:
1. 忙等待的锁机制。当无法获取自旋锁时,不断尝试,直接获取锁为止
2. 同一时刻只能有一个内核线程获得该锁
3. 使用自旋锁完成的临界区执行的任务要尽快完成。等待自旋锁的线程将占用CPU大量时间,进行原地踏步,不会进入休眠状态
4. 自旋锁可在中断上下文中使用
对自旋锁的应用,我个人觉得,是将其应用到队列的入队和出队操作上。在入队和出队需要进行队列计数的更新,使用自旋锁可以在快速更新后,快速释放。如果此刻,有其它线程在等待,则亦可快速获取到自旋锁。
## 互斥锁Mutex Lock
我在使用锁时,对于软件变量多采用自旋锁,而对于硬件资源的占用,则更多使用互斥锁。互斥锁也是在并行处理环境中对多个处理器访问某个公共资源进行保护的机制。因为其仅包含”0“与”1“操作,所以其较信号量更加轻便。
对于互斥锁,我在实际项目中,多用在硬件外设资源的调配上。比如I2C外设操作EEPROM。当我们需要将诸如不同配置参数写入到EEPROM时,各个不相关模块线程均调入同一个I2C外设操作,作用于同一个EEPROM器件。这时,使用互斥锁来实现谁持有,谁操作。未持有者可进行休眠处理。
生活中有各式各样地锁,在Linux内核中亦有多种锁以适应不同的场景,用于提升系统整体效率。本文讲解、分析了自旋锁与互斥锁两种实际项目中常用的锁,还有其它锁亦非常重要,如读写锁,RCU锁。限于篇幅,在未来的工作学习中再继续总结、分享。