1. 程式人生 > >linux 驅動——併發和競態

linux 驅動——併發和競態

一、併發造成的原因

l 中斷——中斷幾乎可以在任何時刻非同步發生,也就可能隨時打斷當前正在執行的程式碼。
2 睡眠及與使用者空間的同步——在核心執行的程序可能會睡眠,這就會喚醒排程程式,從而導致排程一個新的使用者程序執。
3 對稱多處理——兩個或多個處理器可以同時執行程式碼。
4核心搶佔——因為核心具有搶佔性,所以核心中的任務可能會被另一任務搶佔(在2.6核心引進的新能力)。
5 多個使用者空間程序組合訪問程式碼

要在核心程式碼中避免使用全域性變數
程序上下文:實際上是程序執行活動全過程的靜態描述。我們把已執行過的程序指令和資料在相關暫存器與堆疊中的內容稱為上文,把正在執行的指令和資料在暫存器和堆疊中的內容稱為正文,把待執行的指令和資料在暫存器與堆疊中的內容稱為下文。

二、訊號量與互斥體

訊號量: 是P、V操作,訪問臨界區時,程序條用P,訊號量大於零時,訊號量值減一,可獲得臨界區資源,程序繼續;訊號量不大於零時,程序等待其他人獲取臨界區資源。釋放臨界區資源時,呼叫V操作,訊號量值增加,並喚醒等待此資源的程序。
互斥體:當訊號量初始化為1時,只能被一個程序同時擁有,此時可稱為互斥體。

使用核心態中, 訊號量幾乎都用於互斥。與互斥體相同。
使用者態: 訊號量:是程序間(執行緒間)同步用的,一個程序(執行緒)完成了某一個動作就通過訊號量告訴別的程序(執行緒),別的程序(執行緒)再進行某些動作。有二值和多值訊號量之分。
互斥鎖:是執行緒間互斥用的,一個執行緒佔用了某一個共享資源,那麼別的執行緒就無法訪問,直到這個執行緒離開,其他的執行緒才開始可以使用這個共享資源。可以把互斥鎖看成二值訊號量。

 訊號量的初始化及操作。
void sema_init(struct semaphore *sem, int val);

void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);

void up(struct semaphore *sem);

在新的核心中推薦使用互斥體。訊號量不被推薦。

互斥體初始化及其操作

巨集DEFINE_MUTEX靜態定義和初始化一個互斥鎖
static DEFINE_MUTEX(mymutex); 

動態初始化
void
mutex_init(struct mutex *mutex); void mutex_lock(struct mutex *lock); ————> 睡眠時,不可被訊號中斷 int mutex_lock_interruptible(struct mutex *lock); ————> 睡眠時,可被訊號中斷 int mutex_trylock(struct mutex *lock); ————> 獲取不到訊號時,會返回 void mutex_unlock(struct mutex *lock);

非訊號中斷可以理解為建立不可殺的程序。一般採用mutex_lock_interruptible作為使用

讀取者/寫入者訊號量

void init_rwsem(struct rw_semaphore *sem);
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);

void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);

三、自旋鎖 (spinlock)

使用 : 可在中斷處理例程中獲得更高的效能。

自旋鎖死鎖造成的原因: 獲得自旋鎖時,在臨界區執行時,若呼叫了可能造成休眠的函式如copy_to_user, kmalloc, 此時程序可能休眠,也可能發生核心搶佔,或是發生中斷,此時擁有鎖的程序會丟到處理器;之後,其他程序可能會呼叫此自旋鎖,此時就會造成死鎖。
擁有自旋鎖時,但核心搶佔被禁止,SMP下的該核的搶佔排程也被禁止。但可能受到硬體中斷核軟體中斷的影響,所以需要使用軟中斷。
自旋鎖定義及使用

void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock);  ————>沒有禁止中斷
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);————>禁止中斷,返回時,中斷狀態與禁止中斷一致
void spin_lock_irq(spinlock_t *lock);————>禁止中斷,返回時,中斷狀態開
void spin_lock_bh(spinlock_t *lock);————>禁止軟中斷

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);

四、自旋鎖與互斥鎖的使用原則

1、自旋鎖時忙等待、互斥鎖是休眠,所以大的臨界區用互斥鎖,小的用自旋鎖
2、互斥鎖保護的臨界區可以包含引起阻塞的函式、程式碼如copy_to_user、kmalloc、msleep;
而自旋鎖禁止,若引起阻塞,新的程序可能呼叫此自旋鎖,就會造成死鎖。
3、被中斷呼叫的臨界區需要使用自旋鎖,若要使用互斥鎖,則只能使用mutex_trylock();

五、鎖之外的方法

1、迴圈佇列
迴圈緩衝區及兩個索引, 要保證兩個索引不重疊

2、原子操作

void atomic_set(atomic_t *v, int i);
atomic_t v = ATOMIC_INIT(0);
int atomic_read(atomic_t *v);
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
int atomic_add_negative(int i, atomic_t *v);
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
atomic_sub(amount, &first_atomic);
atomic_add(amount, &second_atomic);

3、 位操作

void set_bit(nr, void *addr);
void clear_bit(nr, void *addr);
void change_bit(nr, void *addr);
test_bit(nr, void *addr);
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);

4、順序鎖讀寫更新