futex 設計成用戶空間快速鎖操作,由用戶空間實現fastpath,以及內核提供鎖競爭排隊仲裁服務,由用戶空間使用futex系統調用來實現slowpath。futex系統調用提供了三種配對的調用接口,滿足不同使用場合的,分別為noraml futex,pi-futex,以及 requeue-pi。
futex的同步(鎖)狀態定義由用戶空間去執行,futex系統調用並不需要理解用戶空間是如何定義和使用這個地址對齊的4字節長的整型的futex,但是pi-futex除外,用戶空間必須使用futex系統調用定義的鎖規則。用戶空間通過總線鎖原子訪問這個整型futex,進行狀態的修改,上鎖,解鎖和鎖競爭等。當用戶空間發現futex進入了某種定義需要排隊服務的狀態時,用戶空間就需要使用futex系統調用進行排隊,待排隊喚醒后再回到用戶空間再次進行上鎖等操作。當鎖競爭時,每次的Lock和Unlock,都必需先后進行用戶空間的鎖操作和futex系統調用,並且兩步並非原子性執行的,Lock和Unlock的執行過程可能會發生亂序。
這是我們希望的
| task A | futex in user | futex queue in kerenl | task B |
| owned | empty | 1. own futex | |
| 1.try lock (嘗試修改futex) | empty | ||
| 2.mark waiter (發現鎖競爭,修改futex狀態) | owned -> waiters | empty | |
| 3.futex_wait | 0 | empty | 2. unlock (修改futex,得到舊狀態為waiters) |
| 4. enqueue | 0 | has waiter | 3. futex_wake (發現有鎖競爭) |
| 5. sleep and schedule | 0 | has waiter | 4. dequeue |
| 0 | empty | 5. wakeup | |
| 6. wokenup | 0 | empty | |
| 7.try lock again (被喚醒后,並不知道還有沒有其它任務在等待, 所以鎖競爭狀態來上鎖,以確保自己unlock時進行slowpath, 進行內核檢查有沒有其它等待的任務) |
0 -> waiters | empty | |
| 8. own futex | waiters | empty | |
| 9. unlock (approach to slowpath) | waiters |
但是總會發生我們不希望的情況,雖然總線鎖原子操作使得Lock和Unlock的用戶空間階段的操作以Lock為先,讓futex進行鎖競爭狀態,使得Lock和Unlock都要進行slowpath。然而,在它們各自調用futex系統調用時,執行futex_wait的cpu被中斷了,futex_wake先於futex_wait執行了。futex_wake發現沒有可喚醒的任務就離開了。然后遲到的futex_wait卻一無所知,毅然排隊等待在一個已經釋放的鎖。這樣一來,如果這個鎖將來不發生鎖競爭,那么task A就不會被喚醒而被遺忘。
| task A | futex in user | futex queue in kerenl | task B |
| owned | empty | 1. own futex | |
| 1.try lock (嘗試修改futex) | empty | ||
| 2.mark waiter (發現鎖競爭,修改futex狀態) | owned | waiters | empty | |
| 3.futex_wait | 0 | empty | 2. unlock (修改futex,得到舊狀態為owned | waiters) |
| interupted | 0 | empty | 3. futex_wake (發現有鎖競爭) |
| interupted | 0 | empty | 4. quit |
| 4. enqueue | 0 | has waiter | |
| 5. sleep and schedule | 0 | has waiter | |
所以需要進行排隊等待的futex系統調用,都要求將futex當前的副本作為參數傳入,futex系統調用在執行排隊之前都通過副本和用戶空間的futex最新值進行對比,決定是否要返回用戶空間,讓用戶空間重新判斷。對於pi-futex的futex_lock_pi系統調用操作入口,並不需要用戶空間傳入當前futex的副本,是因為用戶空間必須使用由futex系統調用對pi-futex的鎖規則,futex_lock_pi 函數則以pi-futex的鎖規則來判斷pi-futex是否被釋放。當一個用戶空間的futex遵照futex.h對pi-futex鎖狀態規則,並使用futex系統調用的futex_lock_pi和futex_unlock_pi操作,這個futex就是一個pi-futex。
futex系統調用配對的操作入口:
1. normal futex:
static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset)
static int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
2. pi-futex:
static int futex_lock_pi(u32 __user *uaddr, unsigned int flags, int detect, ktime_t *time, int trylock)
static int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
3. requeue-pi:
static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset, u32 __user *uaddr2)
static int futex_requeue(u32 __user *uaddr1, unsigned int flags, u32 __user *uaddr2, int nr_wake, int nr_requeue, u32 *cmpval, int requeue_pi)
4. robust-futex:
SYSCALL_DEFINE3(get_robust_list, int, pid, struct robust_list_head __user * __user *, head_ptr, size_t __user *, len_ptr)
SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head, size_t, len)
futex_wait 應用於non-pi futex,futex的規則由用戶空間定義,要求用戶空間將non-pi futex副本值傳入來,過濾工作是由 futex_wait_setup子函數完成,再由 futex_wait_queue_me子函數進行non-pi futex的排隊和睡眠等待。
futex_wait_requeue_pi 整合了對futex從non-pi到pi的requeue,以及non-pi到non-pi的requeue。但它首先是對non-pi的futex進行futex_wait,所以它和futex_wait 一樣要求用戶空間將non-pi futex副本值傳入來。所以futex_wait_requeue_pi 如其名字一樣,拆分成兩個階段,或者說組合了兩種操作,futex_wait和 requeue_pi 。先進行futex_wait ,待被futex_requeue 喚醒后執行requeue_pi 。可以從代碼看到futex_wait_requeue_pi 前半段和 futex_wait 代碼流程是差不多的。
futex_lock_pi 應用於pi-futex,futex的規則由futex系統調用(頭文件)定義,用戶空間必須遵從規則來使用。由於規則是由內核定義的,並不要求用戶空間傳入一個futex當前副本,並且還會在內核中,在使用rt_mutex代理排隊等待之前,進行 futex_lock_pi_atomic上鎖的嘗試,失敗后才進入rt_mutex代理排隊等待。待排隊喚醒后,通過 fixup_owner 和 fixup_pi_state_owner 對用戶空間的pi-futex進行上鎖。這里有兩點注意,rt_mutex的上鎖規則是使用task_struct的指針標記,而pi-futex的上鎖規則是使用pid(tid)號標記。另外pi-futex的排隊也要注意,pi-futex雖然在rt_mutex代理上進行排隊,但是還要像non-pi futex一樣插入到futex_hash_bucket的鏈表中,為的不是排隊,而是讓后面進來的排隊,可以在futex_hash_bucket中找出futex_queue,從而得到futex_pi_state(rt_mutex代理所在)。
futex的pi-support以及robust-support都跟task_struct偶合在一起。
robust futex 依賴的是進程(或線程)的task_struct結構體中的 robust_list 鏈表。futex的使用者(用戶空間)通過(系統調用)將 用戶空間維護的 robust_list 添加進內核中task_struct->robust_list。當進程(或線程)在退出的時候,內核可以遍歷用戶空間的robust_list鏈表,並對沒有釋放的robust futex進行recovery處理。
進程(或線程)在退出時,exit_mm -> mm_release 會調用futex的服務,exit_robust_list 去對用戶空間使用的未釋放的futex進行recovery處理 handle_futex_death。
對於pi-futex,它所使用的rtmutex代理也是會被恢復的,但不必經過robust_list,mm_release 會調用futex的服務 exit_pi_state_list 進行恢復處理。
對於pthread,當使用pthread_create創建線程時,同時會調用系統調用set_robust_list將內核的task_strust->robust_list,與用戶空間的pthread維護的robust_list關聯起來。
