Linux驱动_并发与竞争

目录

前言

一、原子操作

1、原子操作函数

原子操作API函数:     ​

原子位操作 API 函数 :

二、自旋锁

自旋锁API函数:

自旋锁中断API函数: ​

下半部竞争处理函数:

三、信号量 

信号量的 API 函数:

四、互斥体

互斥体API函数:


前言

        Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,
多个任务甚至中断都能访问的资源叫做共享资源。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。

        Linux产生并发的原因主要由一下几种:

        ① 多线程并发访问, Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。

        ② 抢占式并发访问,从 2.6 版本内核开始, Linux 内核支持抢占,也就是说调度程序可以
在任意时刻抢占正在运行的线程,从而运行其他的线程。

        ③ 中断程序并发访问,这个无需多说,学过 STM32 的同学应该知道,硬件中断的权利可
是很大的。

        ④ SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并
发访问
        防止并发访问共享资源,换句话说就是要保护共享资源,防止进行并发访问。Linux提供了以下几种方式并发和竞争的处理方法。


一、原子操作

        原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

        ○ 原子操作就是指不能再进一步分割的操作。

        ○ 原子操作一旦开始,就一直运行到结束。

        ○ 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。

        1、原子操作函数

                Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作。

typedef struct {
    int counter;
} atomic_

           如果要使用原子操作 API 函数,首先要先定义一个 atomic_t 的变量,如下所示:

atomic_t a;

        原子操作API函数:
     

        原子位操作 API 函数 :

二、自旋锁

        ○ 当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。
        ○ 中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(即本 CPU 中断,对于多核 SOC 来说会有多个 CPU 核),否则可能导致锁死现象的发生。
        缺点:等待自旋锁的线程会一直处于自旋状态,浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。
        ○ 自旋锁适用于短时期的轻量级加锁,如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。
自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
        ○ 不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。

         自旋锁API函数:

        上图自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。

        自旋锁中断API函数:
 

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中spin_lock/spin_unlock,

        下半部竞争处理函数:

三、信号量 

特点:

  • 信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
  • 信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
  • 共享资源的持有时间比较短时不适合使用信号量,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的优势。

        Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下所示:

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

        要想使用信号量就得先定义,然后初始化信号量。

信号量的 API 函数:

        信号量的使用如下所示:

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

四、互斥体

  • mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
  • 和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。
  • 因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。


    Linux 内核使用 mutex 结构体表示互斥体,定义如下:
    struct mutex {
        /* 1: unlocked, 0: locked, negative: locked, possible waiters */
        atomic_t count;
        spinlock_t wait_lock;
    };

    互斥体API函数:

  •  

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux设备驱动中,由于多个进程或线程可能会同时访问设备,因此需要进行并发控制以确保设备的正确性和稳定性。以下是一些常用的Linux设备驱动中的并发控制方法: 1. 互斥锁(mutex):互斥锁是用于保护临界区的一种机制,当一个进程或线程进入临界区时,其他进程或线程必须等待其退出后才能进入。Linux内核提供了多种不同类型的互斥锁,如spinlock、semaphore等,开发者可以根据实际需求选择不同的锁类型。 2. 读写锁(rwlock):读写锁是一种特殊的互斥锁,它允许同时有多个读者访问共享资源,但只允许一个写者访问。读写锁可以提高并发性能,但也需要考虑读写锁的开销。 3. 自旋锁(spinlock):自旋锁是一种忙等待的锁,当一个进程或线程无法获取锁时,它会一直循环尝试获取锁,直到获取成功。自旋锁对于短时间的临界区保护非常有效,但长时间的自旋会浪费CPU资源。 4. 原子操作(atomic):原子操作是一种不可分割的操作,可以保证操作的完整性和一致性。在Linux设备驱动中,原子操作通常用于对共享变量的操作,如增减计数器等。 除了以上方法,还有一些高级的并发控制技术,如RCU、信号量(semaphore)等,它们可以根据具体的应用场景来选择使用。在开发Linux设备驱动时,需要根据实际情况选择合适的并发控制方法,并注意避免死锁和竞争条件等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值