標籤:
現代作業系統有三大特性:中斷處理、多任務處理和多處理器。這些特性導致當多個進程、線程或者CPU同時訪問一個資源時,可能發生錯誤,這些錯誤是作業系統運行所不允許的。在作業系統中,核心需要提供並發控制機制,對共用資源進行保護。
在作業系統中,並發是指一個時間段中有幾個程式都處於已啟動運行到運行完畢之間,且這幾個程式都是在同一個處理機上運行,但任一個時刻點上只有一個程式在處理機上運行。並發容易導致競爭的問題。競爭就是兩個或兩個以上的進程同時訪問一個資源,同時引起資源的錯誤。並發控制機制有以下幾種:
1.原子變數操作: 原子變數操作(分為原子整型操作和原子位操作)就是絕不會在執行完畢前被任何其他任務和時間打斷,不會執行一半,又去執行其他代碼。原子操作需要硬體的支援,因此是架構相關的,其API和原子類型的定義都在include/asm/atomic.h中,使用組合語言實現。 在linux中,原子變數的定義如下: typedef struct {
volatile int counter;
} atomic_t;
關鍵字volatile用來暗示GCC不要對該類型做資料最佳化,所以對這個變數counte的訪問都是基於記憶體的,不要將其緩衝到寄存器中。儲存到寄存器中,可能導致記憶體中的資料已經改變,而寄存其中的資料沒有改變。
(1)原子整型操作:
1)定義atomic_t變數:#define ATOMIC_INIT(i) ( (atomic_t) { (i) } )
atomic_t v = ATOMIC_INIT(0); //定義原子變數v並初始化為0
2)設定原子變數的值#define atomic_set(v,i) ((v)->counter = (i))
void atomic_set(atomic_t *v, int i);//設定原子變數的值為i 3)擷取原子變數的值:#define atomic_read(v) ((v)->counter + 0)
atomic_read(atomic_t *v);//返回原子變數的值
4)原子變數加/減:
static __inline__ void atomic_add(int i, atomic_t * v); //原子變數增加i
static __inline__ void atomic_sub(int i, atomic_t * v); //原子變數減少i
5)原子變數自增/自減:
#define atomic_inc(v) atomic_add(1, v); //原子變數加1
#define atomic_dec(v) atomic_sub(1, v); //原子變數減1
6)操作並測試:
//這些操作對原子變數執行自增,自減,減操作後測試是否為0,是返回true,否則返回false
#define atomic_inc_and_test(v) (atomic_add_return(1, (v)) == 0)
static inline int atomic_add_return(int i, atomic_t *v)
(2)原子位操作(根據資料的每一位單獨進行操作):
static inline void set_bit(nr, void *addr); //設定addr地址的第nr位,所謂設定位即將位寫為1
static inline void clear_bit(nr,void *addr); //清除addr地址的第nr位,所謂清除位即將位寫為0
static inline void change_bit(nr,void *addr);
static inline void test_bit(nr, void *addr);
static inline int test_and_set_bit(nr, void *addr);
static inline int test_and_clear_bit(nr, void *addr);
static inline int test_and_change_bit(nr, void *addr); 原子操作的優點編寫簡單;缺點是功能太簡單,只能做計數操作,保護的東西太少。
2.自旋鎖 自旋鎖是專為防止多處理器並發而引入的一種鎖,它應用於中斷處理等部分。對於單一處理器來說,防止中斷處理中的並發可簡單採用關閉中斷的方式,不需要自旋鎖。 自旋鎖最多隻能被一個核心任務持有,如果一個核心任務試圖請求一個已被爭用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙迴圈——旋轉——等待鎖重新可用。要是鎖未被爭用,請求它的核心任務便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於一個的核心任務同時進入臨界區,因此這種鎖可有效地避免多處理器上並發啟動並執行核心任務競爭共用資源。(1)自旋鎖的使用:spinlock_t spin; //定義自旋鎖
spin_lock_init(lock); //初始化自旋鎖
spin_lock(lock); //成功獲得自旋鎖立即返回,否則自旋在那裡直到該自旋鎖的保持者釋放
spin_trylock(lock); //成功獲得自旋鎖立即返回真,否則返回假,而不是像上一個那樣"在原地打轉"
spin_unlock(lock);//釋放自旋鎖 使用代碼:spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock);
....//臨界資源區
spin_unlock(&lock);
(2)注意事項:
1)自旋鎖是一種忙等待。它是一種適合短時間鎖定的輕量級的加鎖機制。 2)自旋鎖不能遞迴使用。自旋鎖被設計成在不同線程或者函數之間同步。這是因為,如果一個線程在已經持有自旋鎖時,其處於忙等待狀態,則已經沒有機會釋放自己持有的鎖了。如果這時再調用自身,則自旋鎖永遠沒有執行的機會了。
3.訊號量 linux中,提供了兩種訊號量:一種用於核心程式中,一種用於應用程式中。這裡只講屬前者。 訊號量和自旋鎖的使用方法基本一樣。與自旋鎖相比,訊號量只有當得到訊號量的進程或者線程時才能夠進入臨界區,執行臨界代碼。訊號量和自旋鎖的最大區別在於:當一個進程試圖去獲得一個已經鎖定的訊號量時,進程不會像自旋鎖一樣在遠處忙等待。 訊號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的訊號量時,訊號量會將其推入等待隊列,然後讓其睡眠。這時處理器獲得自由去執行其它代碼。當持有訊號量的進程將訊號量釋放後,在等待隊列中的一個任務將被喚醒,從而便可以獲得這個訊號量。(1)訊號量的實現: 在linux中,訊號量的定義如下:struct semaphore {
spinlock_t lock; //用來對count變數起保護作用。
unsigned int count; // 大於0,資源空閑;等於0,資源忙,但沒有進程等待這個保護的資源;小於0,資源不可用,並至少有一個進程等待資源。
struct list_head wait_list; //存放等待隊列鏈表的地址,當前等待資源的所有睡眠進程都會放在這個鏈表中。
}; (2)訊號量的使用:1)定義訊號量: struct semaphore sem;2)初始化訊號量 :static inline void sema_init(struct semaphore *sem, int val); //設定sem為val
#define init_MUTEX(sem) sema_init(sem, 1) //初始化一個使用者互斥的訊號量sem設定為1
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0) //初始化一個使用者互斥的訊號量sem設定為0定義和初始化可以一步完成:DECLARE_MUTEX(name); //該宏定義訊號量name並初始化1
DECLARE_MUTEX_LOCKED(name); //該宏定義訊號量name並初始化0 當訊號量用於互斥時(即避免多個進程同是在一個臨界區運行),訊號量的值應初始化為1。這種訊號量在任何給定時刻只能由單個進程或線程擁有。在這種使用模式下,一個訊號量有事也稱為一個“互斥體(mutex)”,它是互斥(mutual exclusion)的簡稱。Linux核心中幾乎所有的訊號量均用於互斥。 使用訊號量,核心代碼必須包含<asm/semaphore.h> 3)擷取(鎖定)訊號量:void down(struct semaphore *sem);//該函數會導致睡眠,所以不能在中斷上下文中使用int down_interruptible(struct semaphore *sem);//該函數進入睡眠後,可以被訊號喚醒int down_killable(struct semaphore *sem);
4)釋放訊號量
void up(struct semaphore *sem); //釋放訊號量sem,喚醒等待者4.完成量 它用於一個執行單元等待另一個執行單元執行完某事;struct completion {
unsigned int done; //大於0,表示完成量的函數可以立即執行,不要要等待;等於0,將擁有完成量的線程置於等待狀態。
wait_queue_head_t wait;
};1)定義完成量:struct completion com;2)初始化:init_completion(&com); //要是覺得這兩步麻煩,就再給你來個宏即定義又初始化DECLARE_COMPLETION(com);3)等待完成量:void __sched wait_for_completion(struct completion *x); //等待一個completion被喚醒int __sched wait_for_completion_interruptible(struct completion *x);//可中斷的wait_for_completionunsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout);//帶逾時處理的wait_for_completion4)喚醒完成量void complete(struct completion *x); //只喚醒一個等待的進程或線程。
void complete_all(struct completion *x); //喚醒所有等待這個完成量的進程或者線程
後記:除了上述幾種廣泛使用的的並發控制機制外,還有中斷屏蔽、順序鎖(seqlock)、RCU(Read-Copy-Update)等等,做個簡單總結如
linux驅動程式中的並發控制