驅動程序中的併發控制方法:
一個硬件可能會被多個進程併發使用,例如scull_read的時候被另外一個進程調用的scull_write打斷,那麼讀到的數據就不是以前應該讀到的數據,這就需要併發控制
併發控制其實多數是使用信號量來完成,包括如下5種方式:1 信號量,2自旋鎖,3讀寫信號量,4讀寫自旋鎖,5completion機制
信號量操作方法:
1 定義及初始化
struct semaphore sem;
可用void sema_init(struct semaphore *sem, int val);直接創建,其中val爲信號量初值。
也可以用兩個宏來定義和初始化信號量的值爲1或0:
DECLARE_MUTEX(name) : 定義信號量name並初始化爲1
DECLARE_MUTEX_LOCKED(name) : 定義信號量name並初始化爲0
還可以用下面的函數初始化:
void init_MUTEX(struct semaphore *sem); //初始化信號量的值爲1
void init_MUTEX_LOCKED(struct semaphore *sem); //初始化信號量的值爲0
2 信號量操作
p操作:
void down(struct semaphore *sem); //用來獲取信號量,如果信號量值大於或等於0,獲取信號量,否則進入睡眠狀態,睡眠狀態不可喚醒
void down_interruptible(struct semephore *sem); //用來獲取信號量,如果信號量大於或等於0,獲取信號量,否則進入睡眠狀態,等待信號量被釋放後,激活該程。
void down_trylock(struct semaphore *sem); //試圖獲取信號量,如果信號量已被其他進程獲取,則立刻返回非零值,調用者不會睡眠
v操作:
void up(struct semaphore *sem); //釋放信號量,並喚醒等待該資源進程隊列的第一個進程函數
3 使用方法
定義:struct semaphore sem;
初始化:sema_init(&sem, 1);
獲取信號量:if(down_interruptible(&sem))
return -ERESTARTSYS;
釋放信號量:up(&sem);
4注意事項
down爲深度睡眠,不能被信號中斷;淺度睡眠down_interruptible可以被信號中斷,返回值可以用於判斷被喚醒的原因是由於其他進程執行了up還是收到了信號。
5 實驗
在scull_read中加入:ssleep(5);
寫入數據:echo yang>./scull0
併發讀出數據:cat ./scull0 & cat ./scull0
查看驅動的輸出:tail /var/log/syslog
自旋鎖的編程實戰:
1 自旋鎖的特點及與信號量的區別
(1)自旋鎖是一個互斥設備,只有兩個值:上鎖,解鎖
(2)上鎖spin_lock進入臨界區以後,操作系統不能進行任務調度,只會響應中斷,且中斷結束後也不進行進程調度,而是回到持有自旋鎖的進程
(3)如果獲取自旋鎖的時候已經被上鎖,代碼會一直檢查這個鎖,持續佔用cpu,且不會進入休眠,導致佔用大量cpu資源,所以只適合臨界區代碼比較短的場合
(4)持有自旋鎖以後的進程不要進入休眠,否則無法釋放自旋鎖,導致系統死鎖。
2 自旋鎖的操作方法
(1)定義和初始化
spinlock_t my_lock=SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);
(2)自旋鎖操作函數
void spin_lock(spinlock_t *lock) //獲取鎖
void spin_unlock(spinlock_t *lock) //釋放鎖
其他函數
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)//禁止中斷,但是保存interrupt中的flag
void spin_lock_irq(spinlock_t *lock)//期間禁止中斷
void spin_lock_bh(spinlock_t *lock)//禁止軟件中斷,使能硬件中斷
int spin_trylock(spinlock_t *lock)
int spin_trylock_bh(spinlock_t *lock)
2 使用方法:
(1)定義
spinlock_t lock;
(2)初始化
spin_lock_init(&lock);
(3)獲取自旋鎖
spin_lock(&lock);
(4)釋放自旋鎖
spin_unlock(&lock);
3 讀寫自旋鎖
在控制讀和寫不能併發執行的前提下,使得多個讀可以併發執行聲明和初始化
rwlock_t my_rwlock=RW_LOCK_UNLOCKED;
rwlock_t my_rwlock;rwlock_init(&my_rwlock);
操作方法:
void read_lock(rwlock_t *lock)
void read_lock_irqsave(rwlock_t *lock, unsigned long flags)
void read_lock_irq(rwlock_t *lock)
void read_lock_bh(rwlock_t *lock)
void read_unlock(rwlock_t *lock)
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
void read_unlock_irq(rwlock_t *lock)
void read_unlock_bh(rwlock_t *lock)
Competions機制
編寫驅動的時候,經常會做如下操作,在當前線程中初始化並啓動另外的線程,並且當前現成必須等待被啓動的夥伴線程幫助自己完成某些工作後,自己才能繼續執行
內核的信號量api針對可獲得的情況進行了專門的優化,當按照上述程序的方式來使用信號量api完成工作,則會導致第一個線程幾乎必然阻塞在down調用處,因此會導致性能遭受極大的下降
(1)定義,初始化
struct completion my_completion;
init_completion(&my_completion);
(2)等待完成量
void wait_for_completion(struct completion *c)
(3)喚醒完成量
void complete(struct completion *c)
void complete_all(struct completion *c)