【系列文章】Linux中的并发与竞争[04]-信号量

【系列文章】Linux中的并发与竞争[04]-信号量

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


文章目录


在上面两篇文章对自旋锁和自旋锁死锁进行了学习,自旋锁会让请求的任务原地"自旋",在等待的过程中会循环检测自旋锁的状态,进而占用系统资源,而本章节要讲解的信号量也是解决竞争的一种常用方法,与自旋锁不同的是,信号量会使等待的线程进入休眠状态,适用于那些占用资源比较久的场合。下面对信号量相关知识的进行讲解。

一、信号量

信号量是操作系统中最典型的用于同步和互斥的手段,本质上是一个全局变量,信号量的值表示控制访问资源的线程数,可以根据实际情况来自行设置,如果在初始化的时候将信号量量值设置为大于 1,那么这个信号量就是计数型信号量,允许多个线程同时访问共享资源。如果将信号量量值设置为 1,那么这个信号量就是二值信号量,同一时间内只允许一个线程访问共享资源,注意!信号量的值不能小于 0。当信号量的值为 0 时,想访问共享资源的线程必须等待,直到信号量大于 0 时,等待的线程才可以访问。当访问共享资源时,信号量执行"减一"操作,访问完成后再执行"加一"操作。

相比于自旋锁,信号量具有休眠特性,因此适用长时间占用资源的场合,但由于信号量会引起休眠,所以不能用在中断函数中,最后如果共享资源的持有时间比较短,使用信号量的话会造成频繁的休眠,反而带来更多资源的消耗,使用自旋锁反而效果更好。再同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁,因为信号量会导致睡眠。

以现实生活中的银行办理业务为例,银行的业务办理窗口就是共享资源,业务办理窗口的数量就是信号量量值,进入银行之后,客户需要领取相应的排序码,然后在休息区进行等待,可以看作线程的睡眠阶段,当前面的客户办理完业务之后,相应的窗口会空闲出来,可以看作信号量的释放,之后银行会通过广播,提醒下一位客户到指定的窗口进行业务的办理,可以看作线程的唤醒并获取到信号量,访问共享资源的过程。

Linux 内核使用 semaphore 结构体来表示信号量,该结构体定义在"内核源码/include/linux/semaphore.h"文件内,结构体内容如下所示:

c 复制代码
struct semaphore {
	raw_spinlock_t lock;
	unsigned int count;
	struct list_head wait_list;
};

与信号量相关的 API 函数同样定义在 semaphore.h 文件内,部分常用 API 函数如下:

函数 描述
DEFINE_SEAMPHORE(name) 定义信号量,并且设置信号量的值为1。
void sema_init(struct semaphore *sem, int val) 初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem) 获取信号量,不能被中断打断,如ctrl+c
int down_interruptible(struct semaphore *sem) 获取信号量,可以被中断打断,如ctrl+c
void up(struct semaphore *sem) 释放信号量
int down_trylock(struct semaphore *sem); 尝试获取信号量,如果能获取到信号量就获取,并且返回0。 如果不能就返回非0

二、实验程序的编写

2.1驱动程序编写

与之前章节设置标志位,在同一时间内只允许一个任务对共享资源进行访问的方式所不同,本小节将采用信号量的方式避免竞争的产生。本实验设置的信号量量值为 1,所以需要在open()函数中加入信号量获取函数,在 release()函数中加入信号量释放函数即可。

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

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/semaphore.h>

struct semaphore semaphore_test;//定义一个 semaphore 类型的结构体变量 semaphore_test

static int open_test(struct inode *inode,struct file *file)
{
	printk("\nthis is open_test \n");
	down(&semaphore_test);//信号量数量减 1
	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)
{
	up(&semaphore_test);//信号量数量加 1
	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)
{
	sema_init(&semaphore_test,1);//初始化信号量结构体 semaphore_test,并设置信号量的数量为 1
	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

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

c 复制代码
./app /dev/device_test topeet

可以看到传递的 buf 值为 topeet,然后输入以下命令在后台运行两个 app,来进行竞争测试,运行结果如下图所示:

c 复制代码
./app /dev/device_test topeet &
./app /dev/device_test itop

上述打印信息正常,证明数据被正确传递了,没有发生共享资源的竞争,第一个任务运行之后,由于设置的信号量量值为 1,所以第二个任务会进入休眠状态,第一个任务执行完毕之后,会唤醒第二个任务去执行,所以避免了并发与竞争。

相关推荐
東雪蓮☆2 小时前
LVS 负载均衡群集
linux·运维·服务器·负载均衡·lvs
和煦的春风2 小时前
性能案例分析 | Waiting for GPU completion
android·linux
lybugproducer3 小时前
深入 Linux 文件系统:从数据存储到万物皆文件
linux
烦躁的大鼻嘎3 小时前
【Linux】深入Linux多线程架构与高性能编程
linux·运维·服务器·开发语言·c++·ubuntu
羚羊角uou3 小时前
【Linux】system V共享内存
linux·运维·服务器
林克爱塞尔达4 小时前
Linux入门(二)
linux·运维·chrome
破烂儿4 小时前
Ubuntu Server 安装图形界面和通过Window远程桌面连接服务器(Xrdp)
linux·服务器·ubuntu
存储服务专家StorageExpert4 小时前
手搓一个 DELL EMC Unity存储系统健康检查清单
linux·运维·服务器·存储维护·emc存储
笑口常开xpr4 小时前
Linux 库开发入门:静态库与动态库的 2 种构建方式 + 5 个编译差异 + 3 个加载技巧,新手速看
linux·c语言·动态库·静态库