Linux 下的同步機制

2022-03-19 02:29:08 字數 4170 閱讀 7021

2017-03-10

回想下最初的計算機設計,在單個cpu的情況下,同一時刻只能由乙個執行緒(在linux下為程序)占用cpu,且2.6之前的linux核心並不支援核心搶占,當程序在系統位址執行時,能打斷當前操作的只有中斷,而中斷處理完成後發現之前的狀態是在核心,就不觸發地排程,只有在返回使用者空間時,才會觸發排程。所以核心中的共享資源在單個cpu的情況下其實不需要考慮同步機制,儘管表面上看起來是多個程序在同時執行,其實那只是排程器以很小的時間粒度,排程各個程序執行的結果,事實上是乙個偽並行。但是隨著時代的發展,單個處理器根本滿足不了人們對效能的需求,多處理器架構才應運而生。這種情況下,多個處理器之間的工作互不干擾,可實現真正的並行。

但是作業系統只有乙個,其中不乏很多全域性共享的變數,即使是多cpu也不能同時對其程序操作。然而在多處理器情況下,如果我們不加以防護措施,極有可能兩個程序同時對同一變數進行訪問,這樣就容易造成資料的不同步。這種情況是開發者和使用者都無法忍受的。況且,在2.6之後的核心啟用了核心搶占,即使程序執行在系統位址空間也有可能被搶占,基於此,核心同步機制便被提出來。

核心中的同步機制又很多,具體由原子操作、訊號量、自旋鎖、讀寫者鎖,rcu機制等。每種方案都有其優缺點,且適用於不同的應用場景。

原子操作

原子操作在核心中主要保護某個共享變數,防止該變數被同時訪問造成資料不同步問題。為此,核心中定義了一系列的api,在核心中定義了atomic_t資料型別,其定義的資料操作都像是一條彙編指令執行,中間不會被中斷。atomic_t定義的資料型別和標準資料型別int/short等不相容,資料的加減不能通過標準運算子,必須通過其本身的api,下面是一些該型別操作的api

static __inline__ void atomic_add(int i, atomic_t *v)

static __inline__ void atomic_sub(int i, atomic_t *v)

static inline int atomic_add_return(int i, atomic_t *v)

static __inline__ long atomic_sub_return(int i, atomic_t * v)

基於上面的基礎api,還實現了其他的api,這裡就不在列舉。

訊號量

訊號量一般實現互斥操作,但是可以指定處於臨界區的程序數目,當規定數目為1時,表示此為互斥訊號量。訊號量在核心中的結構如下

struct

semaphore ;

開頭是乙個自旋鎖,用以保護該資料結構的操作,count指定了訊號量關聯的資源允許同時訪問的程序數目,wait_list是等待訪問資源的程序鍊錶。和自旋鎖相比,訊號量的乙個好處允許等待的程序睡眠,而不是一直在輪詢請求。所以訊號量比較適合於較長的臨界區。訊號量操作很簡單,初始初始化乙個訊號量,在臨界資源前需要down操作以請求獲得訊號量,執行完畢執行up操作釋放資源。

相關**如下

void down(struct semaphore *sem)

void up(struct semaphore *sem)

對於down操作,首先獲取訊號量結構的自旋鎖,並會關閉當前cpu的中斷,然後如果count還大於0,則直接分配資源,count--,否則呼叫down函式阻塞當前程序,down函式中直接呼叫了down_common函式。

static inline int __sched __down_common(struct semaphore *sem, long

state,

long

timeout)

timed_out:

list_del(&waiter.list);

return -etime;

interrupted:

list_del(&waiter.list);

return -eintr;

}

首先構建了乙個semaphore_waiter結構,插入到訊號量結構的等待程序鍊錶中。timeout是乙個超時時間,當設定為小於等於0時表示不在此等待資源。通過這些檢查後,設定當前程序為task_interruptible狀態,表示可被中斷喚醒的阻塞。然後開啟本地中斷表示當前任務告一段落,下面要呼叫schedule_timeout程序排程。在具體切換程序後,下半部分的**就是下次被排程的時候執行了。

而對於up操作,首先獲取自旋鎖,如果當前等待隊列為空,則單純的增加count表示可用資源增加,否則執行_up操作,該函式實現比較簡單。首先從等待鍊錶中移除對應節點,設定結構的up訊號為true,然後呼叫wake_up_process函式喚醒執行程序。這樣喚醒是吧程序加入就緒鍊錶中,可以被排程器正常排程。

static noinline void __sched __up(struct semaphore *sem)

自旋鎖

自旋鎖恐怕是核心中應用最為廣泛的同步機制了,在核心中表現為兩個功用:

1、對於資料結構或者變數的保護

2、對於臨界區**的保護

對於自旋鎖的操作很簡單,其結構spinlock_t,對於自旋鎖的操作,根據對臨界區的不會要求級別,有多種api可以選擇

static inline void spin_lock(spinlock_t *lock

)static inline void spin_unlock(spinlock_t *lock

)static inline void spin_lock_bh(spinlock_t *lock

)static inline void spin_unlock_bh(spinlock_t *lock

)static inline void spin_lock_irq(spinlock_t *lock

)static inline void spin_unlock_irq(spinlock_t *lock)

前面最基礎的還是spin_lock,用以獲取自旋鎖,在具體獲取之前會呼叫preempt_disable禁止核心搶占,所以自旋鎖保護的臨界**執行期間會不會被排程。本局臨界**的性質,可以呼叫spin_lock_bh禁止軟中斷或者通過呼叫spin_lock_irq禁止本地cpu的中斷。有自旋鎖保護的**不能進入睡眠狀態,因為等待獲取鎖的cpu會一直輪詢,不做其他事情,如果在臨界區內睡眠,則對cpu效能耗能較大。

通過上面函式獲取鎖和釋放鎖主要用於對臨界**的保護,操作本身是乙個原子操作。

對於資料結構的保護,自旋鎖往往作為乙個字段嵌入到資料結構中,在操作具體的結構之前,需要獲取鎖,操作完畢釋放鎖。

讀寫者鎖

讀寫者問題其實就是針對讀寫操作分別做的處理,可以看到其他的同步機制沒有區分讀寫操作,只要是執行緒訪問,就需要加鎖,但是很多資源在不是寫操作的情況下,是可以允許多程序訪問的。因此為了提高效率,讀寫者鎖就應運而生。讀寫者鎖在執行寫操作時,需要加writelock,此時只有乙個執行緒可以進入臨界區,而在執行讀操作時,加readlock,此時可以允許多個執行緒進入臨界區。適用於讀操作明顯多於寫操作的臨界區。

rcu機制

rcu機制是一種較新的核心同步機制,可以提供兩種型別的保護:對資料結構和對鍊錶。在核心中應用的相當頻繁。

rcu機制使用條件:

rcu保護的資料結構,不能反引用其指標,即不能*ptr獲取其內容,必須使用其對應的api。同時反引用指標並使用其結果的**,必須使用rcu_read_lock()和rcu_read_unlock()保護起來。

如果要修改ptr指向的物件,需要先建立乙個副本,然後呼叫rcu_assign_pointer(ptr,new_ptr)進行修改。所以這種情況,受保護的資料結構允許讀寫併發執行,因為實質上是操作兩個結構,只有在對舊的資料結構訪問完成後,才會修改指標指向。

記憶體和優化屏障

在看核心原始碼的時候經常看見有barrier()的出現,相當於一堵牆,讓編譯器在處理完屏障之前的**之前,不會處理屏障後面的**。原來為了提高**的執行效率,編譯器都會適當的對**進行指令重排,一般情況下這種重排不會影響程式功能,但是編譯器畢竟不是人,某些對順序有嚴格要求的**,很可能無法被編譯器準確識別,比如關閉和啟用搶占的**,這樣,如果編譯器把核心**移出關閉搶占區間,那麼很可能影響最終結果,因此,這種時候在關閉搶占後應該加上記憶體屏障,保障不會把後面的**排到前面來。

linux同步機制

一.併發控制 1 自旋鎖 得不到資源,會原地打轉,直到獲得資源為止 定義自旋鎖 spinlock t spin 初始化自旋鎖 spin lock init lock 獲得自旋鎖 spin lock lock 獲得自旋鎖,如果能立即獲得,則馬上返回,否則自旋在那裡,直到該自旋鎖的保持者釋放 spin ...

linux同步機制

原子操作 原子操作是由編譯器來保證的,保證乙個執行緒對資料的操作不會被其他執行緒打斷。當執行緒正在對乙個變數操作而這個操作過程不想被其他執行緒打斷時,可以用原子操作,原子操作結構體 atomic t 原子操作缺點 會阻塞優先順序很高的執行緒。自旋鎖 當乙個執行緒在讀寫乙個共享資源時,加上自旋鎖,其他...

linux同步機制

1 自旋鎖 獲得自旋鎖之後禁止核心搶占,但可以被中斷上半部打斷。執行於中斷上下文 單cpu不可搶占核心 空操作 單cpu可搶占核心 禁止核心搶占,不發生自旋 多cpu可搶占核心 禁止核心搶占 自旋 2 互斥鎖 核心可以搶占,可以被其他程序搶占,執行於程序上下文 3 讀寫鎖 4 順序鎖 對讀寫鎖的一種...