首页 > 系统相关 >Linux下的并发与竞争

Linux下的并发与竞争

时间:2024-05-31 19:31:03浏览次数:24  
标签:led struct 并发 int lock void 竞争 mutex Linux

文章目录


前言

Linux系统是个多任务操作系统,并发访问带来的问题就是竞争,所谓的临界区就是共享数据段,要保证临界区是原子访问的。主要方法有四种:原子操作,自旋锁,信号量,互斥体。本文主要介绍内核下各方式的常用API及基本用法,帮助大家快速上手。


一、原子操作

原子操作就是指不能在进一步分割的操作,原子操作只能对整形变量或者位进行保护。
1.原子整形操作:
typedef struct {
int counter;
} atomic_t;

操作API如下,对于32位的系统,把接口名的"atomic64_"换成"atomic_"即可。

ATOMIC64_INIT(int i) 定义原子变量的时候对其初始化。
int atomic64_read(atomic64_t *v) 			读取 v 的值,并且返回。
void atomic64_set(atomic64_t *v, int i) 	向 v 写入 i 值。
void atomic64_add(int i, atomic64_t *v) 	给 v 加上 i 值。
void atomic64_sub(int i, atomic64_t *v) 	从 v 减去 i 值。
void atomic64_inc(atomic64_t *v) 			给 v 加 1,也就是自增。
void atomic64_dec(atomic64_t *v) 			从 v 减 1,也就是自减
int atomic64_dec_return(atomic64_t*v) 	从 v 减 1,并且返回 v 的值。
int atomic64_inc_return(atomic64_t *v) 	给 v 加 1,并且返回 v 的值。
int atomic64_sub_and_test(int i,atomic64_t *v)	从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic64_dec_and_test(atomic64_t*v)			从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic64_inc_and_test(atomic64_t*v)			给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic64_add_negative(int i,atomic64_t *v)	给 v 加 i,如果结果为负就返回真,否则返回假

示例:
atomic_t a = ATOMIC_INT(3);
atomic64_set(&a,10);	a赋值为10
atomic64_read(&a);		读出a的值

原子位操作API:
void set_bit(int nr, void *p) 		将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p) 		将 p 地址的第 nr 位清零。
void change_bit(int nr, void*p) 	将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 		获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void*p)	将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr,void *p)	将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr,void *p)	将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

驱动代码示例:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/of_gpio.h>

#define LED_NAME	"gpio_init"
#define LED_MAJOR	202
#define LED_MINOR	0

struct led_dev {
	struct class * fclass;
	struct device * fdevice;
	struct cdev * cdev;
	dev_t dev_num;
	struct device_node * nd; 
	int major;
	int minor;
	int gpio_led;
	atomic64_t lock;
};

struct led_dev led = {0};

int led_open (struct inode *a, struct file *b)
{
	int val = 0;
	printk("lzw led open !!!\n");
	if(atomic64_sub_and_test(1,&led.lock) == 0)
	{
		val = atomic64_inc_return(&led.lock);	//减了1要加回来
		printk("lzw led atomic has been open, now failed, val = %d !\n",val);
		
		return -EBUSY;
	}
	else
		printk("lzw led atomic open sueccess !\n");
	
	b->private_data = &led;
	return 0;
}

int led_close (struct inode *a, struct file *b)
{
	atomic64_inc(&led.lock);
	printk("lzw led atomic close, atomic lock +1 sueccess !\n");
	return 0;
}

struct file_operations fop = 
{
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_close,
};


int __init led_init(void)
{
	led.cdev = cdev_alloc();
	led.dev_num = MKDEV(LED_MAJOR,LED_MINOR);
	register_chrdev_region(led.dev_num, 1, LED_NAME);
	cdev_init(led.cdev,&fop);
	cdev_add(led.cdev,led.dev_num,1);
	
	led.fclass = class_create(THIS_MODULE, "led_class");
	led.fdevice = device_create(led.fclass, NULL, led.dev_num, NULL, "led_device");
	
	led.major = LED_MAJOR;
	led.minor = LED_MINOR;

	atomic64_set(&led.lock,1);

	printk("lzw led init success, atomic lock = %lld ~~~\n",atomic64_read(&led.lock));

	return 0;
}

void __exit led_exit(void)
{
	device_destroy(led.fclass,led.dev_num);
	class_destroy(led.fclass);
	cdev_del(led.cdev);
	unregister_chrdev(LED_MAJOR, LED_NAME);

	return;
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

应用程序代码:

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

#define LED_IOCTL   	_IO('T',0)
#define LED_UNDEFINE   	_IO('T',1)

struct led_dev {
	struct class * fclass;
	struct device * fdevice;
	struct cdev * cdev;
	dev_t dev_num;
	struct device_node * nd; 
	int major;
	int minor;
};

int main(void)
{
	int fd = open("/dev/led_device",O_RDWR);
	if(fd > 0)
		printf("open 1 success \n");
	else
		printf("open 1 failed \n");
	
	int fd2 = open("/dev/led_device",O_RDWR);
	if(fd2 > 0)
		printf("open 2 success \n");
	else
		printf("open 2 failed \n");
		
	if(fd>0)
		close(fd);

	if(fd2>0)
		close(fd2);
	
	return 0;
}

加载驱动及运行结果如下:
在这里插入图片描述
这样就能保证该节点只被一个应用程序打开使用,后面的方法效果及原理也和此类似,就不讲得那么详细了。

二、自旋锁

自旋锁适用于短时期的轻量级加锁,等待锁的线程会一直处于自旋状态(会浪费处理器时间)。

用结构体spinlock_t表示自旋锁
spinlock_t lock;

基本操作API:
DEFINE_SPINLOCK(spinlock_t lock) 定义并初始化一个自选变量。
int spin_lock_init(spinlock_t*lock) 初始化自旋锁。
void spin_lock(spinlock_t*lock) 	获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t*lock) 	释放指定的自旋锁。
int spin_trylock(spinlock_t*lock)	尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t*lock)	检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0。

注意,当线程获得锁,执行中被中断抢走cpu使用权,中断获取不到锁一直自旋,线程A执行不了,放不了锁,死锁发生

线程A 					中断
spin_lock(&lock)	|-->spin_lock(&lock)
spin_unlock(&lock)--|	functionB
functionA()				spin_unlock(&lock)

最好解决方法就是关闭本地中断:
void spin_lock_irq(spinlock_t*lock) 		禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 		激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags)		保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)	将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

我们是很难确定某个时刻的中断状态,因此不推荐使用 spin_lock_irq/spin_unlock_irq
一般在线程中使用spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock。自旋锁使用注意事项:
1.锁的持有时间不能太长,否则会降低系统性能
2.不能调用任何可能导致线程休眠的API,否则可能死锁
3.不能递归申请自旋锁
4.必须考虑可移植性

驱动如下,真正实现设备互斥访问的是变量 dev_stats,但是要使用自旋锁对 dev_stats 来做保护:

struct led_dev {
	...
	int dev_stat;	//以此作为计数值判断打开程序有几个
	spinlock_t lock;
};

int led_open (struct inode *a, struct file *b)
{
	unsigned long  flags = 0;
	int ret = 0;
	printk("lzw led spinlock open !!!\n");
	
	spin_lock_irqsave(&led.lock,flags);
	
	if(led.dev_stat != 0)
	{
		printk("lzw led spin_lock has been open, now failed !\n");
		ret = -1;
	}
	else
	{
		printk("lzw led spin_lock open sueccess !\n");
		led.dev_stat++;
		b->private_data = &led;
	}
	
	spin_unlock_irqrestore(&led.lock,flags);
	
	return ret;
}

int led_close (struct inode *a, struct file *b)
{
	unsigned long flags = 0;
	spin_lock_irqsave(&led.lock,flags);
	if(led.dev_stat)
		led.dev_stat--;
	spin_unlock_irqrestore(&led.lock,flags);
	
	printk("lzw led spin_lock close +++ sueccess !\n");
	return 0;
}

int __init led_init(void)
{
	...
	led.dev_stat = 0;
	spin_lock_init(&led.lock);

	printk("lzw led init spinlock success ~~~\n");

	return 0;
}

测试用例:

int main(void)
{
	int fd = open("/dev/led_device",O_RDWR);
	if(fd > 0)
		printf("open 1 success \n");
	else
		printf("open 1 failed \n");
	
	int fd2 = open("/dev/led_device",O_RDWR);
	if(fd2 > 0)
		printf("open 2 success \n");
	else
		printf("open 2 failed \n");

	if(fd>0)
		close(fd);

	if(fd2>0)
		close(fd2);
	
	return 0;
}

运行结果如下:
在这里插入图片描述

三、读写自旋锁

一次只允许一个写操作,不能有读;读的时候可以多人并发的读

typedef struct {
	arch_rwlock_t raw_lock;
} rwlock_t;

API参考上面自旋锁:
void read_lock(rwlock_t *lock)	获取读锁
void write_lock(rwlock_t *lock) 获取写锁。

顺序锁:可以写操作时进行读操作,保护的资源不能是指针(同时读写可能崩溃)
typedef struct {
struct seqcount seqcount;
	spinlock_t lock;
} seqlock_t;

void write_seqlock(seqlock_t *sl) 获取写顺序锁。

四、信号量

同步的一种方式
1.可以使等待线程进入休眠状态,适合占用资源久的场合
2.不能用于中断中,因为会引起休眠
3.共享资源持有时间短,不适合
结构体如下:

struct semaphore {
	raw_spinlock_t lock;
	unsigned int count;
	struct list_head wait_list;
};

DEFINE_SEAMPHORE(name) 定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore*sem, int val)	初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem) 	获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore*sem);	尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)	获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 释放信号量

简单举例:
struct semaphore sem; 	/* 定义信号量 */
sema_init(&sem, 1); 	/* 初始化信号量 */
down(&sem);			 	/* 申请信号量 */
/* 临界区 */
up(&sem); 				/* 释放信号量 */

驱动:

int led_open (struct inode *a, struct file *b)
{

	printk("lzw led semphore open !!!\n");
	down_interruptible(&led.sem);	//如果获取不到进入休眠状态,sem大于等于1时会被唤醒

#if 0
	down(&sem);
#endif
	
	return 0;
}

int led_close (struct inode *a, struct file *b)
{
	up(&led.sem);
	
	printk("lzw led semphore close +++ sueccess !\n");
	return 0;
}

int __init led_init(void)
{
	...
	sema_init(&led.sem,1);
}

测试用例test:

	int fd = open("/dev/led_device",O_RDWR);
	if(fd > 0)
		printf("open 1 success \n");
	else
		printf("open 1 failed \n");
	
	for(int i=0;i<10;i++)
	{
		sleep(1);
		printf("led 1 on ~\n");
	}

	if(fd>0)
		close(fd);

测试用例test2:

	int fd2 = open("/dev/led_device",O_RDWR);
	if(fd2 > 0)
		printf("open 2 success \n");
	else
		printf("open 2 failed \n");

	for(int i=0;i<5;i++)
	{
		sleep(1);
		printf("led 2 on ~\n");
	}

	if(fd2>0)
		close(fd2);

先运行test,再运行test2,可发现因为test已经占用打开了/dev/led_device,导致test2只能阻塞在open,等test关闭之后才能继续运行。
在这里插入图片描述

五、互斥体

互斥体,也叫互斥锁。虽然可以通过信号量为1实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥。和二值信号量类似,只不过互斥体专门用于互斥访问。
注意:
1.mutex会导致休眠,所以不能在中断用,中断只能用自旋锁
2.和信号量一样,mutex保护的临界区可以调用引起阻塞的API
3.一次只有一个线程可以持有mutex,必须由mutex持有者释放mutex

struct mutex {
	/* 1: unlocked, 0: locked, negative: locked, possible waiters */
	atomic_t count;
	spinlock_t wait_lock;
};

DEFINE_MUTEX(name) 						定义并初始化一个 mutex 变量。
void mutex_init(struct 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(structmutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。

struct mutex lock;
mutex_init(&lock);
mutex_lock(&lock);
/* 临界区 */
mutex_unlock(&lock);

驱动:


struct led_dev {
	...
	struct mutex lock;
};

int led_open (struct inode *a, struct file *b)
{
	printk("lzw led mutex open !!!\n");
	if(mutex_lock_interruptible(&led.lock))//可被信号打断,失败进入休眠状态
		return -1;

#if 0
	mutex_lock(&led.lock);	
#endif

	return 0;
}
int led_close (struct inode *a, struct file *b)
{
	mutex_unlock(&led.lock);
	
	printk("lzw led mutex close +++ sueccess !\n");
	return 0;
}
int __init led_init(void)
{
	...
	mutex_init(&led.lock);
}

测试用例及效果与第四节信号量的一致。


总结

以上就是今天要讲的内容,本文简单介绍了Linux内核下的并发与竞争,常用的API,用法以及测试案例。制作不易,多多包涵。

标签:led,struct,并发,int,lock,void,竞争,mutex,Linux
From: https://blog.csdn.net/qq_40514606/article/details/139274822

相关文章

  • linux 文件属性被替换修改查询并修改
    系统服务发布本来非常正常,但是今天不知道为什么,打包发布异常,删除文件目录的权限都没有。上网搜索后,最终找到文件属性被修改,导致无法删除。通过lsattr命令查询文件的属性,如果出现,其中----i-----------的文件是属性被修改的。查找文件时,注意,隐藏文件也要查询。[root@localhost......
  • 【LINUX】LINUX基础(目录结构、基本权限、基本命令)
    文章目录LINUX的目录结构LINUX的基本权限LINUX基本命令LINUX的目录结构/:表示根目录bin:存放二进制可执行文件(命令ls、cat、mkdir等)boot:存放系统引导文件dev:存放设备文件etc:存放系统配置文件home:存放当前用户的文件(~表示当前用户默认目录、…则是上一级目录、-表......
  • archlinux安装yay和微信
    1.增加archlinuxcn源,编辑/etc/pacman.conf文件,在最后添加如下内容:[archlinuxcn]Server=https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch2.设置在本地信任farseerfc的GPGkey:sudopacman-key--lsign-key"[email protected]"3.安装archlinuxcn-keyring......
  • 每天一个Linux命令(1):xargs
    命令简介xargs可以将stdin中以空格或换行符进行分隔的数据,形成以空格分隔的参数(arguments),传递给其他命令。因为以空格作为分隔符,所以有一些文件名或者其他意义的字符串内含有空格的时候,xargs可能会误判。简单来说,xargs的作用是给其他命令传递参数,是构建单行命令的重要组件之一。......
  • 在Linux/Ubuntu/Debian上安装TensorFlow 2.14.0
    在Ubuntu上安装TensorFlow2.14.0,可以遵循以下步骤。请注意,由于TensorFlow的版本更新可能很快,这里提供的具体步骤可能需要根据你的系统环境和实际情况进行微调。准备工作检查系统要求:确保你的Ubuntu系统满足TensorFlow的运行要求。TensorFlow支持Ubuntu16.04或更高版本......
  • linux 快速部署jar 并加入开机自启(超方便)
    第一步cd/etc/systemd/system/第二步创建app.service可以在本地创建好在传到/etc/systemd/system/目录下/usr/bin/java需要改成自己的java环境对应地址/srv/sites/app.jar改为自己jar存放包地址[Unit]Description=appserviceAfter=syslog.target[Service]Type......
  • 01Linux以及操作系统概述
    课程目标1.了解现代操作系统的整体构成及发展历史2.了解Linux操作系统及其分支版本3.直观上理解服务器端与桌面端版本的区别课程实验1.通过对CentOS和Ubuntu的演示,直观理解Linux与Windows的异同课堂引入本章内容主要为大家详细讲解Linux操作系统(以下简称Linux)的基本情......
  • linux 离线安装docker
    docker-ce下载地址很多情况下我们不能对docker进行在线安装,这时可以参照本篇博客尝试进行离线安装docker-ce1.下载对应版本的安装包2.解压安装包tar-zxvfdocker-XXX.tgz3.将解压的文件复制到指定位置cp./docker/*/usr/bin4.配置docker服务的文件如下cd/etc/syste......
  • linux核心基础-进程与资源管理
    1、linux资源管理器linux中对需要运维去管理、去查看的资源信息,如下内存资源、使用率free命令磁盘资源、使用率dfCPU资源、使用率tophtopglances进程资源、使用率pspstreepidof网络资源、使用率Iftop所有资源的整体查看命令topglanceshtop2、进程2.1区分进......
  • JAVA 数据写入excel并发送邮件
    写这个的时候PLM系统还没有开发好,开发这个系统的外包团队每次开会都会被骂,感觉他们快顶不住了,估计完成不了了,烂尾之后应该会有很多需求扔给我。新领导上任之后说这边能不能发邮件,先熟悉一下怎么发邮件吧,这个功能大概率给我来做了流程:​先导包=>邮箱开启配置=>java写好配......