Linux设备驱动中的并发控制

简介:

并发与竞态

解决竞态问题的途径是保证对共享资源的互斥访问

访问共享资源的代码区域称为临界区,临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是Linux设备驱动中可采用的互斥途径。

中断屏蔽

中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。但是,需要注意是的是长时间的中断是危险的,有可能导致数据丢失或着系统崩溃。

local_irq_disable()和local_irq_enable()都只能禁止和使能本CPU内的中断,不能解决SMP多CPU引发的竞态。

local_irq_save(flags)除了进行禁止中断操作以外,还保存目前CPU的中断位信息,local_irq_restore(flags)进行相反的操作。

如果只想禁止中断的底半部,应使用local_bh_disable(),使能底半部使用local_bh_enable()。

原子操作

原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

Linux内核提供了一系列函数来实现内核中的原子操作,这些函数又分为两类,分别针对位和整型变量进行原子操作。

 

整型原子操作

 

1. 设置值 void atomic_set(atomic_t *v,int i); Atomic_t v = ATOMIC_INT(0);

2. 获取值 atomic_read(atomic_t *v);

3. 加减 void atomic_add(int i,atomic_t *v); void atomic_sub(int i,atomic_t *v);

4. 自增自减 void atomic_inc(atomic_t *v); void atomic_dec(atomic_t *v);

5. 操作测试 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);

操作后测试其值是否为0,为0返回true,否则返回false。

6. 操作返回int atomic_inc_and_return(atomic_t *v); int atomic_dec_and_return(atomic_t *v); int atomic_sub_and_return(int i, atomic_t *v); int atomic_add_and_return(int i, atomic_t *v);

操作后返回新值。

 

位原子操作

 

1. 设置位 void set_bit(nr,void *addr); 设置addr地址的第nr位,即将位写1。

2. 清除位 void clear_bit(nr,void *addr); 将位写为0。

3. 改变位 void change_bit(nr,void *addr); 将位进行反置。

4. 测试位test_bit(nr,void *addr); 返回第nr位。

5. 测试操作int test_and_set_bit(nr,void *addr); int test_and_clear_bit(nr,void *addr); int test_and_change_bit(nr,void *addr);

先返回,后操作。

 

例子:使用原子变量使设备只能被一个进程打开

 

static atomic_t xxx_available =ATOMIC_INIT(1);

 

xxx_open

{

if(!atomic_dec_and_test(&xxx_available))

{

atomic_inc(&xxx_available);

return - EBUSY;

}

}

 

xxx_release

{

atomic_inc(&xxx_available);

}

 

自旋锁

自旋锁的使用

自旋锁是一种对临界资源进行互斥访问的典型手段。

 

操作:1. 定义 spinlock_t lock;

2. 初始化 spin_lock_init(&lock);

3 获得 spin_lock(&lock); 自旋等待 spin_trylock(&lock); 非阻塞,立即返回。

4. 释放 spin_unlock(&lock);

 

注意:自旋锁实际是忙等锁;自旋锁可能导致系统死锁。

 

读写自旋锁

 

读写自旋锁可允许读的并发。

 

操作:1. 定义初始化 rwlock_t my_rwlock = RW_LOCK_UNLOCKED;

rwlock_t my_rwlock;

rwlock_init(&my_rwlock);

2. 读锁定 void read_lock(rw_lock_t *lock);

3. 读解锁 void read_unlock(rw_lock_t *lock);

4. 写锁定 void write_lock(rw_lock_t *lock);

void write_trylock(rw_lock_t *lock);

5. 写解锁 void write_unlock(rw_lock_t *lock);

 

 

顺序锁

顺序锁中读或写单元都不会被对方阻塞,但是写写仍然互斥。

顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写单元可能使得指针失效。

操作:1. 获得锁void write_seqlock(seqlock_t *sl);

void write_tryseqlock(seqlock_t *sl);

2. 释放锁void write_sequnlock(seqlock_t *sl);

读完后需要进行检查在读期间是否有写操作,如果有则需要重新进行读操作。

 

读-拷贝-更新

对于被RCU保护的共享数据结构,读执行单元不需要获得任何锁就可以访问它,不需要锁也使得使用更容易,因为死锁问题就不需要考虑。

使用RCU写执行单元在访问它前需首先复制一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的CPU都退出对共享数据的操作的时候。

 

操作:1. 读锁定 rcu_read_lock() rcu_read_lock_bh()

2. 读解锁 rcu_read_unlock() rcu_read_unlock_bh()

3. 同步RCU synchronize_rcu()

4 挂接回调 void fastcall call_rcu(struct rcu_head *head, void (*func)(struct_rcu_head *rcu) );

RCU还增加了链表操作函数的RCU版本。

2.6中 RCU得到普遍使用。

信号量

信号量的使用

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

 

操作:

 

1. 定义 struct semaphore sem;

2. 初始化 void sema_init(struct semaphore *sem, int val);

void init_MUTEX(struct semaphore *sem); //信号量sem的值设置为1

void init_MUTEX_LOCKED(struct semaphore *sem); //信号量sem的值设置为0

DECLARE_MUTEX(name) //name的信号量初始化为1

DECLARE_MUTEX_LOCKED(name) //name的信号量初始化为0

3.获取 void down(struct semaphore *sem); //休眠

int down_interruptible(struct semaphore *sem);

Int down_trylock(struct semaphore *sem); //不会休眠 可在中断上下文使用

4. 释放 void up(struct semaphore *sem);

 

信号量用于同步

如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行单元的继续执行需等待另一执行单元完成某事,保证执行的先后顺序。

 

完成量用于同步

 

Linux提供了一种比信号量更好的同步机制,即完成量(completion)。

 

操作:1. 定义 struct completion my_completion;

2. 初始化 init_completion(&my_completion);

DECLARE_COMPLETION(my_completion);

3. 等待 void wait_for_completion(struct completion *c);

4. 唤醒void complete(struct completion *c);

void complete_all(struct completion *c);

 

自旋锁vs信号量

 

严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。

 

信号量是进程级的,用于多个进程之间对资源的互斥,所以适用于进程占用资源时间比较长的时候,而自旋锁不能在临界区长时间停留。

信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程间的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。

如果被保护的共享资源需要在中断或软中断情况下使用,则只能选择自旋锁。如果一定要使用信号量,则只能通过down_trylock()进行,以避免阻塞。

 

读写信号量

 

它可允许N个读执行单元同时访问共享资源,而最多只能有一个写执行单元。

 

操作:1. 定义初始化 struct rw_semaphore my_rws;

void init_rwsem(struct rw_semaphore *sem);

2. 读信号量获取 void down_read(struct rw_semaphore *sem);

int down_read_trylock(struct rw_semaphore *sem);

3. 读信号量释放void up_read(struct rw_semaphore *sem);

4. 写信号量获取 void down_write(struct rw_semaphore *sem);

int down_write_trylock(struct rw_semaphore *sem);

3. 写信号量释放void up_write(struct rw_semaphore *sem);

 

互斥体

操作:1. 定义初始化 struct mutex my_mutex;

Mutex_init (&my_mutex);

2. 获取 void fastcall mutex_lock(struct mutex *lock);

int fastcall mutex_lock_interruptible(struct mutex *lock);

int fastcall mutex_trylock (struct mutex *lock);

3.释放void fastcall mutex_unlock(struct mutex *lock);

 

 本文转自feisky博客园博客,原文链接:http://www.cnblogs.com/feisky/archive/2010/05/30/1747353.html,如需转载请自行联系原作者


相关文章
|
1月前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
67 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
2月前
|
数据采集 编解码 运维
一文讲完说懂 WowKey -- WowKey 是一款 Linux 类设备的命令行(CLT)运维工具
WowKey 是一款面向 Linux 类设备的命令行运维工具,支持自动登录、批量执行及标准化维护,适用于企业、团队或个人管理多台设备,显著提升运维效率与质量。
|
3月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
214 0
|
5月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
175 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
6月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
6月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
11月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
219 5
|
11月前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
249 6
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
214 3