原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2274684.html

现代操作系统有三大特性:中断处理、多任务处理和多处理器。这些特性导致当多个进程、线程或者CPU同时访问一个资源时,可能发生错误,这些错误是操作系统运行所不允许的。在操作系统中,内核需要提供并发控制机制,对共享资源进行保护。

  在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。并发容易导致竞争的问题。竞争就是两个或两个以上的进程同时访问一个资源,同时引起资源的错误。并发控制机制有以下几种:

1.原子变量操作:
  原子变量操作(分为原子整型操作和原子位操作)就是绝不会在执行完毕前被任何其他任务和时间打断,不会执行一半,又去执行其他代码。原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都在include/asm/atomic.h中,使用汇编语言实现。
  在linux中,原子变量的定义如下:

    typedef struct {
volatile int counter;
} atomic_t;

  关键字volatile用来暗示GCC不要对该类型做数据优化,所以对这个变量counte的访问都是基于内存的,不要将其缓冲到寄存器中。存储到寄存器中,可能导致内存中的数据已经改变,而寄存其中的数据没有改变。

(1)原子整型操作:
1)定义atomic_t变量:

#define ATOMIC_INIT(i) ( (atomic_t) { (i) } )
atomic_t v = ATOMIC_INIT(0);    //定义原子变量v并初始化为0

2)设置原子变量的值:

#define atomic_set(v,i) ((v)->counter = (i))
void atomic_set(atomic_t *v, int i);//设置原子变量的值为i

3)获取原子变量的值:

#define atomic_read(v) ((v)->counter + 0)
atomic_read(atomic_t *v);//返回原子变量的值

4)原子变量加/减:

static __inline__ void atomic_add(int i, atomic_t * v); //原子变量增加i 
static __inline__ void atomic_sub(int i, atomic_t * v); //原子变量减少i

5)原子变量自增/自减:

#define atomic_inc(v) atomic_add(1, v); //原子变量加1 
#define atomic_dec(v) atomic_sub(1, v); //原子变量减1

6)操作并测试:

//这些操作对原子变量执行自增,自减,减操作后测试是否为0,是返回true,否则返回false 
#define atomic_inc_and_test(v) (atomic_add_return(1, (v)) == 0)
static inline int atomic_add_return(int i, atomic_t *v)

(2)原子位操作(根据数据的每一位单独进行操作):

  原子操作的优点编写简单;缺点是功能太简单,只能做计数操作,保护的东西太少。

2.自旋锁
  自旋锁是专为防止多处理器并发而引入的一种锁,它应用于中断处理等部分。对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。
  自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
(1)自旋锁的使用:

spinlock_t spin; //定义自旋锁
spin_lock_init(lock); //初始化自旋锁
spin_lock(lock); //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放
spin_trylock(lock); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转"
spin_unlock(lock);//释放自旋锁

使用代码:

spinlock_t lock; 
spin_lock_init(&lock);
spin_lock (&lock);
....//临界资源区
spin_unlock(&lock);

(2)注意事项:
  1)自旋锁是一种忙等待。它是一种适合短时间锁定的轻量级的加锁机制。
  2)自旋锁不能递归使用。自旋锁被设计成在不同线程或者函数之间同步。这是因为,如果一个线程在已经持有自旋锁时,其处于忙等待状态,则已经没有机会释放自己持有的锁了。如果这时再调用自身,则自旋锁永远没有执行的机会了。

3.信号量
  linux中,提供了两种信号量:一种用于内核程序中,一种用于应用程序中。这里只讲属前者。
  信号量和自旋锁的使用方法基本一样。与自旋锁相比,信号量只有当得到信号量的进程或者线程时才能够进入临界区,执行临界代码。信号量和自旋锁的最大区别在于:当一个进程试图去获得一个已经锁定的信号量时,进程不会像自旋锁一样在远处忙等待。
  信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。

(1)信号量的实现:
  在linux中,信号量的定义如下:

struct semaphore {
spinlock_t lock; //用来对count变量起保护作用。
unsigned int count; // 大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
struct list_head wait_list; //存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
};

(2)信号量的使用:

1)定义信号量:

 struct semaphore sem;

2)初始化信号量 :

static inline void sema_init(struct semaphore *sem, int val); //设置sem为val

#define init_MUTEX(sem) sema_init(sem, 1) //初始化一个用户互斥的信号量sem设置为1
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0) //初始化一个用户互斥的信号量sem设置为0

定义和初始化可以一步完成:

DECLARE_MUTEX(name); //该宏定义信号量name并初始化1
DECLARE_MUTEX_LOCKED(name); //该宏定义信号量name并初始化0

  当信号量用于互斥时(即避免多个进程同是在一个临界区运行),信号量的值应初始化为1。这种信号量在任何给定时刻只能由单个进程或线程拥有。在这种使用模式下,一个信号量有事也称为一个“互斥体(mutex)”,它是互斥(mutual exclusion)的简称。Linux内核中几乎所有的信号量均用于互斥。

  使用信号量,内核代码必须包含<asm/semaphore.h> 。

3)获取(锁定)信号量:

void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_killable(struct semaphore *sem);


4)释放信号量
void up(struct semaphore *sem); //释放信号量sem,唤醒等待者

4.完成量
  它用于一个执行单元等待另一个执行单元执行完某事;

struct completion {
unsigned int done; //大于0,表示完成量的函数可以立即执行,不要要等待;等于0,将拥有完成量的线程置于等待状态。
wait_queue_head_t wait;
};

  使用方法:

1)定义完成量:

struct completion com;

2)初始化:

init_completion(&com); //要是觉得这两步麻烦,就再给你来个宏即定义又初始化DECLARE_COMPLETION(com);

3)等待完成量:

void __sched wait_for_completion(struct completion *x); //等待一个completion被唤醒

int __sched wait_for_completion_interruptible(struct completion *x);//可中断的wait_for_completion
unsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout);//带超时处理的wait_for_completion

4)唤醒完成量

void complete(struct completion *x); //只唤醒一个等待的进程或线程。
void complete_all(struct completion *x); //唤醒所有等待这个完成量的进程或者线程。

后记:除了上述几种广泛使用的的并发控制机制外,还有中断屏蔽顺序锁(seqlock)、RCU(Read-Copy-Update)等等,做个简单总结如下图:

【转】linux驱动程序中的并发控制的更多相关文章

  1. Linux驱动程序中的并发控制

    <临界区> a:对共享资源进行访问的代码称为临界区.   <原子操作>     a:原子操作用于执行轻量级,仅仅执行一次的的操作比如修改计数器,有条件的增加值,设置某一位.所谓 ...

  2. Linux设备中的并发控制

    一.自旋锁1.定义自旋锁:spinlock_t lock2.初始化自旋锁:spin_lock_init(lock)3.获得自旋锁:spin_lock(lock)4.释放自旋锁:spin_unlock( ...

  3. 蜕变成蝶~Linux设备驱动中的并发控制

    并发和竞争发生在两类体系中: 对称多处理器(SMP)的多个CPU 内核可抢占的单CPU系统 访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护.在 ...

  4. 第六章 第一个Linux驱动程序:统计单词个数

    现在进入了实战阶段,使用统计单词个数的实例让我们了解开发和测试Linux驱动程序的完整过程.第一个Linux驱动程序是统计单词个数. 这个Linux驱动程序没有访问硬件,而是利用设备文件作为介质与应用 ...

  5. Linux驱动设备中的并发控制

    一.基本概念 二.中断屏蔽 三.原子操作 四.自旋锁 五.信号量 六.互斥体 七.自旋锁与信号量的比较 Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态,即使 ...

  6. linux设备驱动中的并发控制【转】

    转自:http://www.cnblogs.com/plinx/archive/2013/01/28/2873121.html 在linux内核中,主要的静态发生于以下几种情况: 1.对称多处理器(S ...

  7. 【转】linux设备驱动程序中的阻塞机制

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...

  8. Linux设备驱动中的并发控制

    1.并发是指多个执行单元同时.并行的执行.并发的执行单元对共享资源的访问很容易导致竞态. 在 Linux 内核中,主要的竞态发生于如下几种情况: ①对称多处理器(SMP)的多个 CPU ②单CPU内进 ...

  9. linux驱动程序之电源管理之新版linux系统设备架构中关于电源管理方式的变更

    新版linux系统设备架构中关于电源管理方式的变更 based on linux-2.6.32 一.设备模型各数据结构中电源管理的部分 linux的设备模型通过诸多结构体来联合描述,如struct d ...

随机推荐

  1. hdu 3404 Switch lights 博弈论

    主要是求NIM积!!! 代码如下: #include<iostream> #include<cstdio> #include<stack> #include< ...

  2. Linux多线程之同步3

    需求 客户端将需要解决的task发送给服务器,服务器调用线程来解决客户端发送的task,解决完由线程负责将其发送回客户端.(用管道实现通信) 思路 1. server维护两个列表.一是客户端列表.二是 ...

  3. 解决安装rpm时lib冲突:libstdc++-2-libc6.1-1-2.9.0.so from install of compat-libstdc++-7.3-2.96.118 conflicts with file from ...

    sudo rpm -ivh xxx.rpm -aid --force [oracle@localhost Oracle]$ .i386.rpm compat-libstdc++-devel-.i386 ...

  4. 华为上机:IP地址转换

    IP地址转换 描述: IP地址的长度为32,即有2^32-1个地址.IP地址一般采用点分十进制表示法,例如"192.168.1.1".IP地址也可以直接用一个32位的整数进行表示. ...

  5. Linux服务器 scp 不需要密码配置与密钥转换(id_rsa->ppk)

    案例:▲服务器A对服务器B.C进行ssh连接,免输入密码    或▲服务器A向服务器B.C复制文件(源文件在服务器A上),免输入密码 主机A:192.168.0.221主机B:192.168.0.22 ...

  6. React状态的含义和用法

    一. 二.代码 <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset=&quo ...

  7. SQL Server下实现利用SQL Server Agent Job对索引重建实现Balance Load

    昨天工作中遇到这样一个场景,有个项目需要把某台服务器下所有的表和索引都启用数据压缩(data_compression=page),已经启用了的表和索引就不需要再压缩一次了.统计一下后发现要运行的REB ...

  8. PHP高级特性二之文件处理

    PHP中的文件处理也是一个相当重要的模块,这一篇的主要内容就是PHP中文件系统的简介. 文件系统用途 1. 项目处理都离不开文件处理 2. 可以用文件长时间保存数据 3. 建立缓存,在服务器中进行文件 ...

  9. Python 时间戳与时间字符串互相转

    #设a为字符串 import time a = "2011-09-28 10:00:00" #中间过程,一般都需要将字符串转化为时间数组 time.strptime(a,'%Y-% ...

  10. 二叉搜索树的两种实现(数组模拟,STL)

    书上实现: 二叉搜索数的特点:高效实现 插入一个数值,查询是否包含某个数值,删除某一个数值. 所有的节点都满足左子树上的所有节点都比自己的小,而右子树上的所有节点都比自己大的特点. 查询:如果当前数值 ...