《Linux核心設計與實現》讀書筆記(8)— 核心同步方法(2)

來源:互聯網
上載者:User

4.訊號量

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

    訊號量和自旋鎖在使用上的差異:

    1)由於爭用訊號量的過程在等待鎖重新變為可用時會睡眠,所以訊號量適用於鎖會被長時間持有的情況;相反,鎖被短時間持有時,使用訊號量就不太適宜了。因為睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖被佔用的全部時間還要長。

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

    3)你可以在持有訊號量時去睡眠,因為當其他進程試圖獲得同一訊號量時不會因此而死結(因為該進程也只是去睡眠而已,最終會繼續執行的)。

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

    5)訊號量同時允許任意數量的鎖持有人,而自旋鎖在一個時刻最多允許一個任務持有它。

 

    靜態聲明訊號量

    static DECLARE_SEMAPHORE_GENRIC( name, count );

    name 是訊號量變數名,count 是訊號量的使用者數量,建立互斥訊號量也可以使用:

    static DECLARE_MUTEX( name );

 

    動態聲明訊號量

    sema_init( sem, count );

    sem 為結構指標,count 是使用者數量,初始化一個互斥訊號量也可以使用:

    init_MUTEX( sem );

 

/* 定義並聲明一個訊號量,名字為 mr_sem,用於訊號量計數 */static DECLARE_MUTEX( mr_sem );/* 試圖擷取訊號量... */if ( down_interruptible( &mr_sem ) ) {    /* 訊號被接收,訊號量還未擷取 */}/* 臨界區...  *//* 釋放給定的訊號量 */up( &mr_sem );

    

5.讀-寫訊號量

    讀-寫訊號量在核心中是由 rw_semaphore 結構表示的,定義在檔案<linux/rwsem.h>中,通過以下語句建立讀-寫訊號量。

    靜態:static DECLARE_RWSEM( name );

    動態:init_rwsem( struct rw_semaphore *sem );

 

    所有的讀-寫訊號量都是互斥訊號量,只要沒有寫者,並發持有讀鎖的讀者鎖不限。相反,只有惟一的寫者(在沒有讀者時)可以獲得寫鎖。所有讀-寫鎖的睡眠都不會被訊號打斷,所以它只有一個版本的down操作。

static DECLARE_RWSEM( mr_rwsem );/* 試圖擷取訊號量用於讀... */down_read( &mr_rwsem );/* 臨界區(唯讀)...  *//* 釋放訊號量  */up_read( &mr_rwsem );/* 試圖擷取訊號量用於寫... */down_write( &mr_sem );/* 臨界區(讀和寫)... *//* 釋放訊號量 */up_write( &mr_sem );

    讀-寫訊號量也提供了 down_read_trylock 和 down_write_trylock方法。如果成功獲得了訊號量鎖,它們返回非0值;如果訊號量鎖被爭用,則返回0。要小心——這與普通訊號量的情形完全相反。

    讀-寫訊號量相比讀-寫自旋鎖多一種特有的操作,downgrade_writer。這個函數可以動態地將擷取的寫鎖轉換為讀鎖。

 

6.完成變數

    如果在核心中一個任務需要發出訊號通知另一任務發生了某個特定事件,利用完成變數(completion variable)是使兩個任務得以同步的簡單方法。如果一個任務要執行一些工作時,另一個任務就會在完成變數上等待。當這個任務完成工作後,會使用完成變數去喚醒在等待的任務。

    

 

7.Seq鎖

    這種鎖提供了一種很簡單的機制,用於讀寫共用資料。實現這種鎖主要依靠一個序列計數器。當有疑義的資料被寫入時,會得到一個鎖,並且序列值會增加。在讀取資料之前和之後,序號都被讀取。如果讀取的序號值相同,說明在讀操作進行的過程中沒有被寫操作打斷過。此外,如果讀取的值是偶數,那麼就表明寫操作沒有發生(要明白因為鎖的初值是0,所以寫鎖會使值成奇數,釋放的時候變成偶數)。

/* 定義一個seq鎖 */seqlock_t mr_seq_lock = SEQLOCK_UNLOCKED;write_seqlock( &mr_seq_lock );/* 寫鎖被擷取... */write_sequnlock( &mr_seq_lock );/* 在讀的情況,與自旋鎖有很大不同 */unsigned long seq;do {    seq = read_seqbegin( &mr_seq_lock );    /* 這裡讀資料... */} while ( read_seqretry( &mr_seq_lock, seq ) );

    在多個讀者和少數寫者共用一把鎖的時候,seq鎖有助於提供一種非常輕量級和具有可擴充性的外觀。但是 seq 鎖對寫者更有利,只要沒有其他寫者,寫鎖總是能夠被成功獲得。掛起的寫者會不斷地使得讀操作迴圈(前一個例子),直到不再有任何寫者持有鎖為止。

 

8.禁止搶佔

    由於核心是搶佔性的,核心中的進程在任何時刻都可能停下來以便另一個具有更高優先權的進程運行。這意味著一個任務與被搶佔的任務可能會在同一個臨界區內運行。為了避免這種情況,核心搶佔代碼使用自旋鎖作為非搶佔地區的標記。如果一個自旋鎖被持有,核心便不能進行搶佔。

    實際中,某些情況並不需要自旋鎖,但是仍然需要關閉核心搶佔。為瞭解決這個問題,可以通過 preempt_disable 禁止核心搶佔。這是一個可以嵌套調用的函數,可以調用任意次。每次調用都必須有一個相應的 preempt_enable 調用。當最後一次 preempt_enable 被調用後,核心搶佔才重新佔用。

preempt_disable();/* 搶佔被禁止... */preempt_enable();

    

    為了用更簡潔的方法解決每個處理器上的資料訪問問題,可以通過 get_cpu 獲得處理器編號,這個函數在返回當前處理器號前首先會關閉核心搶佔。

int cpu;/* 禁止核心搶佔,並將 cpu 設定為當前處理器 */cpu = get_cpu();/* 對每個處理器的資料進行操作... *//* 再給予核心搶佔性 */put_cpu();

 

9.順序和屏障

    編譯器和處理器為了提高效率,可能對讀和寫重新排序,幸好,所有可能重新排序和寫的提供了機器指令來確保順序要求,同樣也可以指示編譯器不要對給定點周圍的指令序列進行重新排序。這些確保順序的指令稱做屏障(barrier)。

    

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.