<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      《Linux內(nèi)核設(shè)計與實現(xiàn)》讀書筆記(十)- 內(nèi)核同步方法

      內(nèi)核中提供了多種方法來防止競爭條件,理解了這些方法的使用場景有助于我們在編寫內(nèi)核代碼時選用合適的同步方法,

      從而即可保證代碼中臨界區(qū)的安全,同時也讓性能的損失降到最低。

      主要內(nèi)容:

      • 原子操作
      • 自旋鎖
      • 讀寫自旋鎖
      • 信號量
      • 讀寫信號量
      • 互斥體
      • 完成變量
      • 大內(nèi)核鎖
      • 順序鎖
      • 禁止搶占
      • 順序和屏障
      • 總結(jié)

       

      1. 原子操作

      原子操作是由編譯器來保證的,保證一個線程對數(shù)據(jù)的操作不會被其他線程打斷。

      原子操作有2類:

      1. 原子整數(shù)操作,有32位和64位。頭文件分別為<asm/atomic.h>和<asm/atomic64.h>
      2. 原子位操作。頭文件 <asm/bitops.h>

       

      原子操作的api很簡單,參見相應(yīng)的頭文件即可。

      原子操作頭文件與具體的體系結(jié)構(gòu)有關(guān),比如x86架構(gòu)的相關(guān)頭文件在 arch/x86/include/asm/*.h

       

      2. 自旋鎖

      原子操作只能用于臨界區(qū)只有一個變量的情況,實際應(yīng)用中,臨界區(qū)的情況要復(fù)雜的多。

      對于復(fù)雜的臨界區(qū),linux內(nèi)核中也提供了多種同步方法,自旋鎖就是其中一種。

       

      自旋鎖的特點就是當一個線程獲取了鎖之后,其他試圖獲取這個鎖的線程一直在循環(huán)等待獲取這個鎖,直至鎖重新可用。

      由于線程實在一直循環(huán)的獲取這個鎖,所以會造成CPU處理時間的浪費,因此最好將自旋鎖用于能很快處理完的臨界區(qū)。

       

      自旋鎖的實現(xiàn)與體系結(jié)構(gòu)有關(guān),所以相應(yīng)的頭文件 <asm/spinlock.h> 位于相關(guān)體系結(jié)構(gòu)的代碼中。

       

      自旋鎖使用時有2點需要注意:

      1. 自旋鎖是不可遞歸的,遞歸的請求同一個自旋鎖會自己鎖死自己。
      2. 線程獲取自旋鎖之前,要禁止當前處理器上的中斷。(防止獲取鎖的線程和中斷形成競爭條件)
          比如:當前線程獲取自旋鎖后,在臨界區(qū)中被中斷處理程序打斷,中斷處理程序正好也要獲取這個鎖,
          于是中斷處理程序會等待當前線程釋放鎖,而當前線程也在等待中斷執(zhí)行完后再執(zhí)行臨界區(qū)和釋放鎖的代碼。

       

      中斷處理下半部的操作中使用自旋鎖尤其需要小心:

      1. 下半部處理和進程上下文共享數(shù)據(jù)時,由于下半部的處理可以搶占進程上下文的代碼,
          所以進程上下文在對共享數(shù)據(jù)加鎖前要禁止下半部的執(zhí)行,解鎖時再允許下半部的執(zhí)行。
      2. 中斷處理程序(上半部)和下半部處理共享數(shù)據(jù)時,由于中斷處理(上半部)可以搶占下半部的執(zhí)行,
          所以下半部在對共享數(shù)據(jù)加鎖前要禁止中斷處理(上半部),解鎖時再允許中斷的執(zhí)行。
      3. 同一種tasklet不能同時運行,所以同類tasklet中的共享數(shù)據(jù)不需要保護。
      4. 不同類tasklet中共享數(shù)據(jù)時,其中一個tasklet獲得鎖后,不用禁止其他tasklet的執(zhí)行,因為同一個處理器上不會有tasklet相互搶占的情況
      5. 同類型或者非同類型的軟中斷在共享數(shù)據(jù)時,也不用禁止下半部,因為同一個處理器上不會有軟中斷互相搶占的情況

       

      自旋鎖方法列表如下:

      方法

      描述

      spin_lock() 獲取指定的自旋鎖
      spin_lock_irq() 禁止本地中斷并獲取指定的鎖
      spin_lock_irqsave() 保存本地中斷的當前狀態(tài),禁止本地中斷,并獲取指定的鎖
      spin_unlock() 釋放指定的鎖
      spin_unlock_irq() 釋放指定的鎖,并激活本地中斷
      spin_unlock_irqstore() 釋放指定的鎖,并讓本地中斷恢復(fù)到以前狀態(tài)
      spin_lock_init() 動態(tài)初始化指定的spinlock_t
      spin_trylock() 試圖獲取指定的鎖,如果未獲取,則返回0
      spin_is_locked() 如果指定的鎖當前正在被獲取,則返回非0,否則返回0

       

      3. 讀寫自旋鎖

      1. 讀寫自旋鎖除了和普通自旋鎖一樣有自旋特性以外,還有以下特點:
        讀鎖之間是共享的
          即一個線程持有了讀鎖之后,其他線程也可以以讀的方式持有這個鎖
      2. 寫鎖之間是互斥的
          即一個線程持有了寫鎖之后,其他線程不能以讀或者寫的方式持有這個鎖
      3. 讀寫鎖之間是互斥的
          即一個線程持有了讀鎖之后,其他線程不能以寫的方式持有這個鎖

       

      :讀寫鎖要分別使用,不能混合使用,否則會造成死鎖。

      正常的使用方法:

      DEFINE_RWLOCK(mr_rwlock);
      
      read_lock(&mr_rwlock);
      /* 臨界區(qū)(只讀).... */
      read_unlock(&mr_rwlock);
      
      write_lock(&mr_lock);
      /* 臨界區(qū)(讀寫)... */
      write_unlock(&mr_lock);

      混合使用時:

      /* 獲取一個讀鎖 */
      read_lock(&mr_lock);
      /* 在獲取寫鎖的時候,由于讀寫鎖之間是互斥的,
       * 所以寫鎖會一直自旋等待讀鎖的釋放,
       * 而此時讀鎖也在等待寫鎖獲取完成后繼續(xù)下面的代碼。
       * 因此造成了讀寫鎖的互相等待,形成了死鎖。
       */
      write_lock(&mr_lock);

       

      讀寫鎖相關(guān)文件參照 各個體系結(jié)構(gòu)中的 <asm/rwlock.h>

      讀寫鎖的相關(guān)函數(shù)如下:

      方法

      描述

      read_lock() 獲取指定的讀鎖
      read_lock_irq() 禁止本地中斷并獲得指定讀鎖
      read_lock_irqsave() 存儲本地中斷的當前狀態(tài),禁止本地中斷并獲得指定讀鎖
      read_unlock() 釋放指定的讀鎖
      read_unlock_irq() 釋放指定的讀鎖并激活本地中斷
      read_unlock_irqrestore() 釋放指定的讀鎖并將本地中斷恢復(fù)到指定前的狀態(tài)
      write_lock() 獲得指定的寫鎖
      write_lock_irq() 禁止本地中斷并獲得指定寫鎖
      write_lock_irqsave() 存儲本地中斷的當前狀態(tài),禁止本地中斷并獲得指定寫鎖
      write_unlock() 釋放指定的寫鎖
      write_unlock_irq() 釋放指定的寫鎖并激活本地中斷
      write_unlock_irqrestore() 釋放指定的寫鎖并將本地中斷恢復(fù)到指定前的狀態(tài)
      write_trylock() 試圖獲得指定的寫鎖;如果寫鎖不可用,返回非0值
      rwlock_init() 初始化指定的rwlock_t

       

      4. 信號量

      信號量也是一種鎖,和自旋鎖不同的是,線程獲取不到信號量的時候,不會像自旋鎖一樣循環(huán)的去試圖獲取鎖,

      而是進入睡眠,直至有信號量釋放出來時,才會喚醒睡眠的線程,進入臨界區(qū)執(zhí)行。

       

      由于使用信號量時,線程會睡眠,所以等待的過程不會占用CPU時間。所以信號量適用于等待時間較長的臨界區(qū)。

      信號量消耗的CPU時間的地方在于使線程睡眠和喚醒線程,

      如果 (使線程睡眠 + 喚醒線程)的CPU時間 > 線程自旋等待的CPU時間,那么可以考慮使用自旋鎖。

       

      信號量有二值信號量和計數(shù)信號量2種,其中二值信號量比較常用。

      二值信號量表示信號量只有2個值,即0和1。信號量為1時,表示臨界區(qū)可用,信號量為0時,表示臨界區(qū)不可訪問。

      二值信號量表面看和自旋鎖很相似,區(qū)別在于爭用自旋鎖的線程會一直循環(huán)嘗試獲取自旋鎖,

      而爭用信號量的線程在信號量為0時,會進入睡眠,信號量可用時再被喚醒。

       

      計數(shù)信號量有個計數(shù)值,比如計數(shù)值為5,表示同時可以有5個線程訪問臨界區(qū)。

       

      信號量相關(guān)函數(shù)參照: <linux/semaphore.h> 實現(xiàn)方法參照:kernel/semaphore.c

      使用信號量的方法如下:

      /* 定義并聲明一個信號量,名字為mr_sem,用于信號量計數(shù) */
      static DECLARE_MUTEX(mr_sem);
      
      /* 試圖獲取信號量...., 信號未獲取成功時,進入睡眠
       * 此時,線程狀態(tài)為 TASK_INTERRUPTIBLE
       */
      down_interruptible(&mr_sem);
      /* 這里也可以用:
       * down(&mr_sem);
       * 這個方法把線程狀態(tài)置為 TASK_UNINTERRUPTIBLE 后睡眠
       */
      
      /* 臨界區(qū) ... */
      
      /* 釋放給定的信號量 */
      up(&mr_sem);

       

      一般用的比較多的是down_interruptible()方法,因為以 TASK_UNINTERRUPTIBLE 方式睡眠無法被信號喚醒。

      對于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 補充說明一下:

      • TASK_INTERRUPTIBLE - 可打斷睡眠,可以接受信號并被喚醒,也可以在等待條件全部達成后被顯式喚醒(比如wake_up()函數(shù))。
      • TASK_UNINTERRUPTIBLE - 不可打斷睡眠,只能在等待條件全部達成后被顯式喚醒(比如wake_up()函數(shù))。

       

      信號量方法如下:

      方法

      描述

      sema_init(struct semaphore *, int) 以指定的計數(shù)值初始化動態(tài)創(chuàng)建的信號量
      init_MUTEX(struct semaphore *) 以計數(shù)值1初始化動態(tài)創(chuàng)建的信號量
      init_MUTEX_LOCKED(struct semaphore *) 以計數(shù)值0初始化動態(tài)創(chuàng)建的信號量(初始為加鎖狀態(tài))
      down_interruptible(struct semaphore *) 以試圖獲得指定的信號量,如果信號量已被爭用,則進入可中斷睡眠狀態(tài)
      down(struct semaphore *) 以試圖獲得指定的信號量,如果信號量已被爭用,則進入不可中斷睡眠狀態(tài)
      down_trylock(struct semaphore *) 以試圖獲得指定的信號量,如果信號量已被爭用,則立即返回非0值
      up(struct semaphore *) 以釋放指定的信號量,如果睡眠隊列不空,則喚醒其中一個任務(wù)

       

      信號量結(jié)構(gòu)體具體如下:

      /* Please don't access any members of this structure directly */
      struct semaphore {
          spinlock_t        lock;
          unsigned int        count;
          struct list_head    wait_list;
      };

      可以發(fā)現(xiàn)信號量結(jié)構(gòu)體中有個自旋鎖,這個自旋鎖的作用是保證信號量的down和up等操作不會被中斷處理程序打斷。

       

      5. 讀寫信號量

      讀寫信號量和信號量之間的關(guān)系 與 讀寫自旋鎖和普通自旋鎖之間的關(guān)系 差不多。

      讀寫信號量都是二值信號量,即計數(shù)值最大為1,增加讀者時,計數(shù)器不變,增加寫者,計數(shù)器才減一。

      也就是說讀寫信號量保護的臨界區(qū),最多只有一個寫者,但可以有多個讀者。

       

      讀寫信號量的相關(guān)內(nèi)容參見:<asm/rwsem.h> 具體實現(xiàn)與硬件體系結(jié)構(gòu)有關(guān)。

       

      6. 互斥體

      互斥體也是一種可以睡眠的鎖,相當于二值信號量,只是提供的API更加簡單,使用的場景也更嚴格一些,如下所示:

      1. mutex的計數(shù)值只能為1,也就是最多只允許一個線程訪問臨界區(qū)
      2. 在同一個上下文中上鎖和解鎖
      3. 不能遞歸的上鎖和解鎖
      4. 持有個mutex時,進程不能退出
      5. mutex不能在中斷或者下半部中使用,也就是mutex只能在進程上下文中使用
      6. mutex只能通過官方API來管理,不能自己寫代碼操作它

       

      在面對互斥體和信號量的選擇時,只要滿足互斥體的使用場景就盡量優(yōu)先使用互斥體。

      在面對互斥體和自旋鎖的選擇時,參見下表:

      需求

      建議的加鎖方法

      低開銷加鎖 優(yōu)先使用自旋鎖
      短期鎖定 優(yōu)先使用自旋鎖
      長期加鎖 優(yōu)先使用互斥體
      中斷上下文中加鎖 使用自旋鎖
      持有鎖需要睡眠 使用互斥體

       

      互斥體頭文件:<linux/mutex.h>

      常用的互斥體方法如下:

      方法

      描述

      mutex_lock(struct mutex *) 為指定的mutex上鎖,如果鎖不可用則睡眠
      mutex_unlock(struct mutex *) 為指定的mutex解鎖
      mutex_trylock(struct mutex *) 試圖獲取指定的mutex,如果成功則返回1;否則鎖被獲取,返回0
      mutex_is_locked(struct mutex *) 如果鎖已被爭用,則返回1;否則返回0

       

      7. 完成變量

      完成變量的機制類似于信號量,

      比如一個線程A進入臨界區(qū)之后,另一個線程B會在完成變量上等待,線程A完成了任務(wù)出了臨界區(qū)之后,使用完成變量來喚醒線程B。

       

      完成變量的頭文件:<linux/completion.h>

      完成變量的API也很簡單:

      方法

      描述

      init_completion(struct completion *) 初始化指定的動態(tài)創(chuàng)建的完成變量
      wait_for_completion(struct completion *) 等待指定的完成變量接受信號
      complete(struct completion *) 發(fā)信號喚醒任何等待任務(wù)

      使用完成變量的例子可以參考:kernel/sched.c 和 kernel/fork.c

      一般在2個任務(wù)需要簡單同步的情況下,可以考慮使用完成變量。

       

      8. 大內(nèi)核鎖

      大內(nèi)核鎖已經(jīng)不再使用,只存在與一些遺留的代碼中。

       

      9. 順序鎖

      順序鎖為讀寫共享數(shù)據(jù)提供了一種簡單的實現(xiàn)機制。

      之前提到的讀寫自旋鎖和讀寫信號量,在讀鎖被獲取之后,寫鎖是不能再被獲取的,

      也就是說,必須等所有的讀鎖釋放后,才能對臨界區(qū)進行寫入操作。

       

      順序鎖則與之不同,讀鎖被獲取的情況下,寫鎖仍然可以被獲取。

      使用順序鎖的讀操作在讀之前和讀之后都會檢查順序鎖的序列值,如果前后值不符,則說明在讀的過程中有寫的操作發(fā)生,

      那么讀操作會重新執(zhí)行一次,直至讀前后的序列值是一樣的。

      do
      {
          /* 讀之前獲取 順序鎖foo 的序列值 */
          seq = read_seqbegin(&foo);
      ...
      } while(read_seqretry(&foo, seq)); /* 順序鎖foo此時的序列值!=seq 時返回true,反之返回false */

      順序鎖優(yōu)先保證寫鎖的可用,所以適用于那些讀者很多,寫者很少,且寫優(yōu)于讀的場景。

      順序鎖的使用例子可以參考:kernel/timer.c和kernel/time/tick-common.c文件

       

      10. 禁止搶占

      其實使用自旋鎖已經(jīng)可以防止內(nèi)核搶占了,但是有時候僅僅需要禁止內(nèi)核搶占,不需要像自旋鎖那樣連中斷都屏蔽掉。

      這時候就需要使用禁止內(nèi)核搶占的方法了:

      方法

      描述

      preempt_disable() 增加搶占計數(shù)值,從而禁止內(nèi)核搶占
      preempt_enable() 減少搶占計算,并當該值降為0時檢查和執(zhí)行被掛起的需調(diào)度的任務(wù)
      preempt_enable_no_resched() 激活內(nèi)核搶占但不再檢查任何被掛起的需調(diào)度的任務(wù)
      preempt_count() 返回搶占計數(shù)

      這里的preempt_disable()和preempt_enable()是可以嵌套調(diào)用的,disable和enable的次數(shù)最終應(yīng)該是一樣的。

      禁止搶占的頭文件參見:<linux/preempt.h>

       

      11. 順序和屏障

      對于一段代碼,編譯器或者處理器在編譯和執(zhí)行時可能會對執(zhí)行順序進行一些優(yōu)化,從而使得代碼的執(zhí)行順序和我們寫的代碼有些區(qū)別。

      一般情況下,這沒有什么問題,但是在并發(fā)條件下,可能會出現(xiàn)取得的值與預(yù)期不一致的情況

       

      比如下面的代碼:

      /* 
       * 線程A和線程B共享的變量 a和b
       * 初始值 a=1, b=2
       */
      int a = 1, b = 2;
      
      /*
       * 假設(shè)線程A 中對 a和b的操作
       */
      void Thread_A()
      {
          a = 5;
          b = 4;
      }
      
      /*
       * 假設(shè)線程B 中對 a和b的操作
       */
      void Thread_B()
      {
          if (b == 4)
              printf("a = %d\n", a);
      }

      由于編譯器或者處理器的優(yōu)化,線程A中的賦值順序可能是b先賦值后,a才被賦值。

      所以如果線程A中 b=4; 執(zhí)行完,a=5; 還沒有執(zhí)行的時候,線程B開始執(zhí)行,那么線程B打印的是a的初始值1。

      這就與我們預(yù)期的不一致了,我們預(yù)期的是a在b之前賦值,所以線程B要么不打印內(nèi)容,如果打印的話,a的值應(yīng)該是5。

       

      在某些并發(fā)情況下,為了保證代碼的執(zhí)行順序,引入了一系列屏障方法來阻止編譯器和處理器的優(yōu)化。

      方法

      描述

      rmb() 阻止跨越屏障的載入動作發(fā)生重排序
      read_barrier_depends() 阻止跨越屏障的具有數(shù)據(jù)依賴關(guān)系的載入動作重排序
      wmb() 阻止跨越屏障的存儲動作發(fā)生重排序
      mb() 阻止跨越屏障的載入和存儲動作重新排序
      smp_rmb() 在SMP上提供rmb()功能,在UP上提供barrier()功能
      smp_read_barrier_depends() 在SMP上提供read_barrier_depends()功能,在UP上提供barrier()功能
      smp_wmb() 在SMP上提供wmb()功能,在UP上提供barrier()功能
      smp_mb() 在SMP上提供mb()功能,在UP上提供barrier()功能
      barrier() 阻止編譯器跨越屏障對載入或存儲操作進行優(yōu)化

       

      為了使得上面的小例子能正確執(zhí)行,用上表中的函數(shù)修改線程A的函數(shù)即可:

      /*
       * 假設(shè)線程A 中對 a和b的操作
       */
      void Thread_A()
      {
          a = 5;
          mb(); 
          /* 
           * mb()保證在對b進行載入和存儲值(值就是4)的操作之前
           * mb()代碼之前的所有載入和存儲值的操作全部完成(即 a = 5;已經(jīng)完成)
           * 只要保證a的賦值在b的賦值之前進行,那么線程B的執(zhí)行結(jié)果就和預(yù)期一樣了
           */
          b = 4;
      }

       

      12. 總結(jié)

      本節(jié)討論了大約11種內(nèi)核同步方法,除了大內(nèi)核鎖已經(jīng)不再推薦使用之外,其他各種鎖都有其適用的場景。

      了解了各種同步方法的適用場景,才能正確的使用它們,使我們的代碼在安全的保障下達到最優(yōu)的性能。

       

      同步的目的就是為了保障數(shù)據(jù)的安全,其實就是保障各個線程之間共享資源的安全,下面根據(jù)共享資源的情況來討論一下10種同步方法的選擇。

      10種同步方法在圖中分別用藍色框標出。

      locks

      posted @ 2013-05-01 11:16  wang_yb  閱讀(14558)  評論(9)    收藏  舉報
      主站蜘蛛池模板: 被黑人巨大一区二区三区| gogogo高清在线播放免费| 一区二区三区鲁丝不卡| 一本久道久久综合中文字幕| 亚洲精品无码久久千人斩| 国产成人精品无码专区| 五月综合网亚洲乱妇久久| 国产精品午夜福利免费看| 成人精品视频一区二区三区尤物 | 国产亚洲精品AA片在线播放天 | 国产av国片精品一区二区| 久久综合伊人77777| 久久综合97丁香色香蕉| 欧美成人片在线观看| 国产二区三区不卡免费| 精品国产亚洲av麻豆特色| 亚洲av无码专区在线亚| 国产成人无码免费视频在线| 亚洲AV无码不卡在线播放| 日本一区二区三区专线| 免费无遮挡无码永久在线观看视频| 精品国产美女av久久久久| 国产成人高清亚洲一区二区| 久青草久青草视频在线观看| 亚洲男人第一无码av网站| 人妻va精品va欧美va| 国产农村妇女高潮大叫| 亚洲欧洲日产国码无码久久99| 亚洲熟少妇一区二区三区| 亚洲综合网中文字幕在线| 91久久精品美女高潮不断| 狠狠做五月深爱婷婷天天综合 | 国产一二三五区不在卡| 国产午夜精品理论大片| 又大又长粗又爽又黄少妇毛片| 韩国19禁无遮挡啪啪无码网站| 欧美黑人又粗又大又爽免费 | 麻豆一区二区三区蜜桃免费| 中文字幕在线观看一区二区| 日日猛噜噜狠狠扒开双腿小说 | 韩国无码AV片午夜福利|