驱动里的并发控制--互斥锁

说明

本文为个人学习实验记录,如有疏漏,错误等,欢迎指正

代码参考了网上的公开资源(deepseek搜集提供)

相关概念

竞态,并发访问导致竞态

互斥锁,保护共享资源

环境

win10 + VMware + ubuntu16.04

实验

多线程调用write,不加锁访问同一个变量,出现错乱

代码1. 无锁,并发竞争(代码注释,详见代码2)

复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#define PROC_NAME "test_counter"

struct demo_dev {
    spinlock_t lock;
    int counter;
    struct timer_list timer;
};

static struct demo_dev dev;

/* 定时器回调函数,模拟中断上下文 */
static void timer_callback(unsigned long data)
{
    struct demo_dev *d = (struct demo_dev *)data;
    unsigned long flags;

    //spin_lock_irqsave(&d->lock, flags);
    d->counter++;
    printk(KERN_INFO "[timer] counter = %d\n", d->counter);
    //spin_unlock_irqrestore(&d->lock, flags);

    /* 重新启动定时器,100ms 后再次触发 */
    mod_timer(&d->timer, jiffies + msecs_to_jiffies(1));
}

/* 读取 /proc/test_counter 时显示当前 counter 值 */
static int counter_show(struct seq_file *m, void *v)
{
    unsigned long flags;
    int val;

    //spin_lock_irqsave(&dev.lock, flags);
    val = dev.counter;
    //spin_unlock_irqrestore(&dev.lock, flags);

    seq_printf(m, "%d\n", val);
    return 0;
}

/* 写入 /proc/test_counter 时,每写入一次(任意内容)就增加 counter */
static ssize_t counter_write(struct file *file, const char __user *buf,
                             size_t len, loff_t *off)
{
    unsigned long flags;

    //spin_lock_irqsave(&dev.lock, flags);
    //dev.counter++;
    int tmp = dev.counter;
    volatile int dummy;
    int i;
    //for (i = 0; i < 1000000; i++) dummy++;
    msleep(1);
    dev.counter = tmp + 1;
    printk(KERN_INFO "[write] counter = %d\n", dev.counter);
    //spin_unlock_irqrestore(&dev.lock, flags);

    return len;
}

static int counter_open(struct inode *inode, struct file *file)
{
    return single_open(file, counter_show, NULL);
}

static const struct file_operations counter_fops = {
    .owner   = THIS_MODULE,
    .open    = counter_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release,
    .write   = counter_write,
};

static int __init demo_init(void)
{
    /* 初始化自旋锁 */
    spin_lock_init(&dev.lock);
    dev.counter = 0;

    /* 初始化定时器(兼容 4.9 的旧版 API) */
    //setup_timer(&dev.timer, timer_callback, (unsigned long)&dev);
    //mod_timer(&dev.timer, jiffies + msecs_to_jiffies(1));

    /* 创建 /proc/test_counter 文件 */
    if (!proc_create(PROC_NAME, 0666, NULL, &counter_fops)) {
        printk(KERN_ERR "Failed to create /proc/%s\n", PROC_NAME);
        return -ENOMEM;
    }

    printk(KERN_INFO "spinlock_demo loaded. Use 'echo 1 > /proc/test_counter' to test.\n");
    return 0;
}

static void __exit demo_exit(void)
{
    /* 删除定时器,避免模块卸载后仍被触发 */
    del_timer_sync(&dev.timer);
    remove_proc_entry(PROC_NAME, NULL);
    printk(KERN_INFO "spinlock_demo unloaded.\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Spinlock demo with timer and proc interface");

编译 得到 ko

在qemu模拟的板卡上加载

写一个多线程写的脚本

复制代码
#!/bin/sh
for i in $(seq 1 10); do (for j in $(seq 1 500); do echo 1 > /proc/test_counter; done) & done
wait
cat /proc/test_counter

启10个线程,每个线程独立进行500次写,每次写加1,

理论上这些线程独立,/proc/test_counter 应该是5000

运行,数据是乱的

加互斥锁保护

复制代码
结构体 demo_dev 里增加成员
struct mutex my_mutex

demo_init 函数里初始化互斥锁
mutex_init(&dev.my_mutex);

在 counter_write 函数里使用
mutex_lock()
访问要保护的资源
mutex_unlock()

代码2. 加了互斥锁的完整代码(其实注释也不算详细)

复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#define PROC_NAME "test_counter"

struct demo_dev {
    spinlock_t lock;  //目前没用到,可用于对比互斥锁和自旋锁
    int counter;
    struct timer_list timer; //目前没用到,可用于模拟线程和中断的竞态
    struct mutex my_mutex; //本次的主角
};

static struct demo_dev dev;

/* 定时器回调函数,模拟中断上下文。本次实验没用到,可以忽略 */
static void timer_callback(unsigned long data)
{
    struct demo_dev *d = (struct demo_dev *)data;
    unsigned long flags;

    //spin_lock_irqsave(&d->lock, flags);//至于为啥有这么多注释的自旋锁,完全是开始想做自旋锁的实验
    d->counter++;
    printk(KERN_INFO "[timer] counter = %d\n", d->counter);
    //spin_unlock_irqrestore(&d->lock, flags);

    /* 重新启动定时器,100ms 后再次触发 */
    mod_timer(&d->timer, jiffies + msecs_to_jiffies(1));
}

/* 读取 /proc/test_counter 时显示当前 counter 值 */
static int counter_show(struct seq_file *m, void *v)
{
    unsigned long flags;
    int val;

    //spin_lock_irqsave(&dev.lock, flags);
    val = dev.counter;
    //spin_unlock_irqrestore(&dev.lock, flags);

    seq_printf(m, "%d\n", val);
    return 0;
}

/* 写入 /proc/test_counter 时,每写入一次(任意内容)就增加 counter */
static ssize_t counter_write(struct file *file, const char __user *buf,
                             size_t len, loff_t *off)
{
    unsigned long flags;

    //spin_lock_irqsave(&dev.lock, flags);
    //dev.counter++;
    
    mutex_lock(&dev.my_mutex);
    int tmp = dev.counter;
    volatile int dummy;
    int i;
    //for (i = 0; i < 1000000; i++) dummy++;  //这里为啥要一个注释掉的空操作呢?当然是为了后面用自旋锁的
    msleep(1); //伪装的耗时操作,cpu会睡眠的哦,注意用自旋锁保护,是不能这样睡的,听说mdelay可以
    dev.counter = tmp + 1;
    mutex_unlock(&dev.my_mutex);
    printk(KERN_INFO "[write] counter = %d\n", dev.counter);
    //spin_unlock_irqrestore(&dev.lock, flags);

    return len;
}

static int counter_open(struct inode *inode, struct file *file)
{
    return single_open(file, counter_show, NULL);
}

static const struct file_operations counter_fops = {
    .owner   = THIS_MODULE,
    .open    = counter_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release,
    .write   = counter_write,
};

static int __init demo_init(void)
{
    /* 初始化自旋锁 */
    spin_lock_init(&dev.lock);
    dev.counter = 0;

    mutex_init(&dev.my_mutex);
    printk(KERN_INFO "init mutex ok\n");
    /* 初始化定时器(兼容 4.9 的旧版 API) */
    //setup_timer(&dev.timer, timer_callback, (unsigned long)&dev);
    //mod_timer(&dev.timer, jiffies + msecs_to_jiffies(1));

    /* 创建 /proc/test_counter 文件 */
    if (!proc_create(PROC_NAME, 0666, NULL, &counter_fops)) {
        printk(KERN_ERR "Failed to create /proc/%s\n", PROC_NAME);
        return -ENOMEM;
    }

    printk(KERN_INFO "spinlock_demo loaded. Use 'echo 1 > /proc/test_counter' to test.\n");
    return 0;
}

static void __exit demo_exit(void)
{
    /* 删除定时器,避免模块卸载后仍被触发 */
    del_timer_sync(&dev.timer);
    remove_proc_entry(PROC_NAME, NULL);
    printk(KERN_INFO "spinlock_demo unloaded.\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Spinlock demo with timer and proc interface");

再次运行

这次是预期的 5000了

参考

《linux设备驱动开发详解:基于最新的Linucx4.0内核》

deepseek

相关推荐
小Y._3 天前
ConcurrentHashMap高效并发机制深度解析
java·并发·juc·concurrenthashmap
趣魂4 天前
五种并发/异步模型整理
并发·异步
lee_curry6 天前
Java中关于“锁”的那些事
java·线程·并发·juc
SudosuBash7 天前
[A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型
并发·进程和线程·内存缓存一致性·多核编程·a primer on mc and cc
坐吃山猪7 天前
Python29_并发编程
开发语言·网络·python·并发
lee_curry9 天前
JUC第一章 java中基础概念和CompletableFuture
java·多线程·并发·juc
丁劲犇12 天前
改造传统Qt6Widgets程序为多会话MCPServer生产力工具-技巧与实现
qt·ai·agent·并发·mcp·mcpserver·widgets
Echoo华地14 天前
Gatling压测案例
java·jmeter·压力测试·并发·scale·压测·gatling
ん贤15 天前
Go 并发高频十问:goroutine 与线程的区别是什么?select 底层原理是什么?
开发语言·golang·并发