linux 內核的各種futex


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關聯起來。


免責聲明!

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



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