【Linux驱动开发】第6天:互斥锁mutex/自旋锁spinlock+驱动全流程+应用测试程序

目录

  1. mutex互斥锁 vs spinlock自旋锁:通俗讲解+区别+场景+限制
  2. 在字符设备驱动中加入锁保护(完整代码)
  3. 传统字符设备驱动完整执行流程:从insmod到应用读写全链路
  4. 应用层测试程序(C语言):完整测试read/write/open/close
  5. 核心总结+面试必背

1. mutex互斥锁 vs spinlock自旋锁:通俗讲解+区别+场景+限制

1.1 大白话类比

  • mutex(互斥锁) :去厕所,没人就进去锁门;有人就出去睡觉等待,不占CPU。
  • spinlock(自旋锁) :去厕所,没人就进去锁门;有人就原地转圈盯着,一直占着CPU直到门开。

1.2 核心区别(面试必考)

对比项 mutex(互斥锁) spinlock(自旋锁)
锁等待时 进程睡眠(放弃CPU) 原地自旋(占着CPU)
能否睡眠 可以睡眠 绝对不能睡眠
上下文限制 只能用在进程上下文 进程/中断上下文都能用
临界区大小 适合长耗时操作 适合极短操作
单核CPU 效率高 效率极低(空转)
多核CPU 适合长临界区 适合短临界区
中断上下文 ❌ 不能用 能用

1.3 使用场景(死记硬背)

  1. 进程上下文 + 临界区长(毫秒级)mutex
  2. 中断上下文 / 软中断 / tasklet必须 spinlock
  3. 进程上下文 + 临界区极短(微秒级) → spinlock
  4. 驱动数据结构保护、硬件寄存器操作 → spinlock

1.4 上下文限制(重中之重)

  • mutex:不能用在中断上下文! 会睡眠 → 系统死机
  • spinlock:持有锁期间不能睡眠、不能调用可能睡眠的API!
    (kmalloc、copy_to_user、msleep 都不行)

2. 在字符设备驱动中加入锁保护(完整代码)

2.1 加锁后的完整字符设备驱动

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>      // mutex
#include <linux/spinlock.h>   // spinlock

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Driver");
MODULE_DESCRIPTION("带锁保护的字符设备驱动");
MODULE_VERSION("1.0");

#define DEV_NAME    "my_chrdev_lock"
#define CLASS_NAME  "my_chr_class"
#define BUF_SIZE    1024

static dev_t dev_num;
static struct cdev chr_cdev;
static struct class *dev_class;
static struct device *dev_device;
static char *kernel_buf;

// ==================== 锁定义 ====================
// 方案1:mutex(推荐用于进程上下文、长操作)
static struct mutex chr_mutex;

// 方案2:spinlock(用于短操作、可在中断使用)
// static spinlock_t chr_spinlock;

static int chr_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "[chrdev] open\n");
    return 0;
}

static ssize_t chr_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
    int ret;
    size_t read_len = min(len, (size_t)BUF_SIZE);

    // ========== 加锁 ==========
    mutex_lock(&chr_mutex);               // mutex版本
    // spin_lock(&chr_spinlock);          // spinlock版本

    if (*offset >= BUF_SIZE) {
        ret = 0;
        goto unlock_out;
    }

    if (*offset + read_len > BUF_SIZE)
        read_len = BUF_SIZE - *offset;

    if (copy_to_user(buf, kernel_buf + *offset, read_len)) {
        ret = -EFAULT;
        goto unlock_out;
    }

    *offset += read_len;
    ret = read_len;

unlock_out:
    // ========== 解锁 ==========
    mutex_unlock(&chr_mutex);
    // spin_unlock(&chr_spinlock);

    return ret;
}

static ssize_t chr_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
    int ret;
    size_t write_len = min(len, (size_t)BUF_SIZE);

    // ========== 加锁 ==========
    mutex_lock(&chr_mutex);
    // spin_lock(&chr_spinlock);

    if (*offset >= BUF_SIZE) {
        ret = -ENOSPC;
        goto unlock_out;
    }

    if (*offset + write_len > BUF_SIZE)
        write_len = BUF_SIZE - *offset;

    if (copy_from_user(kernel_buf + *offset, buf, write_len)) {
        ret = -EFAULT;
        goto unlock_out;
    }

    *offset += write_len;
    ret = write_len;

unlock_out:
    // ========== 解锁 ==========
    mutex_unlock(&chr_mutex);
    // spin_unlock(&chr_spinlock);

    return ret;
}

static int chr_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "[chrdev] release\n");
    return 0;
}

static struct file_operations chr_fops = {
    .owner   = THIS_MODULE,
    .open    = chr_open,
    .read    = chr_read,
    .write   = chr_write,
    .release = chr_release,
};

static int __init chr_drv_init(void)
{
    int ret;

    // 动态申请设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
    if (ret < 0) 
    	return ret;

    // 初始化cdev
    cdev_init(&chr_cdev, &chr_fops);
    chr_cdev.owner = THIS_MODULE;
    ret = cdev_add(&chr_cdev, dev_num, 1);
    if (ret < 0) 
    	goto err1;

    // 创建设备类与设备文件
    dev_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev_class)) { 
    	ret = PTR_ERR(dev_class); 
    	goto err2; 
    }

    dev_device = device_create(dev_class, NULL, dev_num, NULL, DEV_NAME);
    if (IS_ERR(dev_device)) { 
    	ret = PTR_ERR(dev_device); 
    	goto err3; 
    }

    // 分配缓冲区
    kernel_buf = kzalloc(BUF_SIZE, GFP_KERNEL);
    if (!kernel_buf) { 
    	ret = -ENOMEM; 
    	goto err4; 
    }

    // ==================== 初始化锁 ====================
    mutex_init(&chr_mutex);                  // mutex初始化
    // spin_lock_init(&chr_spinlock);         // spinlock初始化

    printk(KERN_INFO "[chrdev] init success\n");
    return 0;

err4: device_destroy(dev_class, dev_num);
err3: class_destroy(dev_class);
err2: cdev_del(&chr_cdev);
err1: unregister_chrdev_region(dev_num, 1);
    return ret;
}

static void __exit chr_drv_exit(void)
{
    kfree(kernel_buf);
    device_destroy(dev_class, dev_num);
    class_destroy(dev_class);
    cdev_del(&chr_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "[chrdev] exit\n");
}

module_init(chr_drv_init);
module_exit(chr_drv_exit);

3. 传统字符设备驱动完整执行流程:从insmod到应用读写全链路

3.1 第一阶段:insmod加载驱动(内核执行)

  1. insmod → 把.ko文件加载到内核
  2. 调用module_initchr_drv_init()
  3. 申请设备号alloc_chrdev_region
  4. 初始化cdev :绑定file_operations
  5. 注册cdev:告诉内核这是一个字符设备
  6. 创建设备类class_create
  7. 自动生成/dev/xxxdevice_create
  8. 驱动加载完成,等待用户操作

3.2 第二阶段:应用层open设备

  1. 应用open("/dev/xxx", O_RDWR)
  2. 内核根据设备号找到对应的cdev
  3. 调用驱动里的chr_open()
  4. 打开成功,返回文件描述符fd

3.3 第三阶段:应用层write写数据

  1. 应用调用write(fd, buf, len)
  2. 系统调用进入内核
  3. 内核调用驱动chr_write()
  4. 加锁
  5. copy_from_user:用户态 → 内核态
  6. 解锁
  7. 返回写入长度

3.4 第四阶段:应用层read读数据

  1. 应用调用read(fd, buf, len)
  2. 内核调用驱动chr_read()
  3. 加锁
  4. copy_to_user:内核态 → 用户态
  5. 解锁
  6. 返回读取长度

3.5 第五阶段:close关闭设备

  1. 应用close(fd)
  2. 内核调用驱动chr_release()
  3. 释放资源

3.6 第六阶段:rmmod卸载驱动

  1. rmmod
  2. 调用module_exit
  3. 释放设备号、销毁cdev、删除设备文件、释放内存

4. 应用层测试程序(C语言):完整测试read/write/open/close

这是标准Linux应用层测试代码,直接编译运行就能测试驱动。

test_chrdev.c 完整代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#define DEV_PATH    "/dev/my_chrdev_lock"
#define BUF_SIZE    1024

int main()
{
    int fd;
    int ret;
    char write_buf[] = "Hello Linux Character Driver!";
    char read_buf[BUF_SIZE] = {0};

    // ========== 1. 打开设备 ==========
    fd = open(DEV_PATH, O_RDWR);
    if (fd < 0) {
        perror("open failed");
        return -1;
    }
    printf("open %s success\n", DEV_PATH);

    // ========== 2. 写入数据 ==========
    ret = write(fd, write_buf, strlen(write_buf));
    if (ret < 0) {
        perror("write failed");
        close(fd);
        return -1;
    }
    printf("write %d bytes: %s\n", ret, write_buf);

    // ========== 3. 偏移到开头 ==========
    lseek(fd, 0, SEEK_SET);

    // ========== 4. 读取数据 ==========
    ret = read(fd, read_buf, BUF_SIZE);
    if (ret < 0) {
        perror("read failed");
        close(fd);
        return -1;
    }
    printf("read %d bytes: %s\n", ret, read_buf);

    // ========== 5. 关闭设备 ==========
    close(fd);
    printf("close success\n");

    return 0;
}

编译运行命令

bash 复制代码
gcc test_chrdev.c -o test_chrdev
sudo ./test_chrdev

预期输出

复制代码
open /dev/my_chrdev_lock success
write 29 bytes: Hello Linux Character Driver!
read 29 bytes: Hello Linux Character Driver!
close success

5. 核心总结(面试必背)

5.1 锁总结

  1. mutex :等待时睡眠 → 不占CPU → 只能在进程上下文
  2. spinlock :等待时自旋 → 占CPU → 进程/中断都能用
  3. 中断上下文绝对不能用mutex
  4. 持有spinlock期间不能睡眠

5.2 字符设备驱动流程

insmod → 申请设备号 → 初始化cdev → 注册cdev → 生成/dev/xxx → open → read/write → close → rmmod

5.3 应用测试

应用通过open/read/write/close系统调用进入内核,最终调用驱动里注册的函数。

相关推荐
pengyi8710152 小时前
共享IP全面优缺点解析,适合什么人群使用?
linux·运维·服务器·网络·tcp/ip
Little At Air2 小时前
LinuxOS阻塞队列模型(单生产者单消费者)
linux·数据结构·c++
南境十里·墨染春水2 小时前
linux学习进展 git详解
linux·git·学习
念恒123063 小时前
基础IO(一切皆文件)
linux·c语言·c++·算法
Irissgwe3 小时前
四、进程控制(进程创建与终止)
linux·c++·进程·系统编程·fork·进程创建·进程终止
宵时待雨3 小时前
linux笔记归纳5:进程控制
linux·运维·笔记
夏日听雨眠4 小时前
LInux(gcc处理器,库文件,动静态库)
linux·运维·服务器
xingfujie4 小时前
Ubuntu K8s 1.28 kubeadm 高可用集群部署实战
linux·运维·服务器·docker·kubernetes
实心儿儿4 小时前
Linux —— 进程间通信 - 命名管道
linux·运维·服务器