嵌入式Linux设备驱动开发笔记(四)

来源:互联网 发布:ubuntu挂载共享文件夹 编辑:程序博客网 时间:2024/06/05 04:41

一、Linux内核锁保护

(1)为什么要保护

如果模块中的某个全局变量可以被多个进程/中断同时访问,那么就必须要提供加锁机制进行保护。同时写是不允许的。

(2)和加锁保护相关的名词

A、临界区(critical region)

访问要保护的变量的代码段,称为临界区。临界区中同一时间只能一个人进入。临界区的代码可能分散在不同的函数中。如果要对临界区加锁,则必须保证在临界区的所有部分都加锁。如果有遗漏,则加锁是不可靠的。

B、同步(synchronization)

其实就是加锁。同步是保证临界区只进一个人的机制。

C、竞争(race condition)

如果有多个人同时进入临界区,就称为竞争。一般来说,认为出现竞争是错误的。

(3)加锁的原则

A、加锁是自愿的

内核不强迫程序加锁,但一旦使用了锁,驱动设计人员应该保证正确地使用锁,并保证在临界区的每个部分都加上锁。

B、不要用锁来实现功能

锁就是用来避免对变量同时访问,是一种保护机制。如果将加锁相关代码去掉,程序功能应该没有任何变化。

C、应该在一开始设计代码时,就考虑如何加锁

一定不要代码设计完毕后再加锁,很容易出问题。最好在设计私有结构体时,就开始考虑锁的使用。锁一般会包括到私有结构体中,根据设备的复杂程度,有可能需要多把锁。

(4)死锁

如果加锁不恰当,可能出现死锁。有两种常见的死锁情况:自死锁和ABBA死锁。

二、Linux内核中的哪些机制导致必须要加锁

换句话说,如果没有这些机制,就可以不加锁了。也就是说,如果当前内核不可能出现“同时”,则不必加锁。

可能导致同时访问变量的内核机制有:
(1)SMP
(2)中断
(3)内核抢占(preempt)
(4)schedule()

需要了解这些机制,才能够正确使用加锁函数。

三、原子变量(atomic_t)

对于i++类型的临界区,如果用普通的加锁保护,效率太低,因此内核推荐用atomic_t类型的变量来替代传统的int型变量。对atomic_t类型的变量,内核提供更简单、安全的访问。只要访问atomic_t变量,就必须使用内核提供的函数。注意!不同的处理器实现原子操作的方式不同。

#include <linux/atomic.h> //或<asm/atomic.h>//由于atomic_t的实现和具体处理器相关,因此参考头文件arch/arm/include/asm/atomic.h//声明并初始化atomic_t变量atomic_t mycnt = ATOMIC_INIT(5); //或者 atomic_set(&mycnt, 5);//获得atomic_t中的计数int i = atomic_read(&mycnt);//变量的++atomic_inc(&mycnt);//有几个最常见的atomic函数,对应int型的操作:atomic_add // +=atomic_sub // -=atomic_inc // ++atomic_dec // --

四、自旋锁(spinlock_t)

内核中用的最多的锁,轻量级的锁,一般加解锁和等待锁的时间在ns级。

spinlock_t的特性:
(1)临界区中只能进入一个人
(2)等待的人忙等(只有SMP才会真正忙等)
(3)持有锁的人不能睡眠

结合前面讨论的内核机制,可以知道,spinlock可以针对SMP,中断和内核抢占提供保护。但schdule()不行。也就是说,如果临界区中必须调用kmalloc(size, GFP_KERNEL),或者copy_to/from_user之类可能导致睡眠的函数,则不能用spinlock保护。

#include <linux/spinlock.h>//声明锁spinlock_t mylock;//使用之前要先初始化spin_lock_init(&mylock);(1)普通的加解锁spin_lock(&mylock);...   //临界区spin_unlock(&mylock);(2)如果临界区可能被中断处理函数打断,并影响到要保护的变量,则应该在加锁的同时关闭中断。加锁同时关中断,将CPSR的当前值存储到flags中;解锁时打开中断,并将flags的值恢复到CPSR中unsigned long flags;spin_lock_irqsave(&mylock, flags);...   //临界区spin_unlock_irqrestore(&mylock, flags);

spinlock_t的一个典型应用是保护链表;由于内核要求同一时间只能一个人访问list_head链表,因此一般要用spinlock_t保护。

五、互斥锁(mutex)

mutex锁也是内核中非常重要的锁,属于重量级的锁,等待锁的时间常常为ms级。

mutex的特性:
(1)临界区中一个人
(2)睡眠等
(3)持有锁时可以睡眠(必须确保能被唤醒)

如果临界区中包含中断处理函数的代码,由于获得mutex时可能导致睡眠,因此不能用mutex来保护。其他情况都可以。

#include <linux/mutex.h>//声明mutex锁struct mutex mylock;//用之前要先初始化mutex_init(&mylock);//加锁和解锁mutex_lock(&mylock);//或者ret = mutex_lock_interruptible(&mylock);if (ret)    return -ERESTARTSYS;...   //临界区mutex_unlock(&mylock);

六、信号量(semaphore)

2.6内核早期没有实现mutex锁,用semaphore来实现mutex锁的功能。在现在的内核中,基本上已经不用semaphore来保护临界区。如果内核中某些资源限定了访问人数(比如只允许3个人同时访问),这时候可以用semaphore进行保护。

#include <linux/semaphore.h>//声明信号量struct semaphore mysem;//按照资源的限定初始化信号量sema_init(&mysem, 3);down(&mysem);//或者ret = down_interruptible(&mysem);...  //访问受限资源的代码up(&mysem);

七、其他的锁:

rwlock_t(读写锁,照顾读者,允许多个读者同时进入临界区,可能造成写者机饿)seqlock_t(顺序锁,克服了写者肌饿,但允许读写同时进行,无法保护list_head等)
阅读全文
0 0
原创粉丝点击