锁(一) 中断屏蔽 原子操作
2017-04-27 10:38
141 查看
中断屏蔽
单cpu,在单cpu范围内避免竞态的简单方法是在进入临界区之前屏蔽系统的中断。cpu一般都具备屏蔽中断和打开中断
的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序抢占,防止某些竞态条件的发生。由于linux内核
的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也得以避免。
中断屏蔽的使用方法为:
local_irq_disable() //屏蔽中断
...
critical section //临界区
...
local_irq_enable() //开中断
由于linux的异步I/O,进程调度等很多重要操作都依赖于中断,中断对于内核的运行非常重要,在屏蔽中断期间所有的中
断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失乃至系统崩溃等后果。在就要求在屏蔽中断
之后,当前的内核执行路径应当尽快地执行完临界区的代码。
local_irq_disable()和local_irq_enable()只能屏蔽使能本cpu(单个cpu)的中断,因此不适合多cpu的竞态处理。因此
单独使用中断屏蔽通常不是一种值得推荐的避免竞态的方法,它适合于自旋锁联合使用。
与local_irq_disable()不同的是,local_irq_save(flags)除了进行禁止中断的操作以外,还保存目前CPU的中断位信息。
如果只是想禁止使能中断的底半部,应使用local_bh_disable(),local_bh_enable()。
原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。linux内核提供了一系列函数来实现内核中的原子操作,
这些函数分为两类,分别针对(位和整形变量)进行原子操作。特点咋任何情况下操作都是原子的,内核代码可以安全地
调用它们而不被打断。位和整形变量原子操作都依赖底层CPU的原子操作来实现,因此所有这些函数都与cpu架构相关。
自旋锁
自旋锁(spin lock)是一种典型的对临界资源进行互斥访问的手段,为了获得自旋锁在某个cpu上运行的代码需先执行一个
原子操作,该操作测试并设置某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存
变量。如果测试操作结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果结果表明锁仍被占用,程序将在一个小
的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”(原地打转)。当自旋锁的持有这通过重置该变量释放自旋锁后,
某个等待的“测试并设置”操作向其调用者报告锁已释放。
定义自旋锁:spin_lock_t lock;
初始化自旋锁:spin_lock_init(lock);
获得自旋锁:spin_lock(lock);如果获取到自旋锁就立即返回,否则它将自旋在哪里,直到该自旋锁的持有者释放。
spin_trylock(lock);尝试获得自旋锁,如果立即获得锁,就返回真,否则立即返回假,不再“原地打转”。
释放自旋锁:spin_unlock(lock);
自旋锁使用中需要注意的问题:
1,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,
使用自旋锁会降低效率。原因是自旋锁实际上是忙等锁,当锁不可用时cpu一直循环执行“测试并设置”该锁直到可用而取得该
锁,cpu在等待自旋锁时不做任何有用的工作,仅仅是等待。
2,自旋锁可能导致系统死锁。比如递归调用一个自旋锁,即如果一个已经拥有某个自旋锁的cpu想第二次获得这个自旋锁,则
该cpu将死锁。
3,自旋锁锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞,如调用copy_from_user(),copy_to_user(),
kmalloc()和msleep()等函数,则可能导致内核的崩溃。
自旋锁实现设备只能被一个进程打开
读写自旋锁:
自旋锁不支持并行读,但是自旋锁的衍生锁读写自旋锁支持读并行。
自旋锁不关心锁定的临界区究竟进行怎样的操作,不管是读还是写都一样对待,即便多个执行单元同时读取临界资源也会被锁住。
实际上对共享资源并发访问时,多个执行单元同时读取它是不会有问题的,自旋锁的衍生锁读写自旋锁(rwlock)可允许读的并发,
但是在写操作方面某一时刻只能最多有1个写进程,在读操作方面某一时刻可以有多个读执行单元,读写某一时刻最多只能有一个。
顺序锁:
顺序锁(seqlock)是对读写锁的一种优化,如使用顺序锁读执行单元不会被写执行单元阻塞,也就是说读执行单元在写执行单元
对被顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行
完成读操作才去进行写操作。
但是,写执行单元也写执行单元之间仍然是互斥的,A没有释放,B只能自旋在哪里直到A释放顺序锁。如果读执行单元在读操作期
间,写执行单元已经发生了写操作,那么读执行单元必须重新读取数据,以便确保得到的数据是完整的。这种锁对于读写同时进行的
概率比较小的情况,性能是非常好的,而且它允许读写同时进行,因而更大的提高了并发性。
顺序锁的限制,它必须要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要去访问该指
针,将导致oops。
单cpu,在单cpu范围内避免竞态的简单方法是在进入临界区之前屏蔽系统的中断。cpu一般都具备屏蔽中断和打开中断
的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序抢占,防止某些竞态条件的发生。由于linux内核
的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也得以避免。
中断屏蔽的使用方法为:
local_irq_disable() //屏蔽中断
...
critical section //临界区
...
local_irq_enable() //开中断
由于linux的异步I/O,进程调度等很多重要操作都依赖于中断,中断对于内核的运行非常重要,在屏蔽中断期间所有的中
断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失乃至系统崩溃等后果。在就要求在屏蔽中断
之后,当前的内核执行路径应当尽快地执行完临界区的代码。
local_irq_disable()和local_irq_enable()只能屏蔽使能本cpu(单个cpu)的中断,因此不适合多cpu的竞态处理。因此
单独使用中断屏蔽通常不是一种值得推荐的避免竞态的方法,它适合于自旋锁联合使用。
与local_irq_disable()不同的是,local_irq_save(flags)除了进行禁止中断的操作以外,还保存目前CPU的中断位信息。
如果只是想禁止使能中断的底半部,应使用local_bh_disable(),local_bh_enable()。
原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。linux内核提供了一系列函数来实现内核中的原子操作,
这些函数分为两类,分别针对(位和整形变量)进行原子操作。特点咋任何情况下操作都是原子的,内核代码可以安全地
调用它们而不被打断。位和整形变量原子操作都依赖底层CPU的原子操作来实现,因此所有这些函数都与cpu架构相关。
自旋锁
自旋锁(spin lock)是一种典型的对临界资源进行互斥访问的手段,为了获得自旋锁在某个cpu上运行的代码需先执行一个
原子操作,该操作测试并设置某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存
变量。如果测试操作结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果结果表明锁仍被占用,程序将在一个小
的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”(原地打转)。当自旋锁的持有这通过重置该变量释放自旋锁后,
某个等待的“测试并设置”操作向其调用者报告锁已释放。
定义自旋锁:spin_lock_t lock;
初始化自旋锁:spin_lock_init(lock);
获得自旋锁:spin_lock(lock);如果获取到自旋锁就立即返回,否则它将自旋在哪里,直到该自旋锁的持有者释放。
spin_trylock(lock);尝试获得自旋锁,如果立即获得锁,就返回真,否则立即返回假,不再“原地打转”。
释放自旋锁:spin_unlock(lock);
自旋锁使用中需要注意的问题:
1,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,
使用自旋锁会降低效率。原因是自旋锁实际上是忙等锁,当锁不可用时cpu一直循环执行“测试并设置”该锁直到可用而取得该
锁,cpu在等待自旋锁时不做任何有用的工作,仅仅是等待。
2,自旋锁可能导致系统死锁。比如递归调用一个自旋锁,即如果一个已经拥有某个自旋锁的cpu想第二次获得这个自旋锁,则
该cpu将死锁。
3,自旋锁锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞,如调用copy_from_user(),copy_to_user(),
kmalloc()和msleep()等函数,则可能导致内核的崩溃。
自旋锁实现设备只能被一个进程打开
int x_count = 0; static int x_open(struct inode *inode,struct file *filp) { ... spinlock(&x_lock); if(x_count){ spin_unlock(&x_lock); return -EBUSY; } x_count++; spin_unlock(&x_lock); ... return 0; } static int x_release(struct inode *inode,struct file *filp) { ... spinlock(&x_lock); x_count--; spin_unlock(&x_lock); return 0; }
读写自旋锁:
自旋锁不支持并行读,但是自旋锁的衍生锁读写自旋锁支持读并行。
自旋锁不关心锁定的临界区究竟进行怎样的操作,不管是读还是写都一样对待,即便多个执行单元同时读取临界资源也会被锁住。
实际上对共享资源并发访问时,多个执行单元同时读取它是不会有问题的,自旋锁的衍生锁读写自旋锁(rwlock)可允许读的并发,
但是在写操作方面某一时刻只能最多有1个写进程,在读操作方面某一时刻可以有多个读执行单元,读写某一时刻最多只能有一个。
顺序锁:
顺序锁(seqlock)是对读写锁的一种优化,如使用顺序锁读执行单元不会被写执行单元阻塞,也就是说读执行单元在写执行单元
对被顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行
完成读操作才去进行写操作。
但是,写执行单元也写执行单元之间仍然是互斥的,A没有释放,B只能自旋在哪里直到A释放顺序锁。如果读执行单元在读操作期
间,写执行单元已经发生了写操作,那么读执行单元必须重新读取数据,以便确保得到的数据是完整的。这种锁对于读写同时进行的
概率比较小的情况,性能是非常好的,而且它允许读写同时进行,因而更大的提高了并发性。
顺序锁的限制,它必须要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要去访问该指
针,将导致oops。
相关文章推荐
- Driver:内核的竞态和并发:中断屏蔽、原子操作、自旋锁、信号量
- linux驱动中的互斥途径一二:中断屏蔽和原子操作
- [Linux]互斥机制(中断屏蔽、原子操作、自旋锁、信号量)
- 中断屏蔽 原子操作 信号量
- Linux设备驱动第七天(原子性:中断屏蔽、自旋锁、信号量)
- 操作系统学习笔记(20)--开中断、关中断及原子操作
- [自制操作系统] 原子操作&核间中断&读写锁&PRWLock
- 嵌入式中断通信控制的原子操作
- 第三讲 ecos中断操作zz
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- 破除java神话之三:原子操作都是线程安全的
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- OpenMP创建线程中的锁及原子操作性能比较
- ecos中断操作
- 屏蔽键盘和鼠标的一些操作