8、Linux设备驱动的并发控制

一、并发与竞争

    并发是指多个 多个执行单元同时执行,而这对对共享的资源,比如硬件的资源、软件的全局变量、静态变量 的访问,很容易导致竞态,

1.1、中断屏蔽

    在单核的  CPU 里,避免竞态的一个简单有效的方法是,在进入临界区之前,就屏蔽系统的中断。也就是说,在进入临界区之前,中断被关闭,使得中断与进程之间的并发不会发生,而且,因为进程的调度器是依赖于中断来实现的,没有了中断,进程就不能被切换,保证了进程之间的并发不会发生。

方法:

local_irq_disable()

XXXXX 临界区的代码

local_irq_enable()

    虽然  local_irq_disable() 和 local_irq_enable() 可以关闭本 CPU 的中断,但是在 多核的  CPU里面,并不能解决问题,因为 它们只能禁止和使能本 CPU 的中断,并不能解决多 CPU 引发的竞争,所以在多个的里面,一般是将关闭中断的方法与 自旋锁 结合使用。

    如果指向禁止中断的底部,在应该使用 locak_bh_disable() ,使能的是, local_bh_enable();

1.2、原子操作

原子操作,可以保证对一个整型数据的修改的排他性,也就是说,原子操作是对一个数据进行操作,操作的过程,是不会被线程的调度机制给打断,也就是说,一旦开始,是绝对不会睡眠和阻塞的,直到操作结束。对于整形数的操作,分别是对整型数和位进行原子的操作。

1.2.1、整型原子的操作

(1)设置原子变量的值

void atomic_set(atomic_t *v,int i);     // 设置原子变量的值为 i

atomic_t v = ATOMIC_INIT(0)  ;    // 定义原子变量 V,并初始化为 0

(2)获取原子变量的值

atomic_read(atomic_t *v)    // 返回值就是原子变量的值

(3)原子的操作

void atomic_add(int i, atomic_t *v)    // i + v

void atomic_sub(int i,atomic_t *v ) // v – i

(4)操作并测试

int atomic_inc_an_test(atomic_t *v)   // 自加1,测试是否为0,使得话返回 true

int atomic_dec_and_test(atomic_t *v)  // 自减 1,测试是否为 0,使得话,返回 true

(5)操作与返回

int atomic_add_reture(int i, atomic_t *v);   // V + i,返回新的值

int  atomic _sub_return(int i, atomic_t *v)  // v – i ,返回新的值

int atomic_inc_return(atomic_t *v)   // V + 1

int atomic_dec_return(atomic_t *v) // V – 1

1.2.2、位原子的操作

(1)设置为

void set_bit(nr,void * addr)   // 设置地址 addr 的 第 nr 位 为 1

(2)清除位

void clear_bit(nr, void *addr)   // 设置地址 addr 的 第 nr 位 为 0

(3)改变位

void change_bit(nr, void *addr) // 设置地址 addr 的 第 nr 位 反转

(4)检测位

test_bit(nr, void *addr);   // 返回 addr 第  nr 位的  值

(5)测试与操作为

int test_and_set_bit(nr, void *addr);

    先进行测试,之后在进行位的设置。

1.2.3、原子操作的特性

    原子操作的特性,就对原子数的操作的时候,绝对不会被线程的调度器进行打断,也就说,这个时间段,是没进程的上下文之间的切换,进一步说,就是绝对不会出现睡眠和阻塞。

1.3、自旋锁

    自旋锁 spin  lock,是另外一种对临界资源进行互斥访问的手段,它的实现也是借助了 原子操作实现的。自旋锁的机制是,进程对资源的访问之前,需要对特性的内存进行读取,当读取到值,也就是获得到锁的时候,就有权限进入下一步的操作;而其他的进程没有获得到锁的时候,就就如原地的等待,然后再次进行读取,这个等待、读取的过程,我们称之为自旋。

1.3.1、自旋的 API

(1)锁的定义

spinlock_t lock;

(2)锁的初始化

spin_lock_init(lock)    // 完成初始化

(3)获得锁

spin_lock(lock)

    尝试去获得锁,如果获得,就马上返回,没有获得的话,就进入自旋的状态,Linux 也提供非阻塞的方式:

spin_trylock(lock)

    获得锁的时候,返回 true,没有获得的时候,就返回 false

(4)释放锁

spin_unlock(lock)

1.3.2、一般执行方式

spinlock_t lock;

spin_init_lock(lock);

spin_lock(lock);

XXXX 临界区代码

spin_unlock(lock);

1.3.3、自旋锁与中断

    在单 CPU 和内核可被抢占的系统中,自旋锁在获得锁的期间,内核的抢占就是被禁止的,因此,这个时候,是可以完全保证获得锁器件的临界区操作代码,不收到其他进程的打扰;但是在 多核的 CPU 中,其中的一个 CPU 获得了自旋锁,但是,只是在该 CPU 上的抢占调度被禁止了,其他核心的CPU 是并没有被禁止抢占调度的,所以为了保证在多核的情况,临界区不受到本 CPU 和其他的  CPU 抢占进程的打扰。

    因为自旋锁的底层是借助原子操作实现的,保证了获得锁的器件的操作是不会被被本 CPU 和其他 CPU 的进程调度所影响,而且,自旋锁关闭了抢占系统,但是这些特性并不能保证在得到锁的的时候,执行临界区代码的时候,不受到中断和底部(BH)的影响,也就是这个时候,本 CPU 的中断发生的时候,还是会反过来影响临界区的代码,所以一般是假如关闭中断的操作,这个时候就借助了锁的衍生机制

spin_lock_irq = spin_lock() + local_irq_disable()    // 获得锁的同时,关闭中断

spin_inlock_irq = spin_unlock() + local_irq_enable   // 释放锁的同时,打开中断

spin_lock_irqsave = spin_lock() + locak_irq_save()  // 获得锁的同时,关闭中断,且保存中断的状态

spin_lock_irqrestore() = spin_unlock() + locak_irq_restore()   // 释放锁的同时,将中断状态进行回复

spin_lock_bh() = spin_lock() + local_bh_disable() ;  // 获得锁的同时,关闭底部中断

spin_unlock_bh() = spin_unlock() + local_bh_enable  // 释放锁,且恢复底部中断

    当在自旋锁内部加了中断的时候,CPU0 获得了自旋锁,进入了原子的操而CPU 1 在 CPU0 还没有释放锁的时候,CPU1 就只等原地等待(浪费

了 CPU);而且,中断的话,就避免受到  本 CPU 的影响,保证了内核并发的可能。

1.3.4、自旋锁注意事项

(1)因为在获得了自旋锁之后,CPU 就开始了原子的操作,其他的CPU 就会原地一直自旋,等待的过程,并不是睡眠,所以浪费了 N 多的资源,所以,一般上,进程拥有锁的时间都是非常的短的,这样才是比较的划算;如果临界区的代码非常大的话,其他的 CPU 就一直原地等待,浪费了资源,导致了系统性能的降低。

(2)进程在拥有一个锁的期间,不能再尝试去获取本锁,否则导致死锁。

(3)在获得自旋锁的器件,绝对不能出现进程调度的情况,也就是,绝对不能出现进程的睡眠和进程的阻塞,不能出现 copy_tousr或者copy_from_usr,kmalloc、msleep等函数,因为自旋锁实现就是通过原子操作实现的,原子操作的特性,就是一旦开始执行,绝对不会出现进程的调度,否则会导致内核的崩溃。

1.4、信号量

    信号量,是非常常用的一种同步互斥的手段,类似于灯,当等的个数不为零的时候,进程获取资源的时候,就将灯拿走一个;当释放资源的时候,就将灯放回去;当灯为零的时候,这个时候,进程将被挂起,进入睡眠的状态,直到被唤醒。

1.4.1、API

(1)定义信号量

struct semaphore sem;

(2)初始化信号量

void sema_init(struct semaphore * sem, int val)

    初始化信号量的值为 val

(3)获得信号量

int down(struct semaphore  * sem)

    获得信号量,和获得锁差不多,当没有得到信号量的时候,就进入睡眠的状态,也就是说,不能在中断里面被使用,因为中断全部被关闭了,进程的调度器是依赖中断实现的,没有了中断,当睡眠的进程,拥有没有进程调度器去唤醒进程,导致程序用死停在那里。内核也提供了可以被打断的接口

int down_interruptible(struct semaphore  *sem)

    回去尝试去获取信号量,没有获得的时候,进入睡眠,但是可以被打断,,

int dowun_trylock(struct semaphore  *sem)

   即使在没有活动信号量的情况下,也不会导致睡眠,而是立即返回,所以可以在中断的上下文进行使用

(4)释放信号量

void up(struct semaphore *sem)

当信号量初始化为 1 的时候,实现的就是互斥锁的功能

 

1.5、互斥锁

   互斥锁的使用方法,和信号量的几乎一模一样 ,只是接口名字不一样而已。

(1)定义互斥锁

struct mutex mymutex;

(2)初始化互斥锁

mutex_init( struct mutex *mymutex)

(3)获得互斥锁

void mutex_lock( struct mutex mymutex;)

   当然也有,void mutex_lock_interruptible( struct mutex mymutex;) 和 void mutex_trylock( struct mutex mymutex;)

(4)释放互斥锁

void mutex_unlock(struct mutex mymutex)

1.5.1、自旋锁和互斥锁的选择

   (1) 自旋锁在没有获得锁的时候,就只能是原地的等地,因此开销就是其他进程获得锁执行的时间;而互斥锁的话,在没有获得锁,就会睡眠当前的进程,锁带来的开销,是进程切换带来的开销。

    (2)互斥锁的过程,会带来进程的睡眠 ,因此,互斥锁是绝对不能出现在中断的上下文;自旋锁则不然,非常适合与中断一起配合使用;自旋锁保护的临界区,绝对不能出现进程的阻塞以及睡眠。

时间: 2024-10-09 16:32:41

8、Linux设备驱动的并发控制的相关文章

Linux设备驱动之并发控制

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

11 Linux设备驱动的并发控制

一.并发和竟态 并发:多CPU或者单CPU多进程多线程同时执行 竟态:对共享资源的同时访问 以下4中情况出现并发: (1)多CPU (2)单CPU多进程多线程 (3)单CPU进程与中断 (4)单CPU中断与中断 二.进程调度 1. 进程调度是按照时间片来调度的,进程A的时间片用完了,就会调用进程B 2. 可抢占与不可抢占 抢占:打断当前进程(用户态或者内核态),执行其他进程. 用户态出现抢占的时刻: (1)系统调用返回到用户态 (2)中断来了,打断用户态进程,然后中断返回的时刻 内核态抢占: [

20150518 Linux设备驱动中的并发控制

20150518 Linux设备驱动中的并发控制 2015-05-18 Lover雪儿 总结一下并发控制的相关知识: 本文参考:华清远见<Linux 设备驱动开发详解>—第7章 Linux 设备驱动中的并发控制,更多详细内容请看原书 一.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量.静态变量等)的访问则很容易导致竞态(race conditions). 在 Linux 内核中,主要的竞态发生于如下几种情况:

Linux设备驱动中的阻塞与非阻塞IO与并发控制

Linux设备驱动中的阻塞与非阻塞IO: 1.Linux设备驱动中的阻塞与非阻塞总结:http://m.blog.csdn.net/blog/dongteen/17264501 2.Linux设备驱动中的阻塞与非阻塞IO:http://m.blog.csdn.net/blog/dongteen/17264501 3.Linux设备驱动中的阻塞与非阻塞I/O实例:http://blog.csdn.net/wenhui_/article/details/6817659 linux内核中等待队列: 1

linux设备驱动中的并发控制

并发指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源的访问则很容易导致竞态 linux内核中主要竞态1.多对称处理器的多个CPU  2.单CPU内进程与抢占它的进程 3.中断(硬中断.软中断.Tasklet.下半部)与进程之间访问共享内存资源的代码区称为“临界区”,临界区需要被以某种互斥机制加以保护,中断屏蔽.原子操作.自旋锁和信号量等是linux设备驱动中可采用的互斥途径. 这几个互斥的介绍: 1.中断屏蔽,这个主要用于单CPU,中断屏蔽将使得中断和进程之间的并发不再发生.使用方

《Linux设备驱动开发详解(第3版)》海量更新总结

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]添加关于QEMU模拟vexpress板的描述 第2章 <驱动设计的硬件基础> [N]增加关于SoC的介绍:[N]增加关于eFuse的内容:[D]删除ISA总线的内容了:[N]增加关于SP

Linux设备驱动开发基础

1.驱动概述和开发环境搭建 1.1驱动设备的作用 对设备驱动最通俗的解释就是"驱动硬件设备行动".驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮训.中断处理.DMA通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据. 由此可见,设备驱动充当了硬件和应用软件之间的纽带,他使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作.在系统中没有操作系统的情况下,工

《Linux设备驱动开发具体解释(第3版)》进展同步更新

本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTEX-A9平台. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]加入关于QEMU模拟vexpress板的描写叙述 第2章 <驱动设计的硬件基础> [

Hasen的linux设备驱动开发学习之旅--异步通知

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:异步通知 * Date:2014-11-05 */ 一.异步通知的概念和作用 阻塞和非阻塞访问.poll()函数提供了较好地解决设备访问的机制,但是如果有了异步通知整套机制就更 加完整了. 异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这 一点非常类似于硬件上"中断"的概