返回顶部
首页 > 资讯 > 操作系统 >linux驱动中并发与竟态的示例分析
  • 183
分享到

linux驱动中并发与竟态的示例分析

2023-06-19 10:06:11 183人浏览 薄情痞子
摘要

小编给大家分享一下linux驱动中并发与竟态的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!首先什么是并发与竟态呢?并发(concurrency)指的是多

小编给大家分享一下linux驱动中并发与竟态的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行被执行。而并发的执行单元对共享资源(硬件资源和软件上的全局、静态变量)的访问则容易导致竞态(race conditions)。可能导致并发和竟态的情况有:

  • SMP(Symmetric Multi-Processing),对称多处理结构。SMP是一种紧耦合、共享存储的系统模型,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。 

  • 中断。中断可 打断正在执行的进程,若中断处理程序访问进程正在访问的资源,则竞态也会发生。中断也可能被新的更高优先级的中断打断,因此,多个中断之间也可能引起并发而导致竞态。

  • 内核进程的抢占。linux是可抢占的,所以一个内核进程可能被另一个高优先级的内核进程抢占。如果两个进程共同访问共享资源,就会出现竟态。

以上三种情况只有SMP是真正意义上的并行,而其他都是宏观上的并行,微观上的串行。但其都会引发对临界共享区的竞争问题。而解决竞态问题的途径是保证对共享资源的互斥访问,即一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。那么linux内核中如何做到对对共享资源的互斥访问呢?在linux驱动编程中,常用的解决并发与竟态的手段有信号量与互斥,Completions 机制,自旋锁(spin lock),以及一些其他的不使用锁的实现方式。下面一一介绍。

信号量与互斥锁

信号量其实就是一个整型值,其核心是一个想进入临界区的进程将在相关信号量上调用 P; 如果信号量的值大于零, 这个值递减 1 并且进程继续. 相反,,如果信号量的值是 0 ( 或更小 ), 进程必须等待直到别人释放信号量. 解锁一个信号量通过调用 V 完成; 这个函数递增信号量的值,,并且, 如果需要, 唤醒等待的进程。而当信号量的初始值为1的时候,就变成了互斥锁。

信号量的典型使用形式:

//声明信号量struct semaphore sem;//初始化信号量void sema_init(struct semaphore *sem, int val)    //常用下面两种形式#define init_MUTEX(sem) sema_init(sem, 1)#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)    //以下是初始化信号量的快捷方式,最常用的DECLARE_MUTEX(name)    //初始化name的信号量为1DECLARE_MUTEX_LOCKED(name) //初始化信号量为0//常用操作DECLARE_MUTEX(mount_sem);down(&mount_sem); //获取信号量...critical section    //临界区...up(&mount_sem);    //释放信号量

常用的down操作还有

// 类似down(),因为down()而进入休眠的进程不能被信号打断,而因为down_interruptible()而进入休眠的进程能被信号打断, // 信号也会导致该函数返回,此时返回值非0int down_interruptible(struct semaphore *sem);// 尝试获得信号量sem,若立即获得,它就获得该信号量并返回0,否则,返回非0.它不会导致调用者睡眠,可在中断上下文使用int down_trylock(struct semaphore *sem);

Completions 机制

完成量(completion)提供了一种比信号量更好的同步机制,它用于一个执行单元等待另一个执行单元执行完某事。

</pre></div><div><pre name="code" class="cpp">// 定义完成量struct completion my_completion; // 初始化completioninit_completion(&my_completion); // 定义和初始化快捷方式:DECLEAR_COMPLETION(my_completion); // 等待一个completion被唤醒void wait_for_completion(struct completion *c); // 唤醒完成量void cmplete(struct completion *c);void cmplete_all(struct completion *c);

自旋锁

若一个进程要访问临界资源,测试锁空闲,则进程获得这个锁并继续执行;若测试结果表明锁扔被占用,进程将在一个小的循环内重复“测试并设置”操作,进行所谓的“自旋”,等待自旋锁持有者释放这个锁。自旋锁与互斥锁类似,但是互斥锁不能用在可能睡眠的代码中,而自旋锁可以用在可睡眠的代码中,典型的应用是可以用在中断处理函数中。自旋锁的相关操作:

// 定义自旋锁 spinlock_t spin;  // 初始化自旋锁spin_lock_init(lock); // 获得自旋锁:若能立即获得锁,它获得锁并返回,否则,自旋,直到该锁持有者释放spin_lock(lock);  // 尝试获得自旋锁:若能立即获得锁,它获得并返回真,否则立即返回假,不再自旋spin_trylock(lock);  // 释放自旋锁: 与spin_lock(lock)和spin_trylock(lock)配对使用spin_unlock(lock);    自旋锁的使用:// 定义一个自旋锁spinlock_t lock;spin_lock_init(&lock); spin_lock(&lock);  // 获取自旋锁,保护临界区...  // 临界区spin_unlock();  // 解锁


自旋锁持有期间内核的抢占将被禁止。自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。为防止这种影响,需要用到自旋锁的衍生:

spin_lock_irq() = spin_lock() + local_irq_disable()spin_unlock_irq() = spin_unlock() + local_irq_enable()spin_lock_irqsave() = spin_lock() + local_irq_save()spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()spin_lock_bh() = spin_lock() + local_bh_disable()spin_unlock_bh() = spin_unlock() + local_bh_enable()

其他的一些选择

以上是linux驱动编程中经常用到的锁机制,下面讲一些内核中其他的一些实现。

不加锁算法

有时, 你可以重新打造你的算法来完全避免加锁的需要.。许多读者/写者情况 -- 如果只有一个写者 -- 常常能够在这个方式下工作.。如果写者小心使数据结构,由读者所见的,是一直一致的,,有可能创建一个不加锁的数据结构。在linux内核中就有一个通用的无锁的环形缓冲实现,具体内容参考<linux/kfifo.h>。

原子变量与位操作

原子操作指的是在执行过程中不会被别的代码路径所中断的操作。原子变量与位操作都是原子操作。以下是其相关操作介绍。

// 设置原子变量的值void atomic_set(atomic_t *v, int i);  // 设置原子变量的值为iatomic_t v = ATOMIC_INIT(0);  // 定义原子变量v,并初始化为0 // 获取原子变量的值atomic_read(atomic_t *v);  // 返回原子变量的值 // 原子变量加/减void atomic_add(int i, atomic_t *v);  // 原子变量加ivoid atomic_sub(int i, atomic_t *v);  // 原子变量减i // 原子变量自增/自减void atomic_inc(atomic_t *v);  // 原子变量增加1void atomic_dec(atomic_t *v);  // 原子变量减少1 // 操作并测试:对原子变量进行自增、自减和减操作后(没有加)测试其是否为0,为0则返回true,否则返回falseint atomic_inc_and_test(atomic_t *v);int atomic_dec_and_test(atomic_t *v);int atomic_sub_and_test(int i, atomic_t *v); // 操作并返回: 对原子变量进行加/减和自增/自减操作,并返回新的值int atomic_add_return(int i, atomic_t *v);int atomic_sub_return(int i, atomic_t *v);int atomic_inc_return(atomic_t *v);int atomic_dec_return(atomic_t *v);  位原子操作:// 设置位void set_bit(nr, void *addr);  // 设置addr地址的第nr位,即将位写1 // 清除位void clear_bit(nr, void *addr);  // 清除addr地址的第nr位,即将位写0 // 改变位void change_bit(nr, void *addr);  // 对addr地址的第nr位取反 // 测试位test_bit(nr, void *addr); // 返回addr地址的第nr位 // 测试并操作:等同于执行test_bit(nr, void *addr)后再执行xxx_bit(nr, void *addr)int test_and_set_bit(nr, void *addr);int test_and_clear_bit(nr, void *addr);int test_and_change_bit(nr, void *addr);
seqlock(顺序锁)

使用seqlock锁,读执行单元不会被写执行单元阻塞,即读执行单元可以在写执行单元对被seqlock锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。写执行单元之间仍是互斥的。若读操作期间,发生了写操作,必须重新读取数据。seqlock锁必须要求被保护的共享资源不含有指针。

// 获得顺序锁void write_seqlock(seqlock_t *sl);int write_tryseqlock(seqlock_t *sl);write_seqlock_irqsave(lock, flags)write_seqlock_irq(lock)write_seqlock_bh() // 释放顺序锁void write_sequnlock(seqlock_t *sl);write_sequnlock_irqrestore(lock, flags)write_sequnlock_irq(lock)write_sequnlock_bh() // 写执行单元使用顺序锁的模式如下:write_seqlock(&seqlock_a);...  // 写操作代码块write_sequnlock(&seqlock_a);  读执行单元操作:// 读开始:返回顺序锁sl当前顺序号unsigned read_seqbegin(const seqlock_t *sl);read_seqbegin_irqsave(lock, flags) // 重读:读执行单元在访问完被顺序锁sl保护的共享资源后需要调用该函数来检查,在读访问期间是否有写操作。若有写操作,重读int read_seqretry(const seqlock_t *sl, unsigned iv);read_seqretry_irqrestore(lock, iv, flags) // 读执行单元使用顺序锁的模式如下:do{    seqnum = read_seqbegin(&seqlock_a);    // 读操作代码块     ...}while(read_seqretry(&seqlock_a, seqnum));
读取-拷贝-更新(RCU)

读取-拷贝-更新(RCU) 是一个高级的互斥方法,在合适的时候可以取得非常高的效率。RCU可以看作读写锁的高性能版本,相比读写锁,RCU的优点在于既允许多个读执行单元同时访问被保护的数据,又允许多个读执行单元和多个写执行单元同时访问被保护的数据。但是RCU不能替代读写锁,因为如果写比较多时,对读执行单元的性能提高不能弥补写执行单元导致的损失。由于平时应用较少,所以不做多说。

以上是“linux驱动中并发与竟态的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网操作系统频道!

--结束END--

本文标题: linux驱动中并发与竟态的示例分析

本文链接: https://lsjlt.com/news/295452.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • linux驱动中并发与竟态的示例分析
    小编给大家分享一下linux驱动中并发与竟态的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!首先什么是并发与竟态呢?并发(concurrency)指的是多...
    99+
    2023-06-19
  • linux驱动程序开发的示例分析
    这篇文章主要为大家展示了“linux驱动程序开发的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“linux驱动程序开发的示例分析”这篇文章吧。前提,一般来说内核代码的错误可能会引起一个用...
    99+
    2023-06-13
  • Linux设备驱动开发的示例分析
    今天就跟大家聊聊有关Linux设备驱动开发的示例分析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。编译和运行驱动编译要用到kernel的Makefile文件 &mdash; ...
    99+
    2023-06-28
  • Hive中静态分区与动态分区的示例分析
    这篇文章给大家分享的是有关Hive中静态分区与动态分区的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。  分区是hive存放数据的一种方式。将列值作为目录来存放数据,就是一个分区。这样查询时使用分区列进行...
    99+
    2023-06-02
  • Linux中启动与自启动的示例分析
    这篇文章将为大家详细讲解有关Linux中启动与自启动的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Linux启动与自启动的实例详解一 启动与自启动服务启动:就是在当前系统中让服务运行,并提供功能...
    99+
    2023-06-09
  • Linux动态库和静态库的示例分析
    今天就跟大家聊聊有关Linux动态库和静态库的示例分析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。函数库分为静态库和动态库两种。创建Linux静态库和Linux动态库和使用它们在这...
    99+
    2023-06-16
  • Java中多线程与并发的示例分析
    这篇文章主要介绍Java中多线程与并发的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、进程与线程进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。线程:是进程的一个执行路径,一个...
    99+
    2023-06-15
  • linux中Intel 845集成显示驱动下载的示例分析
    这篇文章将为大家详细讲解有关linux中Intel 845集成显示驱动下载的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。82845 linux intel集成显卡驱动下载驱动地址:http://...
    99+
    2023-06-13
  • golang中并发和并行的示例分析
    这篇文章主要介绍了golang中并发和并行的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。什么是golanggolang 是Google开发的一种静态强类型、编译型、...
    99+
    2023-06-15
  • java高并发中并发级别的示例分析
    这篇文章给大家分享的是有关java高并发中并发级别的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。阻塞一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用synchronized...
    99+
    2023-06-25
  • JDK中动态代理的示例分析
    这篇文章将为大家详细讲解有关JDK中动态代理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。动态代理步骤创建一个实现接口InvocationHandler的类,它必须实现invoke方法创建被代理...
    99+
    2023-06-15
  • Java中并发容器的示例分析
    这篇文章给大家分享的是有关Java中并发容器的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、并发容器1.1 JDK 提供的并发容器总结JDK 提供的这些容器大部分在java.util.concurre...
    99+
    2023-06-15
  • React中并发功能的示例分析
    这篇文章将为大家详细讲解有关React中并发功能的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。React 是一个开源 JavaScript 库,开发人员使用它来创建基于 Web 和移动的应用程序...
    99+
    2023-06-20
  • Linux中Shell多进程并发以及并发数控制的示例分析
    这篇文章主要介绍了Linux中Shell多进程并发以及并发数控制的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1. 基础知识准备1. linux后台进程Unix是一...
    99+
    2023-06-10
  • mysql.data.dll驱动各版本的示例分析
    这篇文章主要介绍mysql.data.dll驱动各版本的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!此处为mysql驱动mysql.data.dll注意:此处X86版本较多...
    99+
    2024-04-02
  • JUC并发编程中进程与线程的示例分析
    这篇文章将为大家详细讲解有关JUC并发编程中进程与线程的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。进程与线程进程程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,...
    99+
    2023-06-29
  • 网站SEO优化与推广并驾齐驱的示例分析
    这篇文章主要介绍了网站SEO优化与推广并驾齐驱的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。  首先是页面的title(很重要)一般书写按照”文章名+品牌词“的格式...
    99+
    2023-06-10
  • java并发中wait notify notifyAll的示例分析
    这篇文章主要介绍java并发中wait notify notifyAll的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、前言java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题...
    99+
    2023-06-15
  • Java并发中AQS原理的示例分析
    这篇文章给大家分享的是有关Java并发中AQS原理的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。线程阻塞原语Java 的线程阻塞和唤醒是通过 Unsafe 类的 park 和 unpark 方法做到的。...
    99+
    2023-06-02
  • linux驱动之Kconfig文件和Makefile文件的示例分析
    小编给大家分享一下linux驱动之Kconfig文件和Makefile文件的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!linux 驱动之Kconfig文件和Makefile文件实例在Linux编写驱动的过程中,...
    99+
    2023-06-09
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作