LV050-互斥锁
怎么保护共享资源,防止竞争?
一、互斥锁简介
比如公司部门里, 我在使用着打印机打印东西的同时(还没有打印完) , 别人刚好也在此刻使用打印机打印东西, 如果不做任何处理的话,打印出来的东西肯定是错乱的。 那么怎么解决这种情况呢? 只要我在打印着的时候别人是不允许打印的, 只有等我打印结束后别人才允许打印。 这个过程有点类似于, 把打印机放在一个房间里, 给这个房间安把锁, 这个锁默认是打开的。 当 A 需要打印时, 他先过来检查这把锁有没有锁着, 没有的话就进去, 同时上锁在房间里打印。 而在这时, 刚好 B 也需要打印, B 同样先检查锁, 发现锁是锁住的, 他就在门外等着。 而当 A 打印结束后, 他会开锁出来, 这时候 B 才进去上锁打印。
现在应该就知道互斥锁是什么了,也可以叫互斥体,后面我还是叫互斥锁吧。互斥锁会导致休眠, 所以在中断里面不能用互斥锁。 同一时刻只能有一个线程持有互斥锁,并且只有持有者才可以解锁, 并且不允许递归上锁和解锁。
我们将信号量量值设置为 1, 最终实现的就是互斥效果,虽然两者功能相同但是具体的实现方式是不同的, 但是使用互斥锁效率更高、更简洁, 所以如果使用到的信号量“量值”为 1, 一般将其修改为使用互斥锁实现。
当有多个线程几乎同时修改某一个共享数据的时候, 需要进行同步控制。 线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引入互斥锁。 互斥锁为资源引入一个状态:锁定或者非锁定。 某个线程要更改共享数据时, 先将其锁定, 此时资源的状态为“锁定” , 其他线程不能更改; 直到该线程释放资源, 将资源的状态变成“非锁定” , 其他的线程才能再次锁定该资源。 互斥锁保证了每次只有一个线程进行写入操作, 从而保证了多线程情况下数据的正确性, 能够保证多个线程访问共享数据不会出现资源竞争及数据错误。
二、相关数据结构与 API
1. struct mutex
Linux 内核使用 struct mutex 来表示互斥锁:
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};2. 互斥锁相关 API
相关的 API 在 mutex.h - include/linux/mutex.h 中有对应的声明或者定义:
| 函数 | 描述 |
|---|---|
| DEFINE_MUTEX(name) | 定义并初始化一个 mutex 变量。 |
| void mutex_init(mutex *lock) | 初始化 mutex。 |
| void mutex_lock(struct mutex *lock) | 获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。 |
| void mutex_unlock(struct mutex *lock) | 释放 mutex,也就给 mutex 解锁。 |
| int mutex_trylock(struct mutex *lock) | 尝试获取 mutex,如果成功就返回 1,如果失败就返回 0。 |
| int mutex_is_locked(struct mutex *lock) | 判断 mutex 是否被获取,如果是的话就返回 1,否则返回 0。 |
| int mutex_lock_interruptible(struct mutex *lock) | 使用此函数获取信号量失败进入休眠以后可以被信号打断。 |
3. 使用示例
struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
/* 临界区 */
mutex_unlock(&lock); /* 解锁 */三、注意事项
在使用 mutex 的时候要注意如下几点:
①、 mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
②、和信号量一样, mutex 保护的临界区可以调用引起阻塞的 API 函数。
③、因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。
四、互斥锁 demo
1. demo 源码
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h> /* ssleep */
#include <linux/mutex.h>
#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"
// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif
#define CHRDEV_NAME "sdev" /* 设备名, cat /proc/devices 查看与设备号的对应关系 */
#define CLASS_NAME "sclass" /* 类名,在 /sys/class 中显示的名称 */
#define DEVICE_NAME "sdevchr" /* 设备节点名,在 /sys/class/class_name/ 中显示的名称以及 /dev/ 下显示的节点名 */
#define BUFSIZE 32 /* 设置最大偏移量为 32 */
#define MUTEX_FLAG 0 /* 用于有无信号量的现象对比 */
// ioctl 支持的命令定义
#define CMD_TEST0 _IO('S', 0)
#define CMD_TEST1 _IOW('S', 1, int)
#define CMD_TEST2 _IOR('S', 2, int)
#define CMD_TEST3 _IOW('S', 3, int)
struct __CMD_TEST{
int a;
int b;
int c;
};
typedef struct __CHAR_DEVICE
{
char dev_name[32]; // 设备名称, /dev/dev-name
dev_t dev_num; // 定义 dev_t 类型(32 位大小)的变量 dev_num, 用来存放设备号
struct cdev s_cdev; // 定义 cdev 结构体类型的变量 scdev
struct class *class; // 定于 struct class *类型结构体变量 class,表示要创建的类
struct device *device; // 设备
char buf[BUFSIZE]; // 设置数据存储数组 mem
struct mutex mutex_lock; // 互斥锁
} _CHAR_DEVICE;
_CHAR_DEVICE g_chrdev = {0}; //定义一个 device_test 结构体变量
static int scdev_ops_open(struct inode *pInode, struct file *pFile)
{
PRT("This is scdev_ops_open!dev name=%s\n", g_chrdev.dev_name);
pFile->private_data = &g_chrdev; // 设置私有数据
#if MUTEX_FLAG == 1
/* 获取互斥锁, 可以被信号打断 */
if (mutex_lock_interruptible(&g_chrdev.mutex_lock))
{
return -ERESTARTSYS;
}
#if 0
mutex_lock(&g_chrdev.lock); /* 不能被信号打断 */
#endif
#endif
return 0;
}
static ssize_t scdev_ops_read(struct file *pFile, char __user *buf, size_t size, loff_t *off)
{
loff_t offset = *off; // 将读取数据的偏移量赋值给 loff_t 类型变量 p
size_t count = size;
_CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在 write 函数中读取 private_data
if (offset > BUFSIZE)
{
return 0;
}
if (count > BUFSIZE - offset)
{
count = BUFSIZE - offset;
}
if (copy_to_user(buf, p_chrdev->buf + offset, count))
{
// 将 mem 中的值写入 buf,并传递到用户空间
PRT("copy_to_user error!\n");
return -1;
}
#if 0
int i = 0;
for (i = 0; i < BUFSIZE; i++)
{
PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将 mem 中的值打印出来
}
PRT("read offset is %llu, count is %d\n", offset, count);
#endif
*off = *off + count; // 更新偏移值
return count;
}
static ssize_t scdev_ops_write(struct file *pFile, const char __user *buf, size_t size, loff_t *off)
{
loff_t offset = *off; // 将读取数据的偏移量赋值给 loff_t 类型变量 p
size_t count = size;
_CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在 write 函数中读取 private_data
if (offset > BUFSIZE)
{
return 0;
}
if (count > BUFSIZE - offset)
{
count = BUFSIZE - offset;
}
if (copy_from_user(p_chrdev->buf + offset, buf, count))
{
// 将 buf 中的值,从用户空间传递到内核空间
PRT("copy_to_user error \n");
return -1;
}
PRT("copy_from_user buf is %s\n", p_chrdev->buf + offset);
#if 0
int i = 0;
for (i = 0; i < BUFSIZE; i++)
{
PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将 mem 中的值打印出来
}
PRT("write offset is %llu, count is %d\n", offset, count); // 打印写入的值
#endif
*off = *off + count; // 更新偏移值
return count;
}
static int scdev_ops_release(struct inode *pInode, struct file *pFile)
{
_CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在 write 函数中读取 private_data
#if MUTEX_FLAG == 1
mutex_unlock(&p_chrdev->mutex_lock);// 释放互斥锁
#endif
PRT("This is scdev_ops_release!dev_name=%s\n", p_chrdev->dev_name);
return 0;
}
static loff_t scdev_ops_llseek(struct file *pFile, loff_t offset, int whence)
{
loff_t new_offset = 0; // 定义 loff_t 类型的新的偏移值
switch (whence) // 对 lseek 函数传递的 whence 参数进行判断
{
case SEEK_SET:
if (offset < 0 || offset > BUFSIZE)
{
return -EINVAL; // EINVAL = 22 表示无效参数
}
new_offset = offset; // 如果 whence 参数为 SEEK_SET,则新偏移值为 offset
break;
case SEEK_CUR:
if ((pFile->f_pos + offset < 0) || (pFile->f_pos + offset > BUFSIZE))
{
return -EINVAL;
}
new_offset = pFile->f_pos + offset; // 如果 whence 参数为 SEEK_CUR,则新偏移值为 pFile-> f_pos + offset,pFile-> f_pos 为当前的偏移值
break;
case SEEK_END:
if (pFile->f_pos + offset < 0)
{
return -EINVAL;
}
new_offset = BUFSIZE + offset; // 如果 whence 参数为 SEEK_END,则新偏移值为 BUFSIZE + offset,BUFSIZE 为最大偏移量
break;
default:
break;
}
pFile->f_pos = new_offset; // 更新 pFile-> f_pos 偏移值
return new_offset;
}
static long scdev_ops_ioctl(struct file *pFile, unsigned int cmd, unsigned long arg)
{
int val = 0;//定义 int 类型向应用空间传递的变量 val
switch(cmd)
{
case CMD_TEST0:
PRT("this is CMD_TEST0\n");
break;
case CMD_TEST1:
PRT("this is CMD_TEST1\n");
PRT("arg is %ld\n",arg);//打印应用空间传递来的 arg 参数
break;
case CMD_TEST2:
val = 1;//将要传递的变量 val 赋值为 1
PRT("this is CMD_TEST2\n");
if(copy_to_user((int *)arg, &val, sizeof(val)) != 0)
{
//通过 copy_to_user 向用户空间传递数据
PRT("copy_to_user error \n");
}
break;
case CMD_TEST3:
{
struct __CMD_TEST cmd_test3 = {0};
if (copy_from_user(&cmd_test3, (int *)arg, sizeof(cmd_test3)) != 0)
{
PRT("copy_from_user error\n");
}
PRT("cmd_test3.a = %d\n", cmd_test3.a);
PRT("cmd_test3.b = %d\n", cmd_test3.b);
PRT("cmd_test3.c = %d\n", cmd_test3.c);
break;
}
default:
break;
}
return 0;
}
static struct file_operations g_scdev_ops = {
.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = scdev_ops_open,
.read = scdev_ops_read,
.write = scdev_ops_write,
.release = scdev_ops_release,
.llseek = scdev_ops_llseek,
.unlocked_ioctl = scdev_ops_ioctl,
}; // 定义 file_operations 结构体类型的变量 g_cdev_dev_ops
static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
int ret; // 定义 int 类型的变量 ret,用来判断函数返回值
int major, minor; // 定义 int 类型的主设备号 major 和次设备号 minor
#if MUTEX_FLAG == 1
mutex_init(&p_chrdev->mutex_lock); /* 初始化互斥锁 */
#endif
ret = alloc_chrdev_region(&p_chrdev->dev_num, 0, 1, CHRDEV_NAME); // 自动获取设备号,设备名为 chrdev_name
if (ret < 0)
{
PRTE("alloc_chrdev_region is error!ret=%d\n", ret);
goto err_alloc_devno;
}
major = MAJOR(p_chrdev->dev_num); // 使用 MAJOR()函数获取主设备号
minor = MINOR(p_chrdev->dev_num); // 使用 MINOR()函数获取次设备号
//PRT("major is %d, minor is %d !\n", major, minor);
cdev_init(&p_chrdev->s_cdev, &g_scdev_ops); // 使用 cdev_init()函数初始化 p_chrdev-> s_cdev 结构体,并链接到 cdev_ops 结构体
p_chrdev->s_cdev.owner = THIS_MODULE; // 将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
ret = cdev_add(&p_chrdev->s_cdev, p_chrdev->dev_num, 1); // 使用 cdev_add()函数进行字符设备的添加
if (ret < 0)
{
PRTE("cdev_add is error !ret=%d\n", ret);
goto err_cdev_add;
}
p_chrdev->class = class_create(THIS_MODULE, CLASS_NAME); // 使用 class_create 进行类的创建,类名称为 class_dev
if(IS_ERR(p_chrdev->class))
{
ret = PTR_ERR(p_chrdev->class);
goto err_class_create;
}
p_chrdev->device = device_create(p_chrdev->class, NULL, p_chrdev->dev_num, NULL, "%s", DEVICE_NAME); // 使用 device_create 进行设备的创建,设备名称为 device_dev
if(IS_ERR(p_chrdev->device))
{
ret = PTR_ERR(p_chrdev->class);
goto err_device_create;
}
snprintf (p_chrdev->dev_name, sizeof(p_chrdev->dev_name), "/dev/%s", DEVICE_NAME);
PRT("scdev_create %s success!\n", p_chrdev->dev_name);
return 0;
// 一些列的错误处理
err_device_create:
class_destroy(p_chrdev->class); // 删除创建的类
err_class_create:
cdev_del(&p_chrdev->s_cdev); // 使用 cdev_del()函数进行字符设备的删除
err_cdev_add:
unregister_chrdev_region(p_chrdev->dev_num, 1); //注销设备号
err_alloc_devno:
return ret;
}
static void scdev_destroy(_CHAR_DEVICE *p_chrdev)
{
// 需要注意的是, 字符设备的注册要放在申请字符设备号之后,
// 字符设备的删除要放在释放字符驱动设备号之前。
cdev_del(&p_chrdev->s_cdev); // 使用 cdev_del()函数进行字符设备的删除
unregister_chrdev_region(p_chrdev->dev_num, 1); // 释放字符驱动设备号
device_destroy(p_chrdev->class, p_chrdev->dev_num); // 删除创建的设备
class_destroy(p_chrdev->class); // 删除创建的类
PRT("scdev_destroy success!\n");
}
/**
* @brief sdrv_demo_init
* @note 注册驱动
* @param [in]
* @param [out]
* @retval
*/
static __init int sdrv_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
// 注册字符设备
ret = scdev_create(&g_chrdev);
if (ret < 0)
{
PRTE("Failed to scdev_create!ret=%d\n", ret);
goto err_scdev_create;
}
PRT("sdrv_demo module init success!\n");
return 0;
err_scdev_create:
return ret;
}
/**
* @brief sdrv_demo_exit
* @note 注销驱动
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdrv_demo_exit(void)
{
// 注销字符设备
scdev_destroy(&g_chrdev);
PRT("sdrv_demo module exit!\n");
}
module_init(sdrv_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数
/* 模块信息(通过 modinfo xxx.ko 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */2. 开发板验证
其实这个现象和信号量是一样的,我们将编译得到的 sdriver_demo.ko、app_demo.out 拷贝到开发板。
- (1)加载驱动
insmod sdriver_demo.ko- (2)app_demo.out 会创建子进程运行
./app_demo.out /dev/sdevchr 2
可以看到是一个执行完再执行另一个。没有互斥锁保护的时候是这样的:

参考资料: