目录
中断屏蔽" >中断屏蔽
原子操作" >原子操作
整型原子操作" >整型原子操作
位原子操作" >位原子操作
自旋锁(spinlock)" >自旋锁(spinlock)
Basic" >Basic
Reader/Writer Spinlocks" >Reader/Writer Spinlocks
seqlock" >seqlock
RCU(Read-Copy-Update)" >RCU(Read-Copy-Update)
注意事项" >注意事项
信号量 semaphore" >信号量 semaphore
Reader/Writer Semaphores" >Reader/Writer Semaphores
完成量 completion" >完成量 completion
首页 系统教程 操作系统 Linux设备驱动中如何解决并发控制问题?

Linux设备驱动中如何解决并发控制问题?

Feb 13, 2024 pm 07:24 PM
linux linux教程 linux系统 并发访问 linux命令 外壳脚本 同步机制 嵌入式linux linux入门 linux学习

在Linux设备驱动中,当多个执行单元同时访问相同的资源时,可能会引发“竞态”,导致数据不一致或系统崩溃。因此,我们必须对共享资源进行并发控制,保证其互斥访问。本文将介绍Linux内核中解决并发控制的常用方法,包括中断屏蔽、原子操作、自旋锁、信号量、互斥体等,并给出相应的示例代码。

Linux设备驱动中如何解决并发控制问题?

Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态。
中断屏蔽、原子操作、自旋锁和信号量都是解决并发问题的机制。中断屏蔽很少单独被使用,原子操作只能针对整数进行,因此自旋锁和信号量应用最为广泛。
自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小。信号量允许临界区阻塞,可以适用于临界区大的情况。
读写自旋锁和读写信号量分别是放宽了条件的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读。


中断屏蔽

访问共享资源的代码区域称为临界区( critical sections),在单 CPU 范围内避免竞态的一种简单而省事的方法是在进入临界区之前屏蔽系统的中断。中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于 Linux 内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也得以避免了。

    local_irq_disable(); /* 屏蔽中断 */
    ...
    critical section /* 临界区*/
    ...
    local_irq_enable(); /* 开中断 */
登录后复制

但是由于 Linux 的异步 I/O、进程调度等很多重要操作都依赖于中断,长时间屏蔽中断是很危险的;而且中断屏蔽只对本 CPU 内的中断有效,因此也并不能解决 SMP 多 CPU 引发的竞态。在实际应用中并不推荐直接使用,适宜与下文的自旋锁结合使用。


原子操作

Linux 内核提供了一系列函数来实现内核中的原子操作,这些函数又分为两类,分别针对位和整型变量进行原子操作。它们的共同点是在任何情况下操作都是原子的,内核代码可以安全地调用它们而不被打断。

整型原子操作

  • 设置原子变量的值

    #include 
    void atomic_set(atomic_t *v, int i); /* 设置原子变量的值为 i */
    atomic_t v = ATOMIC_INIT(0);         /* 定义原子变量 v 并初始化为 0 */
    
    登录后复制
  • 获取原子变量的值

    int atomic_read(atomic_t *v); /* 返回原子变量的值*/
    
    登录后复制
  • 原子变量加/减

    void atomic_add(int i, atomic_t *v); /* 原子变量增加 i */
    void atomic_sub(int i, atomic_t *v); /* 原子变量减少 i */
    
    void atomic_inc(atomic_t *v); /* 原子变量自增 1 */
    void atomic_dec(atomic_t *v); /* 原子变量自减 1 */
    
    /* 操作完结果==0, return true */
    int 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);
    
    /* 操作完结果 return true */
    int atomic_add_negative(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);
    
    登录后复制

位原子操作

位原子操作相当快,一般只需一个机器指令,不需关中断。

  • set/clear/toggle

    #include 
    
    /* 更改指针addr所指数据的第nr位 */
    void set_bit(nr, void *addr);
    void clear_bit(nr, void *addr);
    void change_bit(nr, void *addr);
    
    登录后复制
  • test

    int test_bit(nr, void *addr); /* 返回第nr位 */
    
    登录后复制
  • 测试并操作

    /* 操作第nr位,并返回操作前的值 */
    int test_and_set_bit(nr, void *addr);
    int test_and_clear_bit(nr, void *addr);
    int test_and_change_bit(nr, void *addr);
    
    登录后复制

自旋锁(spinlock)

自旋锁(spinlock)是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式。为了获得一个自旋锁, 在某 CPU 上运行的代码需先执行一个原子操作,该操作测试并设置( test-and-set) 某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行; 如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“ 测试并设置” 操作,即进行所谓的“ 自旋”,通俗地说就是“在原地打转”。 当自旋锁的持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置” 操作向其调用者报告锁已释放。

Basic

  • 定义/初始化

    #include 
    
    /* 静态初始化 */
    spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
    /* 动态初始化 */
    void spin_lock_init(spinlock_t *lock);
    
    登录后复制
  • 获取/释放

    /* 基本操作 */
    void spin_lock(spinlock_t *lock);
    void spin_unlock(spinlock_t *lock);
    
    /* 保存中断状态并关闭 == spin_lock() + local_irq_save() */
    void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
    void spin_unlock_irqsave(spinlock_t *lock, unsigned long flags);
    
    /* 忽略操作前中断状态 */
    void spin_lock_irq(spinlock_t *lock);
    void spin_unlock_irq(spinlock_t *lock);
    
    /* 关闭中断底部(即关闭软件中断,打开硬件中断,详见后续中断的讲解) */
    void spin_lock_bh(spinlock_t *lock);
    void spin_unlock_bh(spinlock_t *lock);
    
    /* 非阻塞获取,成功返回非0 */
    int spin_trylock(spinlock_t *lock);
    int spin_trylock_bh(spinlock_t *lock);
    
    登录后复制

Reader/Writer Spinlocks

粒度更小,可多Reader同时读,但Writer只能单独,且读与写不能同时,适用于写很少读很多的情况。

  • 定义/初始化

    rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
    rwlock_t my_rwlock;
    rwlock_init(&my_rwlock); /* 动态初始化 */
    
    登录后复制
  • void read_lock(rwlock_t *lock);
    void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
    void read_lock_irq(rwlock_t *lock);
    void read_lock_bh(rwlock_t *lock);
    
    void read_unlock(rwlock_t *lock);
    void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
    void read_unlock_irq(rwlock_t *lock);
    void read_unlock_bh(rwlock_t *lock);
    
    登录后复制
  • void write_lock(rwlock_t *lock);
    void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
    void write_lock_irq(rwlock_t *lock);
    void write_lock_bh(rwlock_t *lock);
    
    void write_unlock(rwlock_t *lock);
    void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
    void write_unlock_irq(rwlock_t *lock);
    void write_unlock_bh(rwlock_t *lock);
    
    登录后复制

seqlock

顺序锁(seqlock)是对读写锁的一种优化,采用了重读机制,读写不相互阻塞。

  • 定义/初始化

    #include 
    
    seqlock_t lock1 = SEQLOCK_UNLOCKED; /* 静态 */
    seqlock_t lock2;
    seqlock_init(&lock2); /* 动态 */
    
    登录后复制
  • /* 读之前先获取个顺序号,读完与当前顺序号对比,如不一致则重读 */
    unsigned int seq;
    do {
        seq = read_seqbegin(&the_lock);
        /* Do what you need to do */
    } while read_seqretry(&the_lock, seq);
    
    /* 如果这个锁可能会出现在中断程序中获取,则在这里应使用关中断版本 */
    unsigned int read_seqbegin_irqsave(seqlock_t *lock,unsigned long flags);
    int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq,unsigned long flags);
    
    登录后复制
  • void write_seqlock(seqlock_t *lock);
    void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
    void write_seqlock_irq(seqlock_t *lock);
    void write_seqlock_bh(seqlock_t *lock);
    int write_tryseqlock(seqlock_t *lock);
    
    void write_sequnlock(seqlock_t *lock);
    void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
    void write_sequnlock_irq(seqlock_t *lock);
    void write_sequnlock_bh(seqlock_t *lock);
    
    登录后复制

RCU(Read-Copy-Update)

对于被 RCU 保护的共享数据结构,读执行单元不需要获得任何锁就可以访问它,因此读执行单元没有任何同步开销。使用 RCU 的写执行单元在访问它前需首先拷贝一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的 CPU 都退出对共享数据的操作的时候。写执行单元的同步开销则取决于使用的写执行单元间同步机制。RCU在驱动中很少使用,这里暂不详述。

注意事项

  • 自旋锁实际上是忙等锁,当锁不可用时, CPU 一直循环执行“测试并设置”该锁直到可用而取得该锁, CPU 在等待自旋锁时不做任何有用的工作,仅仅是等待。 因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。 当临界区很大,或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
  • 自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的 CPU 想第二次获得这个自旋锁,则该 CPU 将死锁。
  • 自旋锁锁定期间不能调用可能引起进程调度而导致休眠的函数。如果进程获得自旋锁之后再阻塞, 如调用 copy_from_user()、 copy_to_user()、 kmalloc()和 msleep()等函数,则可能导致内核的崩溃。

信号量 semaphore

使用方式和自旋锁类似,不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

  • 定义/初始化

    #include 
    
    struct semaphore sem;
    void sema_init(struct semaphore *sem, int val);
    
    /* 通常我们将val的值置1,即使用互斥模式 */
    DECLARE_MUTEX(name);
    DECLARE_MUTEX_LOCKED(name);
    void init_MUTEX(struct semaphore *sem);
    void init_MUTEX_LOCKED(struct semaphore *sem);
    
    登录后复制
  • 获得信号量

    void down(struct semaphore * sem); /* 信号量减1, 会导致睡眠,因此不能在中断上下文使用 */
    int down_interruptible(struct semaphore * sem); /* 与down不同的是,进入睡眠后的进程可被打断返回非0 */ 
    int down_trylock(struct semaphore * sem); /* 非阻塞版本,获得返回0,不会导致睡眠,可在中断上下文使用 */
    
    登录后复制
  • 释放信号量

    void up(struct semaphore * sem);
    
    登录后复制

Reader/Writer Semaphores

读写信号量与信号量的关系与读写自旋锁和自旋锁的关系类似,读写信号量可能引起进程阻塞,但它可允许 N 个读执行单元同时访问共享资源, 而最多只能有 1 个写执行单元。因此,读写信号量是一种相对放宽条件的粒度稍大于信号量的互斥机制。

  • 定义/初始化

    #include 
    
    struct rw_semaphore;
    void init_rwsem(struct rw_semaphore *sem);
    
    登录后复制
  • void down_read(struct rw_semaphore *sem);
    int down_read_trylock(struct rw_semaphore *sem);
    void up_read(struct rw_semaphore *sem);
    
    登录后复制
  • /* 写比读优先级高,写时所有读只能等待 */
    void down_write(struct rw_semaphore *sem);
    int down_write_trylock(struct rw_semaphore *sem);
    void up_write(struct rw_semaphore *sem);
    
    登录后复制

完成量 completion

轻量级,用于一个执行单元等待另一个执行单元执行完某事。

  • 定义/初始化

    #include 
    
    /* 静态 */
    DECLARE_COMPLETION(name);
    /* 动态 */
    struct completion my_completion;
    init_completion(struct completion *c);
    
    INIT_COMPLETION(struct completion c); /* 重新初始化已经定义并使用过的 completion */
    
    登录后复制
  • 等待完成

    void wait_for_completion(struct completion *c);
    
    登录后复制
  • 完成信号

    void complete(struct completion *c); /* 唤醒1个 */
    void complete_all(struct completion *c); /* 唤醒所有waiter */
    void complete_and_exit(struct completion *c, long retval); /* call complete() and exit(retval) */
    
    登录后复制

    本文总结了Linux设备驱动中的并发控制问题及其解决方法。通过使用合适的互斥机制,我们可以避免竞态的发生,提高设备驱动的稳定性和性能。在实际开发中,我们需要根据不同的场景选择最优的方案,并注意避免死锁、优先级反转等潜在的问题。

    以上是Linux设备驱动中如何解决并发控制问题?的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Android TV Box 获得非官方 Ubuntu 24.04 升级 Android TV Box 获得非官方 Ubuntu 24.04 升级 Sep 05, 2024 am 06:33 AM

对于许多用户来说,破解 Android 电视盒听起来令人畏惧。然而,在 Broadcom 芯片短缺期间,开发人员 Murray R. Van Luyn 面临着寻找 Raspberry Pi 合适替代品的挑战。他与 Armbia 的合作努力

deepseek网页版入口 deepseek官网入口 deepseek网页版入口 deepseek官网入口 Feb 19, 2025 pm 04:54 PM

DeepSeek 是一款强大的智能搜索与分析工具,提供网页版和官网两种访问方式。网页版便捷高效,免安装即可使用;官网则提供全面产品信息、下载资源和支持服务。无论个人还是企业用户,都可以通过 DeepSeek 轻松获取和分析海量数据,提升工作效率、辅助决策和促进创新。

deepseek怎么安装 deepseek怎么安装 Feb 19, 2025 pm 05:48 PM

DeepSeek的安装方法有多种,包括:从源码编译(适用于经验丰富的开发者)使用预编译包(适用于Windows用户)使用Docker容器(最便捷,无需担心兼容性)无论选择哪种方法,请仔细阅读官方文档并充分准备,避免不必要的麻烦。

BitPie比特派钱包app下载地址 BitPie比特派钱包app下载地址 Sep 10, 2024 pm 12:10 PM

如何下载BitPie比特派钱包App?步骤如下:在AppStore(苹果设备)或GooglePlay商店(安卓设备)中搜索“BitPie比特派钱包”。点击“获取”或“安装”按钮下载应用程序。对于电脑版,访问BitPie比特派钱包官方网站并下载相应软件包。

BITGet官方网站安装(2025新手指南) BITGet官方网站安装(2025新手指南) Feb 21, 2025 pm 08:42 PM

BITGet 是一款加密货币交易所,提供各种交易服务,包括现货交易、合约交易和衍生品。该交易所成立于 2018 年,总部位于新加坡,致力于为用户提供安全可靠的交易平台。BITGet 提供多种交易对,包括 BTC/USDT、ETH/USDT 和 XRP/USDT。此外,该交易所还在安全性和流动性方面享有盛誉,并提供多种功能,如高级订单类型、杠杆交易和 24/7 全天候客户支持。

详解:Shell脚本变量判断参数命令 详解:Shell脚本变量判断参数命令 Sep 02, 2024 pm 03:25 PM

系统变量$n传递给脚本或函数的参数。n是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2$?上个命令的退出状态,或函数的返回值。成功返回0,失败返回1$#传递给脚本或函数的参数个数$*所有这些参数都被双引号引住。若一个脚本接收两个参数,$*等于$1$2$0正在被执行命令的名字。对于shell脚本而言,这是被激活命令的路径$@被双引号(”“)包含时,与$*稍有不同。若一个脚本接收到两个参数,$@等价于$1$2$$当前shell的进程号。对于shell脚本,这是其正在执行时的进程I

Zabbix 3.4 源码编译安装 Zabbix 3.4 源码编译安装 Sep 04, 2024 am 07:32 AM

1.安装环境(Hyper-V虚拟机):$hostnamectlStatichostname:localhost.localdomainIconname:computer-vmChassis:vmMachineID:renwoles1d8743989a40cb81db696400BootID:renwoles272f4aa59935dcdd0d456501Virtualization:microsoftOperatingSystem:CentOSLinux7(Core)CPEOSName:cpe:

欧易okx安装包直接进 欧易okx安装包直接进 Feb 21, 2025 pm 08:00 PM

欧易 OKX,全球领先的数字资产交易所,现推出官方安装包,提供安全便捷的交易体验。欧易 OKX 安装包无需通过浏览器访问,可直接在设备上安装独立应用程序,为用户打造稳定高效的交易平台。安装过程简便易懂,用户只需下载最新版本安装包,按照提示一步步操作即可完成安装。

See all articles