目录
- mutex互斥锁 vs spinlock自旋锁:通俗讲解+区别+场景+限制
- 在字符设备驱动中加入锁保护(完整代码)
- 传统字符设备驱动完整执行流程:从insmod到应用读写全链路
- 应用层测试程序(C语言):完整测试read/write/open/close
- 核心总结+面试必背
1. mutex互斥锁 vs spinlock自旋锁:通俗讲解+区别+场景+限制
1.1 大白话类比
- mutex(互斥锁) :去厕所,没人就进去锁门;有人就出去睡觉等待,不占CPU。
- spinlock(自旋锁) :去厕所,没人就进去锁门;有人就原地转圈盯着,一直占着CPU直到门开。
1.2 核心区别(面试必考)
| 对比项 | mutex(互斥锁) | spinlock(自旋锁) |
|---|---|---|
| 锁等待时 | 进程睡眠(放弃CPU) | 原地自旋(占着CPU) |
| 能否睡眠 | ✅ 可以睡眠 | ❌ 绝对不能睡眠 |
| 上下文限制 | 只能用在进程上下文 | 进程/中断上下文都能用 |
| 临界区大小 | 适合长耗时操作 | 适合极短操作 |
| 单核CPU | 效率高 | 效率极低(空转) |
| 多核CPU | 适合长临界区 | 适合短临界区 |
| 中断上下文 | ❌ 不能用 | ✅ 能用 |
1.3 使用场景(死记硬背)
- 进程上下文 + 临界区长(毫秒级) → mutex
- 中断上下文 / 软中断 / tasklet → 必须 spinlock
- 进程上下文 + 临界区极短(微秒级) → spinlock
- 驱动数据结构保护、硬件寄存器操作 → 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加载驱动(内核执行)
insmod→ 把.ko文件加载到内核- 调用
module_init→chr_drv_init() - 申请设备号 :
alloc_chrdev_region - 初始化cdev :绑定
file_operations - 注册cdev:告诉内核这是一个字符设备
- 创建设备类 :
class_create - 自动生成/dev/xxx :
device_create - 驱动加载完成,等待用户操作
3.2 第二阶段:应用层open设备
- 应用
open("/dev/xxx", O_RDWR) - 内核根据设备号找到对应的
cdev - 调用驱动里的
chr_open() - 打开成功,返回文件描述符fd
3.3 第三阶段:应用层write写数据
- 应用调用
write(fd, buf, len) - 系统调用进入内核
- 内核调用驱动
chr_write() - 加锁
copy_from_user:用户态 → 内核态- 解锁
- 返回写入长度
3.4 第四阶段:应用层read读数据
- 应用调用
read(fd, buf, len) - 内核调用驱动
chr_read() - 加锁
copy_to_user:内核态 → 用户态- 解锁
- 返回读取长度
3.5 第五阶段:close关闭设备
- 应用
close(fd) - 内核调用驱动
chr_release() - 释放资源
3.6 第六阶段:rmmod卸载驱动
rmmod- 调用
module_exit - 释放设备号、销毁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 锁总结
- mutex :等待时睡眠 → 不占CPU → 只能在进程上下文用
- spinlock :等待时自旋 → 占CPU → 进程/中断都能用
- 中断上下文绝对不能用mutex
- 持有spinlock期间不能睡眠
5.2 字符设备驱动流程
insmod → 申请设备号 → 初始化cdev → 注册cdev → 生成/dev/xxx → open → read/write → close → rmmod
5.3 应用测试
应用通过open/read/write/close系统调用进入内核,最终调用驱动里注册的函数。