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)互斥锁的过程,会带来进程的睡眠 ,因此,互斥锁是绝对不能出现在中断的上下文;自旋锁则不然,非常适合与中断一起配合使用;自旋锁保护的临界区,绝对不能出现进程的阻塞以及睡眠。
8、Linux设备驱动的并发控制的更多相关文章
- linux 设备驱动概述
linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和 ...
- linux设备驱动概述,王明学learn
linux设备驱动学习-1 本章节主要学习有操作系统的设备驱动和无操作系统设备驱动的区别,以及对操作系统和设备驱动关系的认识. 一.设备驱动的作用 对设备驱动最通俗的解释就是“驱使硬件设备行动” .设 ...
- 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道
http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...
- 《Linux设备驱动开发具体解释(第3版)》进展同步更新
本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTE ...
- 浅谈Android系统移植、Linux设备驱动
一.Android系统架构 第一层:Linux内核 包括驱动程序,管理内存.进程.电源等资源的程序 第二层:C/C++代码库 包括Linux的.so文件以及嵌入到APK程序中的NDK代码 第三层:An ...
- Linux设备驱动工程师之路——内核链表的使用【转】
本文转载自:http://blog.csdn.net/forever_key/article/details/6798685 Linux设备驱动工程师之路——内核链表的使用 K-Style 转载请注明 ...
- linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-119723.html linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟 xxxxxxxxxx ...
- linux设备驱动归纳总结(十二):简单的数码相框【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...
- linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...
随机推荐
- VMware vCenter Server安装与配置
预先准备好安装包 ESXI6 VMware-VMvisor-Installer-6.0.0.update01-3073146.x86_64.iso VC VMware-VIMSet ...
- 文件的上传(TCP)
问题描述:将本地文件上传(需将文件名一起上传)至指定服务器,服务器将上传的文件保存至指定路径下并文件名添加前缀 "Downlod_原文件名". 思路: 客户端需要一个输入流来读取本 ...
- java 访问 kerberos 认证的 kafka
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...
- php获取rl完整地址
/** * 获取url完整地址 * @author 梁景 * @date 2017-04-27 * @return */ function getUrlInfor() { $sys_protocal ...
- (4)java基础知识
一.注释 java的注释方法主要有三种 1.单行注释 // 2.多行注释 /* 内容 */ 3.文档注释 /** * * */ 这种方法注释用于生成一份API文档,主要说明 方法的功能.参数.返回值 ...
- Qimage与IplImage的转换
QImage test2012::ImageCV2Qimg(IplImage* img){ assert(img!=NULL); int h = img->height; int w = img ...
- HDU 2537 8球胜负(模拟)
/*这是一个模拟题,模拟一种台球的进球过程,并且判定胜负. 对于输入的字符串,如果出现R则红方记1分,如果出现Y则黄方记1分. 最后根据哪一方打进黑球和得分情况判定胜负. 程序说明: 这里给出两个C语 ...
- 简单DP【p3399】丝绸之路
Background 张骞于公元前138年曾历尽艰险出使过西域.加强了汉朝与西域各国的友好往来.从那以后,一队队骆驼商队在这漫长的商贸大道上行进,他们越过崇山峻岭,将中国的先进技术带向中亚.西亚和欧洲 ...
- ubuntu 下终端关于调试C++的命令
先确定安装了vim 和gcc (c语言)或者g++(c++) 如果没有安装可以在终端输入以下命令: sudo apt-get install build-essential sudo apt-get ...
- 【三维偏序】【分块】bzoj3262 陌上花开
裸的三维偏序. 对x坐标排序,y.z坐标分块.复杂度O(n*sqrt(n*log(n))).代码很短. #include<cstdio> #include<cmath> #in ...