Linux核心的同步機制(1)(轉)

gugu99發表於2007-08-10
Linux核心的同步機制(1)(轉)[@more@]

  本文詳細的介紹了Linux核心中的同步機制:原子操作、訊號量、讀寫訊號量和自旋鎖的API,使用要求以及一些典型示例

  一、引言

  在現代作業系統裡,同一時間可能有多個核心執行流在執行,因此核心其實象多程式多執行緒程式設計一樣也需要一些同步機制來同步各執行單元對共享資料的訪問。尤其是在多處理器系統上,更需要一些同步機制來同步不同處理器上的執行單元對共享的資料的訪問。

  在主流的Linux核心中包含了幾乎所有現代的作業系統具有的同步機制,這些同步機制包括:原子操作、訊號量(semaphore)、讀寫訊號量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4核心中)、RCU(只包含在2.6核心中)和seqlock(只包含在2.6核心中)。

  二、原子操作

  所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位,因此這裡的原子實際是使用了物理學裡的物質微粒的概念。

  原子操作需要硬體的支援,因此是架構相關的,其API和原子型別的定義都定義在核心原始碼樹的include/asm/atomic.h檔案中,它們都使用匯編語言實現,因為C語言並不能實現這樣的操作。

  原子操作主要用於實現資源計數,很多引用計數(refcnt)就是透過原子操作實現的。原子型別定義如下:

  typedef struct { volatile int counter; } atomic_t;

  volatile修飾欄位告訴gcc不要對該型別的資料做最佳化處理,對它的訪問都是對記憶體的訪問,而不是對暫存器的訪問。

  原子操作API包括:

  atomic_read(atomic_t * v);

  該函式對原子型別的變數進行原子讀操作,它返回原子型別的變數v的值。

  atomic_set(atomic_t * v, int i);

  該函式設定原子型別的變數v的值為i。

  void atomic_add(int i, atomic_t *v);

  該函式給原子型別的變數v增加值i。

  atomic_sub(int i, atomic_t *v);

  該函式從原子型別的變數v中減去i。

  int atomic_sub_and_test(int i, atomic_t *v);

  該函式從原子型別的變數v中減去i,並判斷結果是否為0,如果為0,返回真,否則返回假。

  void atomic_inc(atomic_t *v);

  該函式對原子型別變數v原子地增加1。

  void atomic_dec(atomic_t *v);

  該函式對原子型別的變數v原子地減1。

  int atomic_dec_and_test(atomic_t *v);

  該函式對原子型別的變數v原子地減1,並判斷結果是否為0,如果為0,返回真,否則返回假。

  int atomic_inc_and_test(atomic_t *v);

  該函式對原子型別的變數v原子地增加1,並判斷結果是否為0,如果為0,返回真,否則返回假。

  int atomic_add_negative(int i, atomic_t *v);

  該函式對原子型別的變數v原子地增加I,並判斷結果是否為負數,如果是,返回真,否則返回假。

  int atomic_add_return(int i, atomic_t *v);

  該函式對原子型別的變數v原子地增加i,並且返回指向v的指標。

  int atomic_sub_return(int i, atomic_t *v);

  該函式從原子型別的變數v中減去i,並且返回指向v的指標。

  int atomic_inc_return(atomic_t * v);

  該函式對原子型別的變數v原子地增加1並且返回指向v的指標。

  int atomic_dec_return(atomic_t * v);

  該函式對原子型別的變數v原子地減1並且返回指向v的指標。

  原子操作通常用於實現資源的引用計數,在TCP/IP協議棧的IP碎片處理中,就使用了引用計數,碎片佇列結構struct ipq描述了一個IP碎片,欄位refcnt就是引用計數器,它的型別為atomic_t,當建立IP碎片時(在函式ip_frag_create中),使用atomic_set函式把它設定為1,當引用該IP碎片時,就使用函式atomic_inc把引用計數加1。

  當不需要引用該IP碎片時,就使用函式ipq_put來釋放該IP碎片,ipq_put使用函式atomic_dec_and_test把引用計數減1並判斷引用計數是否為0,如果是就釋放IP碎片。函式ipq_kill把IP碎片從ipq佇列中刪除,並把該刪除的IP碎片的引用計數減1(透過使用函式atomic_dec實現)。

  三、訊號量(semaphore)

  Linux核心的訊號量在概念和原理上與使用者態的System V的IPC機制訊號量是一樣的,但是它絕不可能在核心之外使用,因此它與System V的IPC機制訊號量毫不相干。

  訊號量在建立時需要設定一個初始值,表示同時可以有幾個任務可以訪問該訊號量保護的共享資源,初始值為1就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問訊號量保護的共享資源。

  一個任務要想訪問共享資源,首先必須得到訊號量,獲取訊號量的操作將把訊號量的值減1,若當前訊號量的值為負數,表明無法獲得訊號量,該任務必須掛起在該訊號量的等待佇列等待該訊號量可用;若當前訊號量的值為非負數,表示可以獲得訊號量,因而可以立刻訪問被該訊號量保護的共享資源。

  當任務訪問完被訊號量保護的共享資源後,必須釋放訊號量,釋放訊號量透過把訊號量的值加1實現,如果訊號量的值為非正數,表明有任務等待當前訊號量,因此它也喚醒所有等待該訊號量的任務。

  訊號量的API有:

DECLARE_MUTEX(name)

  該宏宣告一個訊號量name並初始化它的值為0,即宣告一個互斥鎖。

DECLARE_MUTEX_LOCKED(name)

  該宏宣告一個互斥鎖name,但把它的初始值設定為0,即鎖在建立時就處在已鎖狀態。因此對於這種鎖,一般是先釋放後獲得。

void sema_init (struct semaphore *sem, int val);

  該函用於數初始化設定訊號量的初值,它設定訊號量sem的值為val。

void init_MUTEX (struct semaphore *sem);

  該函式用於初始化一個互斥鎖,即它把訊號量sem的值設定為1。

void init_MUTEX_LOCKED (struct semaphore *sem);

  該函式也用於初始化一個互斥鎖,但它把訊號量sem的值設定為0,即一開始就處在已鎖狀態。

void down(struct semaphore * sem);

  該函式用於獲得訊號量sem,它會導致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函式。該函式將把sem的值減1,如果訊號量sem的值非負,就直接返回,否則呼叫者將被掛起,直到別的任務釋放該訊號量才能繼續執行。

int down_interruptible(struct semaphore * sem);

  該函式功能與down類似,不同之處為,down不會被訊號(signal)打斷,但down_interruptible能被訊號打斷,因此該函式有返回值來區分是正常返回還是被訊號中斷,如果返回0,表示獲得訊號量正常返回,如果被訊號打斷,返回-EINTR。

int down_trylock(struct semaphore * sem);

  該函式試著獲得訊號量sem,如果能夠立刻獲得,它就獲得該訊號量並返回0,否則,表示不能獲得訊號量sem,返回值為非0值。因此,它不會導致呼叫者睡眠,可以在中斷上下文使用。

void up(struct semaphore * sem);

  該函式釋放訊號量sem,即把sem的值加1,如果sem的值為非正數,表明有任務等待該訊號量,因此喚醒這些等待者。

  訊號量在絕大部分情況下作為互斥鎖使用,下面以console驅動系統為例說明訊號量的使用。

  在核心原始碼樹的kernel/printk.c中,使用宏DECLARE_MUTEX宣告瞭一個互斥鎖console_sem,它用於保護console驅動列表console_drivers以及同步對整個console驅動系統的訪問。

  其中定義了函式acquire_console_sem來獲得互斥鎖console_sem,定義了release_console_sem來釋放互斥鎖console_sem,定義了函式try_acquire_console_sem來盡力得到互斥鎖console_sem。這三個函式實際上是分別對函式down,up和down_trylock的簡單包裝。

  需要訪問console_drivers驅動列表時就需要使用acquire_console_sem來保護console_drivers列表,當訪問完該列表後,就呼叫release_console_sem釋放訊號量console_sem。

  函式console_unblank,console_device,console_stop,console_start,register_console和unregister_console都需要訪問console_drivers,因此它們都使用函式對acquire_console_sem和release_console_sem來對console_drivers進行保護。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-940136/,如需轉載,請註明出處,否則將追究法律責任。

相關文章