说明
本文为个人学习实验记录,如有疏漏,错误等,欢迎指正
代码参考了网上的公开资源(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