Linux驱动编程 step-by-step (七)
2011-11-23 13:43
633 查看
分类:
C/C++ Linux
2011-11-17 00:24
2589人阅读 评论(4)
收藏
举报
前面所述的字符驱动都是没有考虑并发竟态的情况,想象一下
一个进程去读一个字符设备,另一个进程在同一时间向这个设备写入(完全有这种情况)
原来设备中存有 A B C D 要想设备写入1 2 3 4 每次读写一个字节
R: read
W:write
所以最后读出了A23D不是原来的ABCD
而如果两个进程同时写入一个设备则写入的值可能既不是A进程想要的又不是B进程想要的。
竞态 是对共享资源访问的结果, 并发进程访问了相同的数据或结构(硬件资源)等。
解决竞态的思想 主要有两种
1、所有的代码都不使用全局变量
2、使用锁机制确保一次只有一个进程使用共享资源
显然第一种方式是不可能的,我们只能尽量少的使用全局变量,而不能完全避免它
下边介绍 两种锁机制
希望进入临界区的进程调用P操作,检测当前信号量,如果信号量大于0,则信号量减1,进入临界区,否则进程等待其他进程释放此信号量,信号量的释放通过一个V操作增加信号量的值,在某些情况下会去唤醒等待此信号量的进程。
以上说到了一个 临界区这个名词 简单来讲就是内部操作了共享数据的代码
Linux 内核中信号量的实现
因为从LLD3(2.6.10)版本出来到现在(3.1)内核函数有了很多的变化,许多书上介绍的函数都已经不存在了,也能几版新kernel之后现在我说的函数也会有变化了。
信号量结构以及相关函数的定义在<linux/semaphore.h>中
to clipboardprint?
void sema_init(struct semaphore *sem, int val)
view plaincopy
to clipboardprint?
struct simple_dev{
char *data;
loff_t count;
struct cdev cdev;
struct semaphore semp;
};
static __init int simple_init(void)
{
...
for( index = 0 ; index < DEV_COUNT ; ++index )
{
sema_init(&char5_dev[index].semp,1);
//init_MUTEX(&(char5_dev[index].semp));
init_waitqueue_head(&(char5_dev[index].queue));
}
...
}
第二个函数 在第一个操作的基础上,如果进程因为没有得到信号量睡眠,在别的进程释放信号量或者发成中断的情况下都会被唤醒,在被中断信号唤醒时候返回-EINTR,成功返回0
第三个函数 如果没有另外的任务会获取此信号量,则可以调用此函数,在收到中断信号时会返回-EINTR
第四个函数 试图去获取信号量,在获取不到的时候不会睡眠,而是继续运行,返回0值表示得到了此信号量, 返回1 表示没能获取到。
第五个函数 可以去设置最长睡眠时间, 但是 此函数不可中断
to clipboardprint?
void up(struct semaphore *sem)
自旋锁
自旋锁是另一种锁机制,信号量会引起进程的休眠,而在不能睡眠的代码中我们就需要使用自旋锁。
自旋锁也是一个互斥的概念,有“锁定”与“解锁”两个操作,当程序需要锁定时候,则先检测锁是否可用,如果可用则获得锁,程序进入临界区,否则进入忙等待重复检测这个锁是否可用(这就是自旋),临界区代码操作完成则解锁。
信号量实现中其实也用到了自旋锁机制(有兴趣的刻一看内核源码,这边不展开,等以后写内核时候再介绍)
因为自旋锁在得不到锁的时候会“自旋” 即不会让出CPU ,所以我们的临界区执行速度应该尽量的快,最好使用原子操作(不会睡眠)。这也是使用自旋锁的核心规则,在多数情况下我们做不到这一点,所以自旋锁在驱动程序中使用的不如信号量频繁。
to clipboardprint?
spin_lock_init(_lock)
第二个函数 会尝试加锁,检测返回之判断是否锁定,在不能锁定时候程序也继续运行
第三个函数 禁止中断,不保存保存原先的中断状态
第四个函数 在禁止中断之前,保存原先的中断状态,
第五个函数 表示只禁止软件中断而保持硬件中断的打开
因为自旋锁本质上要不会被中断,所以调用时候建议使用包含有禁止中断的函数
to clipboardprint?
static inline void spin_unlock(spinlock_t *lock)
//static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)
static inline void spin_unlock(spinlock_t *lock)
//static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)
对应于锁定的五个函数,分别解锁,(对应于锁定的第二个操作,在加锁成功时候需要使用spin_unlock来解锁)
![](http://hi.csdn.net/attachment/201111/16/0_1321451769GUq8.gif)
如上图所示,A进程占有锁1,并在持有锁1的时候需要得到锁2程序才能继续进行
B进程占有锁2, 并在保持锁2的同时去获取锁1程序才能继续运行,这样A, B 进程就卡在这里,互不相让,这就导致了死锁。
亦或 在一个进程内同时试图获取同样的锁
![](http://hi.csdn.net/attachment/201111/16/0_1321452334Py0a.gif)
在必须使用多个锁的时候,应该始终以相同的顺序获取,也最好以获取锁顺序逆序解锁。
![](http://hi.csdn.net/attachment/201111/16/0_1321458865s7Lc.gif)
2、使用原子变量
原子变量不会被多个进程并发操作,内核提供了一种原子类型(atomic_t <asm/stomic.h>中)
3、设置加锁timeout值
在一定时间内不能获得锁,就放弃,并释放已占有的锁
C/C++ Linux
2011-11-17 00:24
2589人阅读 评论(4)
收藏
举报
并发 竞态 (信号量与自旋锁)
代码传至并发竞态控制
并发进程 导致竞态的一个例子
前面所述的字符驱动都是没有考虑并发竟态的情况,想象一下一个进程去读一个字符设备,另一个进程在同一时间向这个设备写入(完全有这种情况)
原来设备中存有 A B C D 要想设备写入1 2 3 4 每次读写一个字节
t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 |
R | W | W | R | W | R | R | W |
A | 1 | 2 | 2 | 3 | 3 | D | 4 |
W:write
所以最后读出了A23D不是原来的ABCD
而如果两个进程同时写入一个设备则写入的值可能既不是A进程想要的又不是B进程想要的。
并发与竞态
并发是指 多个进程同时访问相同一段代码(不仅限于内核空间的代码)竞态 是对共享资源访问的结果, 并发进程访问了相同的数据或结构(硬件资源)等。
解决竞态的思想 主要有两种
1、所有的代码都不使用全局变量
2、使用锁机制确保一次只有一个进程使用共享资源
显然第一种方式是不可能的,我们只能尽量少的使用全局变量,而不能完全避免它
下边介绍 两种锁机制
信号量
这个概念对我们来说应该不是很陌生,至少听说过,它有两种操作:通常叫做P操作与V操作希望进入临界区的进程调用P操作,检测当前信号量,如果信号量大于0,则信号量减1,进入临界区,否则进程等待其他进程释放此信号量,信号量的释放通过一个V操作增加信号量的值,在某些情况下会去唤醒等待此信号量的进程。
以上说到了一个 临界区这个名词 简单来讲就是内部操作了共享数据的代码
Linux 内核中信号量的实现
因为从LLD3(2.6.10)版本出来到现在(3.1)内核函数有了很多的变化,许多书上介绍的函数都已经不存在了,也能几版新kernel之后现在我说的函数也会有变化了。
信号量结构以及相关函数的定义在<linux/semaphore.h>中
创建信号量
view plaincopyto clipboardprint?
void sema_init(struct semaphore *sem, int val)
view plaincopy to clipboardprint? void init_MUTEX(); void init_MUTEX_LOCKED(); void init_MUTEX(); void init_MUTEX_LOCKED();两个定义互斥锁的函数 (最新的内核中已经没有这两个函数了,所以不要用它)
view plaincopy
to clipboardprint?
struct simple_dev{
char *data;
loff_t count;
struct cdev cdev;
struct semaphore semp;
};
static __init int simple_init(void)
{
...
for( index = 0 ; index < DEV_COUNT ; ++index )
{
sema_init(&char5_dev[index].semp,1);
//init_MUTEX(&(char5_dev[index].semp));
init_waitqueue_head(&(char5_dev[index].queue));
}
...
}
view plaincopy to clipboardprint? void down(struct semaphore *sem); int __must_check down_interruptible(struct semaphore *sem); int __must_check down_killable(struct semaphore *sem); int __must_check down_trylock(struct semaphore *sem); int __must_check down_timeout(struct semaphore *sem, long jiffies) void down(struct semaphore *sem); int __must_check down_interruptible(struct semaphore *sem); int __must_check down_killable(struct semaphore *sem); int __must_check down_trylock(struct semaphore *sem); int __must_check down_timeout(struct semaphore *sem, long jiffies)第一个函数在信号量大于0时直接将信号量的值减一程序继续运行,在信号量为0时进程睡眠只有等到有其他进程释放信号量时候才被唤醒(不推荐使用)
第二个函数 在第一个操作的基础上,如果进程因为没有得到信号量睡眠,在别的进程释放信号量或者发成中断的情况下都会被唤醒,在被中断信号唤醒时候返回-EINTR,成功返回0
第三个函数 如果没有另外的任务会获取此信号量,则可以调用此函数,在收到中断信号时会返回-EINTR
第四个函数 试图去获取信号量,在获取不到的时候不会睡眠,而是继续运行,返回0值表示得到了此信号量, 返回1 表示没能获取到。
第五个函数 可以去设置最长睡眠时间, 但是 此函数不可中断
V操作
view plaincopyto clipboardprint?
void up(struct semaphore *sem)
view plaincopy to clipboardprint? static ssize_t simple_read(struct file *filp, char __user *userstr, size_t count, loff_t *loff) { struct simple_dev *dev = NULL; int data_remain = 0; int err; D("[%s:] In %s \n",current->comm, __func__); dev = filp->private_data; err = down_interruptible(&dev->semp); if(err) { if(err == -EINTR) WAR("return by an interrupt signal\n"); else printk(KERN_ERR "an error occured in down_interruptible\n"); return err; } else { D("have get the mutex %d\n", __LINE__); } /******************************************* * 临界区代码 ********************************************/ up(&dev->semp); return count; } static ssize_t simple_read(struct file *filp, char __user *userstr, size_t count, loff_t *loff) { struct simple_dev *dev = NULL; int data_remain = 0; int err; D("[%s:] In %s \n",current->comm, __func__); dev = filp->private_data; err = down_interruptible(&dev->semp); if(err) { if(err == -EINTR) WAR("return by an interrupt signal\n"); else printk(KERN_ERR "an error occured in down_interruptible\n"); return err; } else { D("have get the mutex %d\n", __LINE__); } /******************************************* * 临界区代码 ********************************************/ up(&dev->semp); return count; }
自旋锁
自旋锁是另一种锁机制,信号量会引起进程的休眠,而在不能睡眠的代码中我们就需要使用自旋锁。
自旋锁也是一个互斥的概念,有“锁定”与“解锁”两个操作,当程序需要锁定时候,则先检测锁是否可用,如果可用则获得锁,程序进入临界区,否则进入忙等待重复检测这个锁是否可用(这就是自旋),临界区代码操作完成则解锁。
信号量实现中其实也用到了自旋锁机制(有兴趣的刻一看内核源码,这边不展开,等以后写内核时候再介绍)
因为自旋锁在得不到锁的时候会“自旋” 即不会让出CPU ,所以我们的临界区执行速度应该尽量的快,最好使用原子操作(不会睡眠)。这也是使用自旋锁的核心规则,在多数情况下我们做不到这一点,所以自旋锁在驱动程序中使用的不如信号量频繁。
初始化
view plaincopyto clipboardprint?
spin_lock_init(_lock)
view plaincopy to clipboardprint? static inline void spin_lock(spinlock_t *lock) static inline int spin_trylock(spinlock_t *lock) static inline void spin_lock_irq(spinlock_t *lock) spin_lock_irqsave(lock, flags) static inline void spin_lock_bh(spinlock_t *lock) static inline void spin_lock(spinlock_t *lock) static inline int spin_trylock(spinlock_t *lock) static inline void spin_lock_irq(spinlock_t *lock) spin_lock_irqsave(lock, flags) static inline void spin_lock_bh(spinlock_t *lock)第一个函数是 不去禁止中断 直接锁定
第二个函数 会尝试加锁,检测返回之判断是否锁定,在不能锁定时候程序也继续运行
第三个函数 禁止中断,不保存保存原先的中断状态
第四个函数 在禁止中断之前,保存原先的中断状态,
第五个函数 表示只禁止软件中断而保持硬件中断的打开
因为自旋锁本质上要不会被中断,所以调用时候建议使用包含有禁止中断的函数
解锁
view plaincopyto clipboardprint?
static inline void spin_unlock(spinlock_t *lock)
//static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)
static inline void spin_unlock(spinlock_t *lock)
//static inline void spin_unlock(spinlock_t *lock)
static inline void spin_unlock_irq(spinlock_t *lock)
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
static inline void spin_unlock_bh(spinlock_t *lock)
对应于锁定的五个函数,分别解锁,(对应于锁定的第二个操作,在加锁成功时候需要使用spin_unlock来解锁)
死锁
上边只提到了使用锁的好处,以及如何使用锁,但是引入锁机制也会带来风险,那就是死锁,进程死锁最明显的表现就是死机。![](http://hi.csdn.net/attachment/201111/16/0_1321451769GUq8.gif)
如上图所示,A进程占有锁1,并在持有锁1的时候需要得到锁2程序才能继续进行
B进程占有锁2, 并在保持锁2的同时去获取锁1程序才能继续运行,这样A, B 进程就卡在这里,互不相让,这就导致了死锁。
亦或 在一个进程内同时试图获取同样的锁
![](http://hi.csdn.net/attachment/201111/16/0_1321452334Py0a.gif)
死锁的名词解释
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁解决死锁的方法:
1、加解锁顺序一致在必须使用多个锁的时候,应该始终以相同的顺序获取,也最好以获取锁顺序逆序解锁。
![](http://hi.csdn.net/attachment/201111/16/0_1321458865s7Lc.gif)
2、使用原子变量
原子变量不会被多个进程并发操作,内核提供了一种原子类型(atomic_t <asm/stomic.h>中)
3、设置加锁timeout值
在一定时间内不能获得锁,就放弃,并释放已占有的锁
相关文章推荐
- Linux驱动编程 step-by-step (二)
- Linux驱动编程 step-by-step (二) 简单字符设备驱动
- Linux驱动编程 step-by-step (八)
- Linux驱动编程 step-by-step (三) .
- Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)
- Linux驱动编程 step-by-step (一)驱动程序的作用
- Linux驱动编程 step-by-step
- Linux驱动编程 step-by-step (六)
- Linux驱动编程 step-by-step (八) 阻塞型字符设备驱动
- Linux驱动编程 step-by-step (八) 阻塞型字符设备驱动
- Linux驱动编程 step-by-step (二)
- Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序
- Linux驱动编程 step-by-step (一)
- Linux驱动编程 step-by-step (四)
- Linux驱动编程 step-by-step (十) Linux 内核链表
- Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序
- Linux驱动编程 step-by-step (七)
- Linux驱动编程 step-by-step (五)
- Linux驱动编程 step-by-step (十) Linux 内核链表
- Linux驱动编程 step-by-step (十一)