歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux操作系統內核學習之同步

Linux操作系統內核學習之同步

日期:2017/2/25 10:37:07   编辑:Linux教程

  [臨界區和競爭條件]

  所謂臨界區就是訪問和操作共享數據的代碼段。多個執行線程並發訪問同一個資源通常是不安全的,為了避免在臨界區中並發訪問,coder必須保證這些代碼原子執行。

  如果兩個執行線程有可能處於同一個臨界區中同時執行,那麼這就是程序包含的一個bug.如果這種情況確實發生了,我們就稱它是競爭條件(race conditions)。避免並發和防止競爭條件稱為同步(synchronization)。

  [造成並發執行的原因]

  用戶空間之所以需要同步,是因為用戶程序會被調度程序搶占和重新調度。在內核中有類似可能造成並發執行的原因:

  中斷:中斷幾乎可以在任何時刻異步發生,也就是隨時打斷當前正在執行的代碼;

  軟中斷和tasklet:內核能在任何時刻喚醒或調度軟中斷和tasklet,打斷當前正在執行的代碼;

  內核搶占:因為內核具有搶占性,所以內核中的任務可能會被另一任務搶占;

  睡眠及與用戶空間的同步:在內核執行的進程可能會睡眠,這就會喚醒調度程序,從而導致調度一個新的用戶進程執行;

  對稱處理器:兩個或多個處理器可以同時執行代碼。

  [哪些代碼需要同步]

  我們在編寫內核代碼時,你要問自己下面這些問題:

  這個數據是不是全局的?除了當前線程外,其他線程能不能訪問它?

  這個數據會不會在進程上下文和中斷上下文種共享?它是不是要在兩個不同的中斷處理程序中共享?

  進程在訪問數據時可不可能被搶占?被調度的新程序會不會訪問同一數據?

  當前進程是不是會睡眠(阻塞)在某些資源上,如果是,它會讓共享數據處於何種狀態?

  怎樣防止數據失控?

  如果這個函數又在另一個處理上被調度將會發生什麼呢?

  如何確保代碼遠離並發威脅呢?

  簡而言之,幾乎訪問所有的內核全局變量和共享數據都需要某種形式的同步方法。

  [死鎖]

  死鎖的產生需要一定的條件:要一個或多個執行線程和一個或多個資源,每個線程都在等待其中的一個資源,但所有的資源都已經被占用了。所有線程都在相互等待,但它們永遠不會釋放已經占有的資源,於是任何資源都無法繼續,這就意味著死鎖的發生。

  Example:有兩個線程和兩把鎖

  線程1 線程2

  獲得鎖A 獲得鎖B

  試圖獲得鎖B 試圖獲得鎖A

  等待鎖B 等待鎖A

  [原子操作]

  原子操作可以保證指令以原子的方式執行-執行過程不被打斷。內核提供了兩組原子操作接口:一組針對整數進行操作,另一組針對單獨的位進行操作。

  原子整數類型

  view plain typedef struct { intcounter;} atomic_t;[自旋鎖]

  自旋鎖(spin lock)最多只能被一個可執行線程持有。如果一個執行線程試圖獲得一個被已經持有(即所謂的爭用)的自旋鎖,那麼該線程就會一直進行忙循環-旋轉-等待鎖重新可用。

  spinlock結構體:

  view plain typedef struct spinlock { union { struct raw_spinlock rlock;

  #ifdef CONFIG_DEBUG_LOCK_ALLOC # define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))

  struct { u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};#endif };} spinlock_t;一個被爭用的自旋鎖是的請求它的線程在等待鎖重新可用時自旋,特別浪費處理器時間,這種行為是自旋鎖的要點。所以自旋鎖不應該被長時間持有。持有自旋鎖的時間最好小於完成兩次上下文切換的耗時,也就是兩次調用schedule()的時間。

  自旋鎖可以用於中斷處理程序中,但是信號量不可以,信號量會導致睡眠。

  使用鎖的時候一定要對症下藥,要有針對性。要知道需要保護的是數據而不是代碼。

  [信號量]

  Linux中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個不可用(已經被占用)的信號量時,信號量會將其推進一個等待隊列中,然後讓其睡眠。這時處理器能重獲自由,從而去執行其他代碼。當持有的信號量可用(被釋放)後,處於等待列隊中的那個任務將被喚醒,並獲得該信號量。

  semaphore結構體:

  view plain struct semaphore { spinlock_t lock;unsigned int count;struct list_head wait_list;};使用信號量應注意的地方:

  由於爭用信號量的進程在等待鎖重新變為可用時會睡眠,所以信號量適合用於鎖會被長時間持有的情況;

  相反,鎖被短時間持有時,使用信號量就不能太適合了。因為睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖被占用的全部時間還要長;

  由於執行線程在鎖被爭用時會睡眠,所以只能在進程上下文中才能獲取信號量鎖,因為在中斷上下文是不能進行調度的;

  你可以在持有信號量時去睡眠,因為當其他進程試圖獲得同一信號量時不會因此而死鎖。

  在你占用信號量的同時不能占用自旋鎖。因為在你等待信號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。

  [互斥體]

  Linux最新的linux內核中,互斥體mutex是一種實現互斥的特定睡眠鎖。Mutex在內核中對應數據結構mutex,其行為和使用計數為1的信號量類似,但操作接口更簡單,實現也更高效,而且使用限制更強。

  mutex結構體:

  view plain struct mutex { /* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;spinlock_t wait_lock;struct list_head wait_list;#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)

  struct task_struct *owner;#endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;void *magic;#endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map;#endif };Mutex使用限制:

  任何時候中只有一個任務可以持有mutex,也就是說,mutex的使用計數永遠是1;

  給mutex上鎖者必須負責給其再解鎖,你不能在一個上下文中鎖定一個mutex,而在另一個上下文中給它解鎖。這個限制使得mutex不合適內核同用戶空間復雜的同步場景。最常使用的方式是:在同一上下文中上鎖和解鎖。

  遞歸地上鎖和解鎖是不允許的。也就是說,你不能遞歸地持有同一個鎖,同樣你也不能再去解鎖一個已經被解開的mutex;

  Mutex不能在中斷或者下半部中使用;

  Mutex只能通過官方API管理

  [信號量和互斥體]

  互斥鎖和信號量很相似,內核中兩者共存會令人混淆。所幸,它們的標准使用方式都有很簡單的規范:除非mutex的某個約束妨礙你使用,否則相比信號量要優先使用mutex.當你寫新代碼時,只有碰到特殊場合才會需要使用信號量。因此建議首選mutex.

  [自旋鎖和互斥體]中華考試網

  了解何時使用自旋鎖,何時使用互斥體或者信號量對編寫優良代碼很重要,但是多數情況下,並不需要太多的考慮,因為在中斷上下文中只能使用自旋鎖,而在任務睡眠時只能使用互斥體。

Copyright © Linux教程網 All Rights Reserved