目录

系列文章Linux中的并发与竞争05-互斥量

【系列文章】Linux中的并发与竞争[05]-互斥量

【系列文章】Linux中的并发与竞争[05]-互斥量

该文章为系列文章:Linux中的并发与竞争中的第5篇
该系列的导航页连接:



一、互斥锁

在上一文章中,将信号量量值设置为 1,最终实现的就是互斥效果,与本文章要学习的互斥锁功能相同,虽然两者功能相同但是具体的实现方式是不同的,但是使用互斥锁效率更高、更简洁,所以如果使用到的信号量“量值”为 1,一般将其修改为使用互斥锁实现。

当有多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定或者非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性,能够保证多个线程访问共享数据不会出现资源竞争及数据错误。

为了方便大家理解,这里举个例子来说明。比如公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。那么怎么解决这种情况呢?只要我在打印着的时候别人是不允许打印的,只有等我打印结束后别人才允许打印。这个过程有点类似于,把打印机放在一个房间里,给这个房间安把锁,这个锁默认是打开的。当 A 需要打印时,他先过来检查这把锁有没有锁着,没有的话就进去,同时上锁在房间里打印。而在这时,刚好 B 也需要打印,B 同样先检查锁,发现锁是锁住的,他就在门外等着。而当 A 打印结束后,他会开锁出来,这时候 B 才进去上锁打印。看了这个例子,相信大家已经理解了互斥锁。

互斥锁会导致休眠,所以在中断里面不能用互斥锁。同一时刻只能有一个线程持有互斥锁,并且只有持有者才可以解锁,并且不允许递归上锁和解锁。

内核中以 mutex 结构体来表示互斥体,定义在“内核源码/include/linux/mutex.h”文件中,如下所示:

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
};

一些和互斥体相关的 API 函数也定义在 mutex.h 文件中,常用 API 函数如下所示:

函数描述
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_is_locked(struct mutex *lock)判断 mutex 是否被获取,如果是的话就返回 1,否则返回 0。

二、实验程序的编写

2.1驱动程序编写

由于互斥体在同一时间内只允许一个任务对共享资源进行,所以除了在 atomic_init()函数内加入初始化互斥锁函数之外,只需要在 open()函数中加入互斥锁加锁函数,在 release()函数中加入互斥锁解锁函数即可。

编写完成的 mutex.c 代码如下所示

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/mutex.h>

struct mutex mutex_test;//定义 mutex 类型的互斥锁结构体变量 mutex_test

static int open_test(struct inode *inode,struct file *file)
{
	printk("\nthis is open_test \n");
	mutex_lock(&mutex_test);//互斥锁加锁
	return 0;
}

static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
	int ret;
	char kbuf[10] = "topeet";//定义 char 类型字符串变量 kbuf
	printk("\nthis is read_test \n");
	ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用 copy_to_user 接收用户空间传递的数据
	if (ret != 0){
		printk("copy_to_user is error \n");
	}
	printk("copy_to_user is ok \n");
	return 0;
}

static char kbuf[10] = {0};//定义 char 类型字符串全局变量 kbuf

static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
	int ret;
	ret = copy_from_user(kbuf,ubuf,len);//使用 copy_from_user 接收用户空间传递的数据
	if (ret != 0){
		printk("copy_from_user is error\n");
	}
	if(strcmp(kbuf,"topeet") == 0 ){//如果传递的 kbuf 是 topeet 就睡眠四秒钟
		ssleep(4);
	}
	else if(strcmp(kbuf,"itop") == 0){//如果传递的 kbuf 是 itop 就睡眠两秒钟
		ssleep(2);
	}
	printk("copy_from_user buf is %s \n",kbuf);
	return 0;
}

static int release_test(struct inode *inode,struct file *file)
{
	mutex_unlock(&mutex_test);//互斥锁解锁
	printk("\nthis is release_test \n");
	return 0;
}

struct chrdev_test {
	dev_t dev_num;//定义 dev_t 类型变量 dev_num 来表示设备号
	int major,minor;//定义 int 类型的主设备号 major 和次设备号 minor
	struct cdev cdev_test;//定义 struct cdev 类型结构体变量 cdev_test,表示要注册的字符设备
	struct class *class_test;//定于 struct class *类型结构体变量 class_test,表示要创建的类
};

struct chrdev_test dev1;//创建 chrdev_test 类型的

struct file_operations fops_test = {
	.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	.open = open_test,//将 open 字段指向 open_test(...)函数
	.read = read_test,//将 read 字段指向 read_test(...)函数
	.write = write_test,//将 write 字段指向 write_test(...)函数
	.release = release_test,//将 release 字段指向 release_test(...)函数
};

static int __init atomic_init(void)
{
	mutex_init(&mutex_test);//对互斥体进行初始化
	if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名
		chrdev_name
		printk("alloc_chrdev_region is error \n");
	}
	printk("alloc_chrdev_region is ok \n");
	dev1.major = MAJOR(dev1.dev_num);//使用 MAJOR()函数获取主设备号
	dev1.minor = MINOR(dev1.dev_num);//使用 MINOR()函数获取次设备号
	printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
	//使用 cdev_init()函数初始化 cdev_test 结构体,并链接到fops_test 结构体
	cdev_init(&dev1.cdev_test,&fops_test);
	//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	dev1.cdev_test.owner = THIS_MODULE;
	cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用 cdev_add()函数进行字符设备的添加
	//使用 class_create 进行类的创建,类名称为class_test
	dev1.class_test = class_create(THIS_MODULE,"class_test");
	//使用 device_create 进行设备的创建,设备名称为 device_test
	device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");
	return 0;
}

static void __exit atomic_exit(void)
{
	device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
	class_destroy(dev1.class_test);//删除创建的类
	cdev_del(&dev1.cdev_test);//删除添加的字符设备 cdev_test
	unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
	printk("module exit \n");
}

module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

2.2编写测试 APP

本测试 app 代码和上一文章相同,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序 app.c内容如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	int fd;//定义 int 类型的文件描述符
	char str1[10] = {0};//定义读取缓冲区 str1
	fd = open(argv[1],O_RDWR);//调用 open 函数,打开输入的第一个参数文件,权限为可读可写
	if(fd < 0 ){
		printf("file open failed \n");
		return -1;
	}
	/*如果第二个参数为 topeet,条件成立,调用 write 函数,写入 topeet*/
	if (strcmp(argv[2],"topeet") == 0 ){
		write(fd,"topeet",10);
	}
	/*如果第二个参数为 itop,条件成立,调用 write 函数,写入 itop*/
	else if (strcmp(argv[2],"itop") == 0 ){
		write(fd,"itop",10);
	}
	close(fd);
	return 0;
}

2.3运行测试

使用以下命令运行测试 app,运行结果如下图所示:

./app /dev/device_test topeet

https://i-blog.csdnimg.cn/direct/898e32e3eaf94a0da07787885564f82a.png

./app /dev/device_test topeet &
./app /dev/device_test itop

https://i-blog.csdnimg.cn/direct/b513546c5a9049fbaecfaac9818575a6.png