Linux驱动开发实战(十三):RGB LED驱动并发控制——自旋锁与信号量对比详解

Linux驱动开发实战(十三):RGB LED驱动并发控制------自旋锁与信号量对比详解


文章目录


前言

在Linux驱动开发中,并发控制是一个核心问题。当多个进程同时访问同一个硬件设备时,如果没有合理的保护机制,就会出现数据竞争、资源冲突等问题。

想象一下这个场景:

  • 进程A正在控制RGB LED显示红色
  • 进程B同时也想控制LED显示蓝色
  • 没有保护机制的话,LED的状态会混乱
    本文将通过一个RGB LED驱动实例,用最通俗易懂的方式对比讲解Linux内核中两种最常用的并发控制机制:

自旋锁(Spinlock) - 忙等待机制

信号量(Semaphore) - 睡眠等待机制


一、生动比喻:理解自旋锁与信号量

单人卫生间问题

假设公司只有一个卫生间(共享资源),多个员工(进程/线程)需要使用它。如何保证同一时刻只有一个人在使用?


自旋锁:门口原地转圈等

小明的做法(自旋锁方式):

小明急需使用卫生间,走到门口

如果门锁着(资源被占用):

站在门口,疯狂拧门把手

"能开吗?不能!"

"能开吗?不能!"

"能开吗?不能!"

... 不停尝试 ...

一旦门开了,立刻冲进去!

如果门开着(资源空闲):

直接进去,反锁门
特点分析:

✅ 反应超快:门一开,立刻进去,零延迟

✅ 适合急事:如果前面的人很快出来(1-2秒),这种方式最高效

❌ 消耗体力:一直在门口转圈,不停拧门把手,累得满头大汗

❌ 不适合长时间:如果前面的人蹲半小时,小明会虚脱

内核对应:

c 复制代码
spin_lock(&lock);      // 疯狂拧门把手,直到打开
// 使用卫生间(临界区代码)
spin_unlock(&lock);    // 开门出来

信号量:排队休息等

小红的做法(信号量方式):

小红需要使用卫生间,走到门口

如果门锁着(资源被占用):

在旁边的休息室坐下(进入睡眠)

???(什么都不做,睡觉)

前面的人出来后会叫醒她:"小红,轮到你了!"

小红醒来,走进卫生间

如果门开着(资源空闲):

直接进去,反锁门
特点分析:

✅ 节省体力:在休息室睡觉,完全不消耗体力(CPU资源)

✅ 适合长时间:前面的人蹲多久都没关系,反正我在睡觉

❌ 反应慢:需要被叫醒→站起来→走到卫生间(上下文切换开销)

❌ 短时间不划算:如果前面的人1秒就出来,这套流程反而慢

内核对应:

c 复制代码
down(&sem);           // 尝试进入,如果不行就睡觉
// 使用卫生间(临界区代码)
up(&sem);             // 开门出来,叫醒下一个人

二、实战代码解析:自旋锁版本

核心数据

c 复制代码
spinlock_t s_lock;   // 自旋锁变量
char flag;           // 设备占用标志:0=空闲,非0=占用

为什么需要flag?

  • 自旋锁本身只保护临界区,不记录状态
  • flag用于记录设备是否被占用
  • 自旋锁保护flag变量的读写操作

初始化:驱动加载时

c 复制代码
static int __init led_platform_driver_init(void)
{
    int DriverState;
    
    /* 初始化自旋锁 */
    spin_lock_init(&s_lock);
    
    DriverState = platform_driver_register(&led_platform_driver);
    printk(KERN_ALERT "\tDriverState is %d\n", DriverState);
    return 0;
}
}

打开设备时的自旋锁保护

c 复制代码
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
    /* 获取自旋锁(原子操作开始) */
    spin_lock(&s_lock);
    
    if(flag) {
        /* 设备正在被使用,释放锁并返回错误 */
        spin_unlock(&s_lock);
        printk("\n driver on using!  open failed !!!\n");
        return -EBUSY;  // 返回设备忙错误
    }
    else {
        /* 设备空闲,标记为占用 */
        flag++;
    }
    
    /* 释放自旋锁(原子操作结束) */
    spin_unlock(&s_lock);
    
    printk("\n open form driver \n");
    return 0;
}

关闭设备时释放标志

c 复制代码
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
    /* 获取自旋锁 */
    spin_lock(&s_lock);
    
    if(flag)
        flag--;  // 清除占用标志
    
    /* 释放自旋锁 */
    spin_unlock(&s_lock);
    
    printk("KERN_ALERT  \n finished  !!!\n");
    return 0;
}

实验现象

如果第二次太快打开就会直接返回打开失败

三、实战代码解析:信号量版本代码解析

关键变量

c 复制代码
struct semaphore sem;  // 定义信号量结构体

信号量初始化

c 复制代码
static int __init led_platform_driver_init(void)
{
    int DriverState;
    
    /* 初始化信号量,初始值为1(二值信号量,相当于互斥锁) */
    sema_init(&sem, 1);
    
    DriverState = platform_driver_register(&led_platform_driver);
    printk(KERN_ALERT "\tDriverState is %d\n", DriverState);
    return 0;
}

说明:sema_init(&sem, 1) 将信号量初始化为1,表示资源可用,只允许一个进程访问。

打开设备时获取信号量

c 复制代码
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
    /* 获取信号量,如果信号量值为0,进程将睡眠等待 */
    down(&sem);
    
    printk("\n open form driver \n");
    return 0;
}

关闭设备时释放信号量

c 复制代码
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
    /* 释放信号量,唤醒等待队列中的进程 */
    up(&sem);
    
    printk("KERN_ALERT  \n finished  !!!\n");
    return 0;
}

说明:up(&sem) 将信号量值+1,如果有进程在等待,则唤醒它。

实验现象

在上一个命令没执行完就执行下一个命令的话(很急)

要等待上一个没执行完的命令执行完再执行下一个命令(别急)


总结

信号量:像银行取号,拿到号就等着,轮到你自然会叫你

自旋锁:像查快递柜,有就拿,没有就走,下次再来看

适用场景

  • 信号量:文件I/O、网络、设备独占、长临界区
  • 自旋锁:中断处理、计数器、状态检查、短临界区
  1. 持有自旋锁时绝不能睡眠
  2. 中断上下文绝不能用信号量
  3. 自旋锁临界区要尽可能短
  4. 不确定时优先选择信号量(更安全)
相关推荐
莫回首�3 小时前
ubuntu 20.04 多网卡配置,遇到问题总结
linux·网络·ubuntu
网络安全许木3 小时前
自学渗透测试第11天(Linux压缩解压与磁盘管理)
linux·网络安全·渗透测试
沫璃染墨5 小时前
C++ string 从入门到精通:构造、迭代器、容量接口全解析
c语言·开发语言·c++
UTP协同自动化测试6 小时前
物联网模组测试难点 |APP指令下发+UART 响应+GPIO 电平变化,如何一次性验证?
功能测试·嵌入式硬件·物联网·模块测试
lifewange6 小时前
Linux ps 进程查看命令详解
linux·运维·服务器
功德+n6 小时前
Linux下安装与配置Docker完整详细步骤
linux·运维·服务器·开发语言·docker·centos
计算机安禾7 小时前
【数据结构与算法】第36篇:排序大总结:稳定性、时间复杂度与适用场景
c语言·数据结构·c++·算法·链表·线性回归·visual studio
左手厨刀右手茼蒿7 小时前
Linux 内核中的块设备驱动:从原理到实践
linux·嵌入式·系统内核
杨云龙UP7 小时前
从0到1快速学会Linux操作系统(基础),这一篇就够了!
linux·运维·服务器·学习·ubuntu·centos·ssh