【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下)

这篇具有很好参考价值的文章主要介绍了【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


开发环境:迅为3568开发板 + ubuntu18.04

前文【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(上)

解决资源竞争的方法

自旋锁

自旋锁(spin lock)是为了保护共享资源提出的一种非阻塞锁机制,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗 CPU 的时间,不停的试图获取锁
举个形象生动的例子,以现实生活中银行 ATM 机办理业务为例,ATM 机防护舱在同一时间内只允许一个人进入,当有人进入 ATM 机防护舱之后,两秒钟之后自动上锁,其他也想要存取款的人员,只能在外部等待,办理完相应的存取款业务之后,舱内人员需要手动打开防护锁,其他人才能进入其中,办理业务。

自旋锁的特点:

  • 被自旋锁保护的临界区代码执行时不能进入休眠。
  • 被自旋锁保护的临界区代码执行时是不能被被其他中断中断。
  • 被自旋锁保护的临界区代码执行时,内核不能被抢占。
  • 由于在自旋锁忙等期间,因为并没有进入临界区,所以内核抢占还是有效的,因此,等待自旋锁释放的进程有可能被更高优先级的所取代。
  • 不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!

自旋锁的死锁

  • 在单核条件下,拥有自旋锁的进程 A 处于内核态阻塞状态,内核调度 B 进程,碰巧 B 进程也要获得自旋锁,此时 B 只能自旋转。并且由于此时不会调度 A 进程,B 永远自旋,产生死锁。
  • 进程 A 拥有自旋锁,但由于中断到来,CPU 会中断当前操作去处理中断函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,从而产生死锁。

内核中以 spinlock_t 结构体来表示自旋锁,具体定义如下:

typedef struct spinlock {
	union {
		struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;

自旋锁的API如下:

/tr>
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。
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) 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁

自旋锁的使用:使用自旋锁完成:同一个驱动仅允许一个应用程序打开。

  • 首先,定义一个自旋锁spinlock_t mylock

  • 其次,当驱动被打开时,获取自旋锁spin_lock(&mylock)

  • 最后,当驱动关闭时,解除自旋锁spin_unlock(&mylock)

完成代码如下:

#include <linux/init.h>              //初始化头文件
#include <linux/module.h>            //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>        //注册杂项设备头文件
#include <linux/fs.h>                //注册设备节点的文件结构体
#include <linux/uaccess.h>

/*	含自旋锁操作驱动 */ 

// 驱动名
#define DEVICE_NAME "mytest"

// 数据buffer
static char kbuff[32] ;

// 定义一个自旋锁
spinlock_t mylock;

// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
	// 给自旋锁加锁
	spin_lock(&mylock);
	printk("misc_open is ok.\r\n");
	return 0;
}

// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
	// 给自旋锁解锁
	spin_unlock(&mylock);
	printk("misc_close\r\n");
	return 0;
}

// 读取设备中信息
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{
	int len = strlen(kbuff);
	if(copy_to_user(buff,kbuff,len) != 0) // 将内核中的数据给应用
	{
		printk("copy_to_user error\r\n");
		return -1;
	}	
	printk("copy_to_user is successful\r\n");

	return len;
}

// 写入设备信息
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{
	int ret = copy_from_user(kbuff,buff,size);
	if( ret != 0) // 从应用那儿获取数据
	{
		printk("ret of error is %d.\r\n",ret);
		return ret;
	}	

	printk("copy_from_user data:%s.\r\n",kbuff);
	return size;
}

// 设备文件操作集
struct file_operations misc_fops ={
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_close,
	.read = misc_read,
	.write = misc_write
};

// 设备文件信息描述集
struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &misc_fops
};

// 驱动的入口函数
static int __init misc_init(void)
{
	// 申请杂项设备
	int ret = 0;
	ret = misc_register(&misc_dev);
	printk("--------------------------------------------\r\n");
	if(ret < 0)
	{
		printk("misc register is error.\r\n");
	}
	else
		printk("misc register is seccussful.\r\n");
	
	return ret;
}

//驱动的出口函数
static void __exit misc_exit(void)
{
	// 注销杂项设备
	misc_deregister(&misc_dev);
	
	printk("misc_exit\r\n");
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");

使用insmod命令加载驱动到内核后,就可编写测试程序进行测试,由于此处的此时程序仅仅只需要打开关闭设备文件,因此此处就不过多赘述。测试结果如下:
【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下),LINUX,linux,驱动开发,运维
通过测试结果可看出,在第一次打开设备文件后,如果在进行第二次打开就需要等待第一次完成关闭后才能再次打开设备文件。

信号量

信号量,本质上是一个全局变量,信号量的值表示控制访问资源的线程数,可以根据实际情况来自行设置:

  • 如果在初始化的时候将信号量量值设置为大于 1,那么这个信号量就是计数型信号量,允许多个线程同时访问共享资源。
  • 如果将信号量量值设置为 1,那么这个信号量就是二值信号量,同一时间内只允许一个线程访问共享资源。
  • 当信号量的值为 0 时,想访问共享资源的线程必须等待,直到信号量大于 0 时,等待的线程才可以访问。
    一般,当访问共享资源时,信号量先执行“减一”操作,访问完成后再执行“加一”操作。

注意!信号量的值不能小于 0 。

与自旋锁的比较

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

信号量的结构体定义如下:

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

不过需要注意的是:不能直接使用结构体中的任何内容。

信号量的API如下:

函数名 作用
DEFINE_SEMAPHORE(name) 定义一个 struct semaphore name 结构体, count 值设置为 1
void sema_init(struct semaphore *sem, int val) 初始化 semaphore
void down(struct semaphore *sem) 获得信号量,如果暂时无法获得就会休眠返回之后就表示肯定获得了信号量在休眠过程中无法被唤醒,即使有信号发给这个进程也不处理
int down_trylock(struct semaphore *sem) 尝试获得信号量,不会休眠,返回值:0:获得了信号量 1:没能获得信号量
void up(struct semaphore *sem) 释放信号量,唤醒其他等待信号量的进程

信号量的使用:使用信号量完成:同一个驱动仅允许一个应用程序打开。

  • 首先,定义一个信号量,可使用宏定义DEFINE_SEMAPHORE(mysemaphore)完成定义。或者使用先定义一个型号量变量,然后再使用函数void sema_init(struct semaphore *sem, int val)完成初始化。可以看到宏定义的实质也是调用函数void sema_init(struct semaphore *sem, int val)完成初始化操作。
#define DEFINE_SEMAPHORE(name)	\
	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
  • 其次,打开设备时,使用函数down(&mysemaphore)执行信号量减一操作。
// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
   // 加锁
   down(&mysemaphore);
   
   printk("misc_open is ok.\r\n");
   return 0;
}

  • 最后,关闭设备时,使用函数up(&mysemaphore)完成信号量加一操作。
// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
	// 解锁
	up(&mysemaphore);

	printk("misc_close\r\n");
	return 0;
}

由于此处的实现驱动较为简答,因此,不再过多描述。大家直接将此处的misc_openmisc_close替换自旋锁的函数即可。并且两者的测试程序也是一样的。测试结果如下:
【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下),LINUX,linux,驱动开发,运维

互斥锁

互斥锁,可以将之当成初值为1的信号量来理解。虽然两者功能相同但是具体实现方式不同,但是当需要完成互斥功能时,直接使用互斥锁效率更高、更简洁,所以如果使用到的信号量“量值”为 1,一般将其修改为使用互斥锁。
互斥锁会导致休眠,所以在中断里面不能用互斥锁。同一时刻只能有一个线程持有互斥锁,并且只有持有者才可以解锁,并且不允许递归上锁和解锁。

互斥锁需要注意:

  • 一次只能有一个任务持有互斥对象

  • 只有所有者才能解锁互斥对象

  • 不允许多次解锁

  • 不允许递归锁定

  • 必须通过API初始化互斥对象

  • 互斥对象不能通过memset或复制进行初始化

  • 任务不能在持有互斥对象的情况下退出

  • 不得释放持有锁所在的内存区域

  • 持有的互斥对象不能重新初始化

  • 互斥不能用于硬件或软件中断

互斥锁定义结构体如下:

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_init(mutex) 初始化一个 struct mutex 指针
DEFINE_MUTEX(mutexname) 初始化 struct mutex mutexname
int mutex_is_locked(struct mutex *lock) 判断 mutex 的状态1:被锁了(locked)0:没有被锁
void mutex_lock(struct mutex *lock) 获得 mutex,如果暂时无法获得,休眠返回之时必定是已经获得了 mutex
int mutex_trylock(struct mutex *lock) 尝试获取 mutex,如果无法获得,不会休眠,返回值:1:获得了 mutex,0:没有获得注意,这个返回值含义跟一般的 mutex 函数相反
void mutex_unlock(struct mutex *lock) 释放 mutex,会唤醒其他等待同一个 mutex 的线程

互斥锁的使用:使用互斥锁完成:同一个驱动仅允许一个应用程序打开。

  • 先使用宏定义定义一个互斥锁 DEFINE_MUTEX(mymutex),参数即为互斥锁的名字。或者也可先定义一个互斥锁变量struct mutex,然后再使用函数 mutex_init初始化。宏定义的原型为:
#define DEFINE_MUTEX(mutexname) \
	struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
  • 其次,打开设备时,使用函数mutex_lock(&mylock)执行加锁操作。
// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
   // 加锁
   mutex_lock(&mylock);
   printk("misc_open is ok.\r\n");
   return 0;
}
  • 最后,关闭设备时,使用函数mutex_unlock(&mylock)完成解锁操作。
// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
	// 解锁
	mutex_unlock(&mylock);

	printk("misc_close\r\n");
	return 0;
}

由于此处的实现驱动较为简答,因此,不再过多描述。大家直接将此处的misc_openmisc_close替换自旋锁的函数即可。并且两者的测试程序也是一样的。测试结果如下:

【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下),LINUX,linux,驱动开发,运维
(是不是感觉三次的测试结果差不多,哈哈哈!😂😂😂)

互斥锁与信号量的区别:

  • 信号量一般以同步的方式对共享资源进行控制,而互斥锁通过互斥实现共享资源的控制;

  • 信号量可以对进程的共享资源进行控制,而互斥锁不行;

  • 信号量的值为非负整数,而互斥锁的值只能为0或1;

  • 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到;mutex和二值信号量的区别在于mutex必须是同一个进程来释放

自旋锁与互斥锁的区别:

  • 因为自旋锁不会引起调用者睡眠,所以效率比较高

  • 自旋锁比较适用于锁使用者保持锁时间比较短的情况。

  • 自旋锁容易造成死锁,所以需要安全使用它;文章来源地址https://www.toymoban.com/news/detail-827013.html

到了这里,关于【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 驱动之并发与竞争

    并发会造成多个程序同时访问一个共享资源,这时候由并发同时访问一个共享资源而产生的问题就是竞争。比如A和B要打电话,但是公共电话只有一部。A和B要打电话就是并发,谁先打电话就是竞争。但是电话谁都可以用,所以电话就是共享资源。 Linux 是一个多任务的操作系

    2023年04月24日
    浏览(32)
  • 【看表情包学Linux】进程优先级 | 查看系统进程 | 优先级修改 | 进程的切换 | 竞争性与独立性 | 并行并发的概念 | 环境变量

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 ​ 💭 写在前面: 我们先讲解进程的优先级,探讨为什么会存在优先级,以及如何查看系统进程、进程优先级的修改。然后讲解进程的切换,首次介绍进程的竞争性、独立性,以及并行和并发的概念,在通

    2024年01月19日
    浏览(41)
  • 【嵌入式环境下linux内核及驱动学习笔记-(5-驱动的并发控制机制)】

    在讨论并发前,先要了解以下几个概念:执行流,上下文,共享与临界等。 什么叫执行流: 【执行流】:有开始有结束总体顺序执行的一段代码 又称 上下文 。 上下文分类: 【任务上下文】:普通的,具有五种状态(就绪态、运行态、睡眠态、暂停态、僵死态),可被阻塞

    2023年04月21日
    浏览(46)
  • Java并发(一)----进程、线程、并行、并发

    进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了

    2023年04月10日
    浏览(62)
  • 第19章 并发与竞争实验(iTOP-RK3568开发板驱动开发指南 )

    瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网

    2024年02月09日
    浏览(46)
  • 深入理解并发和并行

    为什么操作系统上可以同时运行多个程序而用户感觉不出来? 因为操作系统营造出了可以同时运行多个程序的假象,通过调度进程以及快速切换CPU上下文,每个进程执行一会就停下来,切换到下个被调度到的进程上,这种切换速度非常快,人无法感知到,从而产生了多个任务

    2024年04月13日
    浏览(39)
  • 【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

      当我们编写程序时,经常需要处理各种数据结构。队列是一种常见的数据结构,它有着广泛的应用场景。队列的基本操作包括入队和出队,应用于模拟等待队列、消息队列、计算机缓存等场合。   在实际编程中,我们可以用不同的数据结构来实现队列。本文主要介绍了

    2024年02月08日
    浏览(116)
  • 详解 javascript 中并发与并行

    还是大剑师兰特 :曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,echarts等技术开发,欢迎加底部微信(gis-dajianshi),一起交流。 No. 内容链接 1 Openlayers 【入门教

    2024年04月27日
    浏览(29)
  • 并发,并行,线程与UI操作

    并行和并发是计算机领域中两个相关但不同的概念。 并行(Parallel)指的是同时执行多个任务或操作 ,它依赖于具有多个处理单元的系统。在并行计算中,任务被分成多个子任务,并且这些子任务可以同时在不同的处理单元上执行,从而加速整体的计算速度。并行计算能够充

    2024年01月21日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包