futex 手冊摘要


 

#include <linux/futex.h>

#include <sys/time.h>

int futex(int *uaddr, int futex_op, int val,

      const struct timespec *timeout, /* or: uint32_t val2 */

      int *uaddr2, int val3);

DESCRIPTION:

  futex()系統調用提供了一種方法用於等待某個特定條件的發生。一種典型的應用是在共享內存同步中作為阻塞裝置。當使用futex時,絕大部分同步操作都在用戶態完成,一個用戶態程序只有在有可能阻塞一段較長的時間等待條件的發生時才使用futex。其他的futex操作可以用來喚醒等待特定條件發生的任何進程或線程。

  一個futex是一個32位的值,它的地址通過futex()傳入,futexs在所有的平台上都是32位的(包括64位系統上),所有的futex操作都是在這個值上。為了在多個進程間共享futex,futex位於由mmap或者shmat創建的一塊共享內存上(這種情況下,futex值在不同的進程上可能位於不同的虛擬地址,但這些虛擬地址都指向相同的物理地址)。在多線程程序中,將futex值放到一個所有線程共享的全局變量中就可以了。

  當執行一個futex操作請求阻塞一個線程時,只有在 *uaddr == val 時,內核才會執行阻塞操作,這個操作中的所有步驟(1. 導入*uaddr的值; 2. 比較; 3.阻塞線程)將原子的執行,並且當其他線程在同一個futex值上並行操作時所有的步驟不會亂序。

  futex的一種使用方式是用來實現鎖,鎖的狀態(acquired or not acquired)可以用在共享內存中的原子標志表示。在非競爭的情況下,線程可以通過原子操作訪問和修改鎖的狀態(這些操作全部在用戶態操作,內核不會保存任何關於鎖的狀態)。從另一方面來說,另一個線程可能無法獲取鎖(因為鎖已經被某個線程獲取),這個線程將通過如下這種方式執行futex()等待操作:

atomic<int> lock; // lock : 0. 鎖未被獲取 1.鎖被獲取
futex(&lock, FUTEX_WAIT, 1, NULL, NULL, NULL);

  futex(FUTEX_WAIT)將會檢測lock的值,只有在等於1時才阻塞線程。當線程釋放鎖時,該線程必須首先重置鎖的狀態,然后執行futex操作喚醒阻塞在lock上的線程。

  注意使用futex並沒有顯示的初始化和銷毀操作,內核只有在一個指定的futex值上執行futex操作時(例如FUTEX_WAIT)才會維護futex數據。

 

Arguments(參數):

  uaddr指向futex值,在所有的平台上,futex值是一個4字節的整數並且必須4字節對齊。

  對於某些阻塞操作,timeout參數是一個指向timespec結構的指針,表明了操作的超時時間。然而,在其他的某些操作下,它的最低4字節被作為一個整數值,這個整數值的含義因futex操作的不同而不同,對於這些操作來說,內核會將timeout值轉換為unsigned long,然后轉換為uint32_t,在接下來的說明中,它將表示為val2。

  在需要的時候,uaddr2是指向第二個futex值的指針,val3的解釋將依賴於具體的操作。

Futex operations (操作) :

  futex_op參數包含兩個部分:1.操作類型 2.標志選項(影響操作行為)。

  FUTEX_PRIVATE_FLAG (since Linux 2.6.22)

    這個標志選項可以用在所有的futex操作中,它告訴內核這個futex是進程內的,不和其他進程共享(它只能用來同步同一個進程內的線程),這樣內核可以做一些額外的優化。

  FUTEX_CLOCK_REALTIME (since Linux 2.6.28)

    這個標志選項只能用在FUTEX_WAIT_BITSET FUTEX_WAIT_REQUEUE_PI 操作中,如果設置了這個標志選項,內核會將timeout視作基於CLOCK_REALTIME 的絕對時間。如果沒有設置這個標志,則內核將它視作基於CLOCK_MONOTONIC的相對時間。

 

  futex操作包含下面的幾種:

  FUTEX_WAIT (since Linux 2.6.0)

  本操作會檢查*uaddr是否等於val,如果相等的話,則睡眠等待在uaddr上的FUTEX_WAKE 操作,如果線程開始睡眠,則它被認為是這個futex值上一個等待者。如果兩者不相等,則操作失敗並返回 error EAGAIN。比較*uaddr和val的目的是為了避免丟失喚醒。

  如果timeout參數不為NULL,則它指定了等待的超時時間(根據CLOCK_MONOTONIC測量得出)。

 

  FUTEX_WAKE (since Linux 2.6.0)

  本操作會喚醒最多val個等待者,絕大部分情況下,val的值為1(只喚醒一個等待者)或INT_MAX(喚醒所有的等待者) 。注意沒有任何機制保證一定喚醒某些特定的等待者(比如被認為是高優先級的等待者)

  參數timeout, uaddr2, val3將被忽略。

 

  FUTEX_REQUEUE (since Linux 2.6.0)

  本操作和下面的 FUTEX_CMP_REQUEUE 完成相同的功能,除了不檢查val3 。(參數val3被忽略)

 

  FUTEX_CMP_REQUEUE (since Linux 2.6.7)

  本操作首先檢查*uaddr是否等於val3,如果不相等,則返回error EAGAIN。否則的話,將喚醒最多val個等待者,如果等待者的數量大於val,則剩下的等待者則從uaddr的等待隊列中移除,添加到uaddr2的等待隊列中。val2參數指定了移動到uaddr2的等待者的最大數量。

  val的典型值為0或1,指定為INT_MAX是沒有任何用處的,這將會使得FUTEX_CMP_REQUEUE 操作和 FUTEX_WAKE 操作相同。同樣,val2的值通常為1或INT_MAX,指定為0是沒有用的,這樣將使得 FUTEX_CMP_REQUEUE  操作和  FUTEX_WAIT 操作相同。

  FUTEX_CMP_REQUEUE 是用來代替 FUTEX_REQUEUE 的,兩者的不同點在於是否檢查uaddr的值,這個檢查操作可用來確保移除添加操作只在特定的條件下發生,這樣就避免了某些 race condition。

  FUTEX_CMP_REQUEUE 和 FUTEX_REQUEUE 都可以用來避免 FUTEX_WAKE 可能產生的“線程驚群”現象。考慮下面的情況,多個線程在等待B(使用futex實現的等待隊列):

lock(A)
while (!check_value(V)) {
      unlock(A);
      block_on(B);
      lock(A);
};
unlock(A);

  如果一個線程使用 FUTEX_WAKE 喚醒 B上的所有線程,則它們會全部嘗試獲取鎖 A,然而在這種情況下喚醒所有的線程時徒勞的,因為除了一個線程之外其他的線程又全部阻塞在 A 上,相比之下,REQUEUE 操作可以喚醒一個等待線程然后將剩下的等待線程移動到 A 下,當線程解鎖 A時其他的線程可以繼續執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM