提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、什么是并发?
- 并发
- 并行
- 并发+并行模式
- 二、什么是竞争
- 三、如何解决竞争
- 1、原子操作
- 整形原子操作:
- 原子位操作
- 2.自旋锁
- 3.信号量
- 4.互斥锁
- 5.如何选择哪种方式
- 总结
前言
在Linux驱动开发中我们经常要考虑的一个问题就是并发和竞争,因为现在的SOC基本都是多核CPU,那么必然会存在争夺资源的问题,所以本小节就来介绍一下驱动中的并发和竞争。
一、什么是并发?
并发
所谓并发,就是通过算法将 CPU 资源合理地分配给多个任务,当一个任务执行 I/O 操作时,CPU 可以转而执行其它的任务,等到 I/O 操作完成以后,或者新的任务遇到 I/O 操作时,CPU 再回到原来的任务继续执行。
举个例子:
假设现在是有两个任务,那么当任务1执行的时候,CPU就会切换到任务2,这样任务2执行一定时间后又切换回任务1,这样两个任务看起来就是同时进行,也就是所谓的并发。
肯定还有人听过并行
,并发和并行通常是面试的高频考点,所以这里也解释下什么是并行:
并行
并发是针对单核 CPU 提出的,而并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行多个任务”。多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。双核 CPU 的工作状态如下图(图 19-2)所示:
双核 CPU 执行两个任务时,每个核心各自执行一个任务,和单核 CPU 在两个任务之间不断切换相比,它的执行效率更高
我们的开发板RK3568是A55架构是4核的芯片,可以看出瑞芯微的这款芯片还是非常强大的。
虽然我们的芯片是4核,也就是说如果只是按照并行的任务的话,那同时也就只能4个任务,可是我们一个系统怎么可能同时只能是4个任务呢,所以在真实的工作中,通常是并行+并发
模式
并发+并行模式
同时存在并发和并行两种情况,即所有核心在并行工作的同时,每个核心还要并发工作
例如一个双核 CPU 要执行四个任务,它的工作状态如下图(图 19-3)所示:
并发可以看作是并行的理想状态,为了便于讲解和避免产生歧义,之后的章节无论是并发
还是并行,都会统称为并发
二、什么是竞争
上面介绍到我们的系统是并发进行的,那么并发进行就会存在着对资源的竞争
举个简单的例子:
在多线程中,多个线程都对同一个全局变量进行了访问,假设在线程A中还没读取这个变量的值就被打断,变量被线程B修改,那么这个线程A最终访问到的变量自然不是我们要访问的结果,这个变量就是资源,多线程之间就存在着竞争。
一般产生竞争的原因有以下几点:
(1)多线程的并发访问。由于 Linux 是多任务操作系统,所以多线程访问是竞争产生的
基本原因。
(2)中断程序的并发访问。中断任务产生后,CPU 会立刻停止当前工作,从而去执行中
断中的任务,如果中断任务对共享资源进行了修改,就会产生竞争。
(3)抢占式并发访问。linux2.6 及更高版本引入了抢占式内核,高优先级的任务可以打断低优先级的任务。在线程访问共享资源的时候,另一个线程打断了现在正在访问共享资源的线程同时也对共享资源进行操作,从而造成了竞争。
(4)多处理器(SMP)并发访问。多核处理器之间存在核间并发访问。
三、如何解决竞争
在 Linux 内核中,提供了四种处理并发与竞争的常见方法,分别是原子操作、自旋锁、信号量、互斥体
1、原子操作
在 Linux 内核中的原子操作可以理解为“不可被拆分的操作”,就是不能被更高等级中断抢夺优先的操作
原子操作又可以进一步细分为“整型原子操作”和“位原子操作”
整形原子操作:
在 Linux 内核中使用 atomic_t
和 atomic64_t
结构体分别来完成 32 位系统和 64 位系统的整形数据原子操作,两个结构体定义在“内核源码/include/linux/types.h
”文件中,具体定义如下:
typedef struct {
int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
long counter;
} atomic64_t;
#endif
操作原子的API
在成功定义原子变量之后,必然要对原子变量进行读取、加减等动作,原子操作的部分常
用 API 函数如下所示,定义在“内核源码/include/linux/atomic.h
”文件中
有了内核提供的这些原子API函数我们可以轻松对某个整形变量进行原子操作,可以看出操作还是非常简单的。
原子位操作
原子位操作没有 atomic_t 的数据结构,原子位操作是直接对内存进行操作,原子位操作相关 API 函数如下(图表 20-2)所示:
举个编写原子操作的代码:
static atomic64_t v = ATOMIC_INIT(1);//初始化原子类型变量 v,并设置为 1
static int open_test(struct inode *inode,struct file *file)
{
if(atomic64_read(&v) != 1){//读取原子类型变量 v 的值并判断是否等于 1
return -EBUSY;
}
atomic64_set(&v,0);//将原子类型变量 v 的值设置为 0
//printk("\nthis is open_test \n");
return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
//printk("\nthis is release_test \n");
atomic64_set(&v,1);//将原子类型变量 v 的值赋 1
return 0;
}
这样的一个驱动,表明,只有第一个应用打开该文件时才有效,,因为当第一个应用打开该文件后的原子变量就被设置为0,那么其他应用程序再进来的时候该变量为0就会打开失败。
2.自旋锁
3.信号量
4.互斥锁
5.如何选择哪种方式
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。