linux内核spin_lock分析

本文详细解析了内核自旋锁的实现,包括spin_lock_init函数的初始化过程,spin_lock和spin_unlock宏的加锁与解锁机制,以及自旋锁的工作原理和循环特性。

今天我们详细了解一下spin_lock在内核中代码实现,我们总共分析四个项目:

 

1.spinlock_t的定义分析:

首先来看一下spinlock_t的定义:

typedef struct {

       raw_spinlock_t raw_lock;

#if defined(CONFIG_PREEMPT) &&defined(CONFIG_SMP)

       unsigned int break_lock;

#endif

#ifdef CONFIG_DEBUG_SPINLOCK

       unsigned int magic, owner_cpu;

       void *owner;

#endif

} spinlock_t;

从上面代码来分析一个完整的spinlock_t的结构有5个成员:raw_lock/ break_lock/ magic/ owner_cpu/ owner,但是这5个成员都没有初始值,所以显然要一个函数去初始化它们。

 

2. spin_lock_init函数分析

我们通常用spinlock_tlock来定义一把自旋锁,然后要初始化自旋锁通过函数spin_lock_init(&lock);这个函数的定义为

/**********************************************************************/

#define spin_lock_init(lock)   do { *(lock) = SPIN_LOCK_UNLOCKED; } while(0)

/**********************************************************************/

从代码分析,所谓初始化就是把一个unlock宏的值赋给lock,从字面的意思来看就是把锁初始化为解锁,那么我们再追踪这个宏的定义:

# defineSPIN_LOCK_UNLOCKED                                        /

       (spinlock_t)   {     .raw_lock= __RAW_SPIN_LOCK_UNLOCKED,       /

                            .magic= SPINLOCK_MAGIC,            /

                            .owner= SPINLOCK_OWNER_INIT,        /

                            .owner_cpu= -1 }

这样就很清晰了,初始化的目的就是把spinlock_t中的各个成员赋了初始值。

 

第一个成员raw_lock被赋予了__RAW_SPIN_LOCK_UNLOCKED是什么意思呢,继续追踪,这个宏在include/linuxspinlock_types_up.h中定义的为:

#define __RAW_SPIN_LOCK_UNLOCKED { 1 } 所以锁初始化时是把成员raw_lock赋值为1,即解锁状态。其他的初始值的含义我尚不了解

 

3. 加锁宏spin_lock(lock)宏的分析:

宏的定义如下:   

#define spin_lock(lock)               _spin_lock(lock)

继续追踪其中的函数_spin_lock(lock)定义如下:

void __lockfunc _spin_lock(spinlock_t*lock)

{

       preempt_disable();

       _raw_spin_lock(lock);

}

这个函数核心就是_raw_spin_lock函数,柯南继续追踪:

void _raw_spin_lock(spinlock_t *lock)

{

       debug_spin_lock_before(lock);

       if(unlikely(!__raw_spin_trylock(&lock->raw_lock)))

              __spin_lock_debug(lock);

       debug_spin_lock_after(lock);

}

Debug的不管,那么核心函数就是__spin_lock_debug(lock),快去看看它的定义吧:

static void __spin_lock_debug(spinlock_t*lock)

{

       intprint_once = 1;

       u64i;

       for(;;) {

              for(i = 0; i < loops_per_jiffy * HZ; i++) {

                     cpu_relax();//空函数,不知道用意是什么

                     if(__raw_spin_trylock(&lock->raw_lock))

                            return;

              }

              /*lockup suspected: */

              if(print_once) {

                     print_once= 0;

                     printk("BUG:spinlock lockup on CPU#%d, %s/%d, %p/n",

                            smp_processor_id(),current->comm, current->pid,

                                   lock);

                     dump_stack();

              }

       }

}

哈哈,看到for (;;)就知道死循环了,那么自旋锁很明显时不会让出CPU的,除非它能够加锁成功,否则就一直自旋吧!其中HZ是CPU相关的,一个CPU时钟每秒中跳动HZ次,这个值就是一个jiffes值。对于ARM来说1秒跳动100次,HZ为100,对于X86/PPC: 1000。loops_per_jiffy= (1<<12),转化十进制为4096。两个相乘的含义就是每个jiffes中进行4096次循环,而一秒钟里面用HZ个jiffes,两个值相乘得到的就是1秒钟进行循环的次数。从这个for循环来看,就是在1秒内不断循环进行__raw_spin_trylock动作(),如果成功就跳出死循环,如果不成功就继续循环了,这样就完成了自旋的功能,而且进程不会睡眠。

static inline int __raw_spin_trylock(raw_spinlock_t *lock)
{
    unsigned long tmp;

    __asm__ __volatile__(
"    ldrex    %0, [%1]\n"
"    teq    %0, #0\n"
"    strexeq    %0, %2, [%1]"
    : "=&r" (tmp)
    : "r" (&lock->lock), "r" (1)
    : "cc");

    if (tmp == 0) {
        smp_mb();
        return 1;
    } else {
        return 0;
    }
}

 

ARM指令LDREX和STREX可以保证在两条指令之间进行的取值-测试操作的原子性,假设有进程A和B都试图调用上述函数获得写入锁,那么谁先执行LDREX指令谁将先获得锁。 首先这段汇编是AT&T内联汇编,首先%0代表=&r" (tmp),%1代表 "r" (&lock->lock),%2代表 "r" (1),以此类推,这样的话首先从lock中把值取出来,放到tmp里面,然后用测试指令比较tmp是否为0,如果是0代表代表没有人获得锁。然后使用strex指令把lock的值设为1,获取锁。如果lock的值不为0,表明之前该锁已经被被其他的进程所使用,那么该进程将自旋。


4. 加锁宏spin_lock(lock)宏的分析:

#define spin_unlock(lock)           _spin_unlock(lock)

无需多言

void __lockfunc _spin_unlock(spinlock_t*lock)

{

       _raw_spin_unlock(lock);

       preempt_enable();

}

这个函数中先调用_raw_spin_unlock,然后preempt_enable(什么含义,fixme)。

void _raw_spin_unlock(spinlock_t *lock)

{

       debug_spin_unlock(lock);

       __raw_spin_unlock(&lock->raw_lock);

}

继续追踪__raw_spin_unlock(&lock->raw_lock)内核真的很能绕弯子,大家慢慢习惯了

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
    smp_mb();

    __asm__ __volatile__(
"    str    %1, [%0]"
    :
    : "r" (&lock->lock), "r" (0)
    : "cc");
}


不就是给lock成员赋值0嘛,呵呵,解锁完成。

 

至此完成自旋锁的初步分析,基本脉络清楚了,代码中还有部分不了解含义,以后再研究,到此休息一下。

你遇到的编译错误: ``` net/ethernet/eth.c:64:29: fatal error: linux/spin_lock.h: No such file or directory #include <linux/spin_lock.h> ``` 是一个典型的 **Linux 内核头文件包含错误**。我们来详细分析原因,并给出正确解决方案。 --- ## ✅ 直接答案 > ❌ 错误原因: > 你不应该直接包含 `<linux/spin_lock.h>`,这个头文件 **不存在于标准内核 API 中**! > ✅ 正确做法: > 改为包含: ```c #include <linux/spinlock.h> ``` 📌 注意: - 文件名是 `spinlock.h`,不是 `spin_lock.h` - 这是 Linux 内核中用于自旋锁的标准头文件 --- ## 🧱 一、为什么会出错? ### 1. 拼写错误(常见!) 你写了: ```c #include <linux/spin_lock.h> // ❌ 错了!没有这个文件 ``` 但正确的文件是: ```c #include <linux/spinlock.h> // ✅ 正确! ``` 在 Linux 内核源码树中: ```bash $ find . -name "spinlock.h" ./include/linux/spinlock.h ``` → 根本没有叫 `spin_lock.h` 的文件。 --- ## 🔍 二、`<linux/spinlock.h>` 提供了什么功能? 这个头文件定义了内核中最基本的 **自旋锁(spinlock)机制**,常用于多处理器环境下的临界区保护。 常用接口包括: | 函数 | 说明 | |------|------| | `spin_lock_init(&lock)` | 初始化 spinlock_t 变量 | | `spin_lock(&lock)` | 获取锁(忙等) | | `spin_unlock(&lock)` | 释放锁 | | `spin_lock_irqsave()` / `spin_unlock_irqrestore()` | 关中断 + 加锁,防止死锁 | 示例代码: ```c #include <linux/spinlock.h> static DEFINE_SPINLOCK(eth_lock); static int packet_count = 0; void eth_update_count(void) { unsigned long flags; spin_lock_irqsave(&eth_lock, flags); packet_count++; spin_unlock_irqrestore(&eth_lock, flags); } ``` --- ## 🛠 三、如何修复你的代码? ### 修改前(错误): ```c #include <linux/spin_lock.h> // ❌ 编译失败 ``` ### 修改后(正确): ```c #include <linux/spinlock.h> // ✅ 正确路径 ``` 同时确保你也包含了其他必要的头文件(如果你用到了相关结构体): ```c #include <linux/spinlock.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/netdevice.h> ``` --- ## ⚠️ 四、可能引发此问题的几种场景 | 场景 | 原因 | 解决方法 | |------|------|---------| | 手动打字拼错 | `spin_lock.h` vs `spinlock.h` | 改成正确名字 | | 旧版本内核文档误导 | 某些老资料写错了 | 查官方源码或最新文档 | | IDE 自动补全建议错误 | 补全出了不存在的头文件 | 手动核对 | | 移植驱动时复制粘贴错误 | 从别的项目拷贝过来没检查 | 清理 include 列表 | --- ## 📦 五、验证头文件是否存在(开发技巧) 你可以进入内核源码目录检查: ```bash # 假设你在内核源码根目录 find include/linux -name "spin*.h" | grep -i spin ``` 输出应包含: ``` include/linux/spinlock.h include/linux/spinlock_api_smp.h include/linux/spinlock_types.h ... ``` → 看不到 `spin_lock.h` --- ## ✅ 六、完整修复示例 假设你的 `net/ethernet/eth.c` 文件中有如下内容: ```c #include <linux/config.h> #include <linux/spin_lock.h> // ❌ 报错行 #include <linux/netdevice.h> ``` 改为: ```c #include <linux/config.h> #include <linux/spinlock.h> // ✅ 修正 #include <linux/netdevice.h> #include <linux/module.h> #include <linux/kernel.h> ``` 然后重新编译: ```bash make -C /lib/modules/$(uname -r)/build M=$(pwd) modules ``` ✅ 错误消失。 --- ## 💡 附加建议:使用 `grep` 快速查找内核头文件 不知道某个功能该包含哪个头?可以用: ```bash # 查找所有含 "spinlock" 的头文件 grep -r "typedef.*spinlock_t" /usr/src/linux/include/ # 或搜索函数声明 grep -r "extern.*spin_lock" /usr/src/linux/include/ ``` 或者使用 `cscope` / `ctags` 构建索引更高效。 --- ## ✅ 总结 > ❓“为什么 `#include <linux/spin_lock.h>` 报错?” ✅ **根本原因:** - 头文件名为 `spinlock.h`,不是 `spin_lock.h` - Linux 内核命名习惯是连写(如 `spinlock.h`, `rwlock.h`, `mutex.h`),不用下划线分隔 ✅ **解决方法:** ```c #include <linux/spinlock.h> ``` ✅ **额外提醒:** - 编译内核模块时务必使用正确的头文件路径 - 不要依赖猜测或拼写自动补全 - 多查内核源码或官方文档(https://elixir.bootlin.com) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值