【驱动】同步、互斥、阻塞

1. 原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
常用原子操作函数举例:
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_inc(atomic_t *v); //原子变量增加1
void atomic_dec(atomic_t *v); //原子变量减少1
int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false。

2. 信号量
信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。
当获取不到信号量时,进程进入休眠等待状态,即当持有信号量时,(进程)是可以休眠的。休眠(同时交出CPU使用权),使得CPU得到最大限度的利用。

  定义信号量
  struct semaphore sem;
  初始化信号量
  void sema_init (struct semaphore *sem, int val);
  void init_MUTEX(struct semaphore *sem);//初始化为0

以上三句指令等价于:

  static DECLARE_MUTEX(button_lock); //定义互斥锁

获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem);
int down_trylock(struct semaphore * sem);
释放信号量
void up(struct semaphore * sem);

问题1:互斥锁是一种特殊的信号量?

 

3. 阻塞
阻塞操作
是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。
被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

非阻塞操作
进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

fd = open("...", O_RDWR | O_NONBLOCK);

 

 

 

在TP驱动定义一个信号量互斥锁DECLARE_MUTEX(xx_lock), //等同struct semaphore xx_lock; init_MUTEX(&xx_lock);之和;
xx_open()中down(&xx_lock)获取信号量,
在xx_close()中up(&xx_lock)释放信号量。

fd = open("/dev/keys_10", O_RDWR);则默认为阻塞操作。


DECLARE_MUTEX宏

Linux可以使用互斥信号量来表示互斥锁,那就是通过宏DECLARE_MUTEX来定义一个互斥信号量,因为DECLARE_MUTEX这个宏,

Marcin Slusarz在08年提交的了一个patch,邮件地址为:https://lkml.org/lkml/2008/10/26/74,Marcin Slusarz认为DECLARE_MUTEX宏会误导开发者,所以建议将DECLARE_MUTEX修改成DEFINE_SEMAPHORE,这个提议最终被内核社区所接受,在2.6.36版本后的内核就没有DECLARE_MUTEX这个宏了,取而代之的是DEFINE_SEMAPHORE宏,在后来同互斥信号量相关的init_MUTEX、init_MUTEX_LOCKED也从<linux/semaphore.h>文件中移除了。

————————————————
版权声明:本文为CSDN博主「Zackary-」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/WANG__RONGWEI/article/details/52832652


自旋锁:

/*简单,直接的互斥体与严格的语义:
*一次只能有一个任务持有互斥锁
* -只有拥有者可以解锁互斥锁
* -不允许多次解锁
* -不允许递归锁定
* -互斥对象必须通过API初始化
* -互斥对象不能通过memset或复制来初始化
* -任务可能不退出互斥锁持有
* -持有锁所在的内存区域不能被释放
* -不能重新初始化被持有的互斥对象互斥体不能用于硬件或软件中断
*上下文,如微线程和计时器**当DEBUG_MUTEXES被执行时,这些语义将被完全执行启用。此外,除了执行上述规则外,互斥锁还可以执行

*调试代码还实现了许多附加功能,使锁调试更容易和更快:

* -在调试输出中打印互斥对象时,使用它们的符号名

* -获取点跟踪,函数名的符号查找
* -系统中所有锁的清单,打印出来
* -业主追踪检测自递归锁并打印出所有相关信息检测多任务循环死锁并打印出所有受影响的锁和任务(仅限那些任务)
* /

struct mutex {

/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct thread_info *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,此即"自旋锁"。

互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁、过多占用cpu资源。

 
信号量、互斥锁、自旋锁的使用区别:
由此可见,自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。
 

自旋锁和信号量的使用要点:
(1)自旋锁不能递归
(2)自旋锁可以用在中断上下文(信号量不可以,因为可能睡眠),但是在中断上下文中获取自旋锁之前要先禁用本地中断

  注:中断上下文是不参与进程调度的。
(3)自旋锁的核心要求是:拥有自旋锁的代码必须不能睡眠,要一直持有CPU直到释放自旋锁
(4)信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。
如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。

 

posted @ 2020-01-15 20:56  大秦长剑  阅读(261)  评论(0编辑  收藏  举报