文章目录
前言
软硬件环境
硬件: PC
软件:ubuntu18.04
本文主要介绍linux 内核同步方法中的自旋锁, 它是一种轻量级的锁,和原子操作,互斥锁,信号量等内核同步方法的作用相同,都是为了保护临界区。自旋锁在内核中主要是用来防止多处理器(SMP)中并发访问临界区,防止内核抢占造成的竞争。
Linux内核是一种抢占式内核。
一、什么是自旋锁
1. 自旋锁的含义
自旋锁( spin lock)是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式。当锁被某个线程持有后,有其他线程试图再次执行加锁操作的时候,所有这些试图获取锁的线程不会阻塞,而是处于循环等待的忙等状态(CPU不能够做其他事情),直到锁重新可用。
自旋锁主要针对 SMP 或单 CPU 但内核可抢占的情况,对于单 CPU 和内核不支持抢占的系统, 自旋锁退化为空操作,自旋锁最基础的功能就是禁止内核抢占。
2. 单处理器的自旋锁
- 如果系统不支持内核抢占,自旋锁的实现是空的,因为单核只有一个线程在执行,不会有内核抢占,从而资源也不会被其他线程访问到;
- 如果系统支持内核抢占,由于自旋锁是禁止抢占内核的,所以不会有其他的进程因为等待锁而自旋;
3. 多处理器(SMP)的自旋锁
- Linux内核是一种抢占式内核,在多cpu下,由于自旋锁是禁止抢占内核的,其他的cpu因为等待该cpu释放锁,而处于自旋状态,不停轮询锁的状态。所以这样的话,如果一旦自旋锁内代码执行时间较长,等待该锁的cpu会耗费大量资源,也是不同于信号量和互斥锁的地方。
二、自旋锁及其衍生型
尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但是得到锁的代码在执行临界区的时候,还可能受到中断和底半部( BH)的影响。为了防止这种影响,就需要用到自旋锁 (spin_lock()/spin_unlock()是自旋锁机制的基础) 的衍生型, 下图是基础型自旋锁及其最常用的两种衍生型的接口描述。
函数(方法) | 描述 |
---|---|
spin_lock() | 获取指定的自旋锁 |
spin_lock_irq() | 先禁止本地中断然后获取指定的自旋锁 |
spin_lock_irqsave() | 先保存本地中断的当前状态,之后禁止本地中断并获取指定的自旋锁 |
spin_unlock() | 释放指定的自旋锁 |
spin_unlock_irq() | 释放指定的自旋锁,并激活本地中断 |
spin_unlock_irqrestore() | 释放指定的自旋锁,并让本地中断恢复到之前的状态 |
以上三种形式的自旋锁,需根据实际情况来选用, 一般在SMP的Linux内核中spin_lock_irqsave()/spin_unlock_irqrestroe()用的最多,特别是在中断处理函数中。
三、自旋锁的使用实例
自旋锁一般这样被使用:
spinlock_t lock; //定义自旋锁
spin_lock_init(&lock); //初始化自旋锁
spin_lock (&lock) ; //获取自旋锁,保护临界区
//临界区代码
spin_unlock (&lock) ; //解锁
1. spin_lock()/spin_unlock()
如下图所示,是一个最简的spin_lock()/spin_unlock()使用实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h> //kzalloc 头文件
#include <linux/spinlock.h> //spinlock 头文件
struct spin_test {
int num; // data
spinlock_t lock; // spin lock
};
struct spin_test *s_test = NULL;
static int hello_init(void)
{
printk(KERN_INFO "in hello init\n");
s_test = kzalloc(sizeof(struct spin_test), GFP_KERNEL);
if ( s_test == NULL) {
printk(KERN_INFO "kzalloc failed\n");
return -ENOMEM;
}
spin_lock_init(&s_test->lock); //spin_lock init 初始化自旋锁
spin_lock(&s_test->lock);