最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

深度講解futex問(wèn)答(上)

2022-10-27 15:12 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

一、什么是futex?

futex是Fast Userspace muTEX的縮寫,該機(jī)制是由Rusty Russell、Hubertus Franke和Mathew Kirkwood在2.5.7版本的內(nèi)核中引入,雖然名字中有互斥鎖(mutex)的含義,但實(shí)際它是一種用于用戶空間應(yīng)用程序的通用同步工具(基于futex可以在userspace實(shí)現(xiàn)互斥鎖、讀寫鎖、condition variable等同步機(jī)制)。Futex組成包括:

  • 內(nèi)核空間的等待隊(duì)列

  • 用戶空間層的32-bit futex word(所有平臺(tái)都是32bit,包括64位平臺(tái))

在沒有競(jìng)爭(zhēng)的場(chǎng)景下,鎖的獲取和釋放性能都非常高,不需要內(nèi)核的參與,僅僅是通過(guò)用戶空間的原子操作來(lái)修改futex word的狀態(tài)即可。在有競(jìng)爭(zhēng)的場(chǎng)景下,如果線程無(wú)法獲取futex鎖,那么把自己放入到 wait queue中(陷入內(nèi)核,有系統(tǒng)調(diào)用的開銷),而在owner task釋放鎖的時(shí)候,如果檢測(cè)到有競(jìng)爭(zhēng)(等待隊(duì)列中有阻塞任務(wù)),就會(huì)通過(guò)系統(tǒng)調(diào)用來(lái)喚醒等待隊(duì)列中的任務(wù),使其恢復(fù)執(zhí)行,繼續(xù)去持鎖。如果沒有競(jìng)爭(zhēng),那么也無(wú)需陷入內(nèi)核。

二、Futex用戶和內(nèi)核空間接口API是什么?

Futex接口函數(shù)的原型如下:



Futex系統(tǒng)調(diào)用的復(fù)雜性體現(xiàn)在其參數(shù)上,要理解futex需要充分理解其參數(shù):



futex系統(tǒng)調(diào)用支持各種各樣的操作碼,如下:

1、FUTEX_WAIT:如果futex word中仍然保存著參數(shù)val給定的值,那么當(dāng)前線程則進(jìn)入睡眠,等待FUTEX_WAKE的操作喚醒它。

2、FUTEX_WAKE:最多喚醒val個(gè)等待在futex word上的線程。Val或者等于1(喚醒1個(gè)等待線程)或者等于INT_MAX(喚醒全部等待線程)

3、FUTEX_WAIT_BITSET:同F(xiàn)UTEX_WAIT,只不過(guò)多提供一個(gè)mask的參數(shù)

4、FUTEX_WAKE_BITSET:同F(xiàn)UTEX_WAKE,只不過(guò)多提供一個(gè)mask參數(shù)用來(lái)選擇喚醒哪一個(gè)waiter。

5、FUTEX_LOCK_PI:PI版本的FUTEX_WAIT

6、FUTEX_UNLOCK_PI:PI版本的FUTEX_WAKE

7、FUTEX_REQUEUE:這個(gè)操作包括喚醒和移動(dòng)隊(duì)列兩個(gè)動(dòng)作。喚醒val個(gè)等待在uaddr上的waiter,如果還有其他的waiter,那么將這些等待在uaddr的waiter轉(zhuǎn)移到uaddr2的等待隊(duì)列上去(最多轉(zhuǎn)移val2個(gè)waiter)

8、FUTEX_CMP_REQUEUE:同上,不過(guò)需要對(duì)比val3這個(gè)uaddr的期望值。

除了futex wait和wake這樣的基本操作,futex還有其他應(yīng)用在復(fù)雜場(chǎng)景的操作碼,由于在手機(jī)場(chǎng)景沒有使用,本文不再介紹。

我們整理各個(gè)操作碼的參數(shù)如下:



【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ?

三、對(duì)于normal futex,內(nèi)核中如何組織等待隊(duì)列?

Futex相關(guān)的數(shù)據(jù)結(jié)構(gòu)組織如下圖所示:



從邏輯上看,通過(guò)futex實(shí)現(xiàn)的互斥鎖和內(nèi)核中的互斥鎖mutex是一樣的(通過(guò)futex實(shí)現(xiàn)的讀寫鎖的概念和內(nèi)核的rwsem也是一樣,不再贅述),只不過(guò)futex互斥鎖是分裂開的:futex word和等待隊(duì)列是分別在用戶空間和內(nèi)核空間,內(nèi)核的mutex互斥鎖可以講把待隊(duì)列頭放置在mutex對(duì)象上,但是對(duì)于futex,我們沒有對(duì)應(yīng)的內(nèi)核鎖對(duì)象,因此我們就需要一個(gè)算法將futex word和其等待隊(duì)列映射起來(lái)。為了管理掛入等待隊(duì)列的futex阻塞任務(wù),內(nèi)核建立了一個(gè)hansh table如下:



在初始化的時(shí)候,內(nèi)核會(huì)構(gòu)建hashsize個(gè)futex hash bucket結(jié)構(gòu),每個(gè)bucket用來(lái)管理futex鏈表(hash key相同)。futex_hash_bucket數(shù)據(jù)結(jié)構(gòu)定義如下:




每一個(gè)等待在futex word的task都有一個(gè)futex_q對(duì)象(后文稱之futex阻塞任務(wù)對(duì)象),根據(jù)其哈希值掛入不同的隊(duì)列:




通過(guò)上面的數(shù)據(jù)結(jié)構(gòu),只要有了futex word,那么我們就能根據(jù)hash key定位到其掛入的鏈表。當(dāng)然,為了精準(zhǔn)的匹配,還需要其futex key完全相等,具體請(qǐng)參考match_futex函數(shù)。關(guān)于優(yōu)先級(jí)繼承相關(guān)的成員后面會(huì)詳細(xì)描述。

四、Futex wait的流程為何?

futex_wait函數(shù)的流程如下:

1、如果參數(shù)中給定了timeout,那么調(diào)用futex_setup_timer來(lái)創(chuàng)建一個(gè)hrtimer來(lái)打斷futex wait阻塞狀態(tài)。

2、通過(guò)三元組計(jì)算futex hash key,對(duì)于process-private類型的futex word,hash key是根據(jù)進(jìn)程地址空間和futex word的虛擬地址來(lái)計(jì)算,也就是說(shuō)三元組是( current->mm, address, 0 )。對(duì)于share類型的futex word,它會(huì)被放置到共享的內(nèi)存中(通過(guò)mmap或者shmat)。在這種場(chǎng)景下,futex word在不同進(jìn)程中有不同的虛擬地址,但是物理地址是相同的,通過(guò)地址空間中的虛擬地址來(lái)計(jì)算hash key是行不通的。因此share類型的futex word使用的三元組( inode->i_sequence, page->index, offset_within_page )這樣的組合來(lái)計(jì)算hash key。具體的細(xì)節(jié)請(qǐng)參考get_futex_key函數(shù)。

3、有了hash key,我們就可以通過(guò)這個(gè)key找到哈希表中對(duì)應(yīng)的表頭(后文稱之hash bucket)。由于后續(xù)會(huì)把本次futex阻塞任務(wù)對(duì)象(futex_q)掛入hash bucket,因此需要上鎖。

4、在真正插入鏈表之前還需要校驗(yàn)用戶空間傳遞來(lái)的期望值是否發(fā)生了變化(表示用戶空間有其他線程對(duì)該futex word進(jìn)行了修改),如果保持不變,那么就可以放心插隊(duì)了,否則返回EWOULDBLOCK,當(dāng)然,不要忘記解鎖。

5、插隊(duì)動(dòng)作是在futex_wait_queue_me函數(shù)中完成。插隊(duì)是考慮了優(yōu)先級(jí)的:對(duì)于rt線程,優(yōu)先級(jí)高的排在隊(duì)首,低的在隊(duì)尾。對(duì)于cfs任務(wù),不按照優(yōu)先級(jí)排隊(duì),而是采用了FIFO這樣的公平策略。同樣的,完成插隊(duì)后不要忘記解鎖。

6、馬上就要阻塞了,如果參數(shù)中給定了timeout,這時(shí)候就需要啟動(dòng)步驟1中設(shè)置的hrtimer了。

7、在真正阻塞之前,還要進(jìn)一步進(jìn)行驗(yàn)證,畢竟這時(shí)候有可能其他的執(zhí)行線索(可能是其他線程的futex wake,也可能是timeout callback)完成出隊(duì)操作。這時(shí)候就不能阻塞,否者這個(gè)線程可能再也無(wú)法醒來(lái)。

8、在步驟7中阻塞后,可能有多個(gè)喚醒場(chǎng)景:如果任務(wù)被正常喚醒(futex wake喚醒),那么其實(shí)已經(jīng)完成出隊(duì)的動(dòng)作,這時(shí)候直接返回即可,當(dāng)然,如果有啟動(dòng)hrtimer,我們需要取消它。

9、如果本次futex阻塞任務(wù)對(duì)象(futex_q)仍然掛在hash bucket的鏈表上,那表示是有異常發(fā)生,需要進(jìn)行相應(yīng)的處理并在當(dāng)前上下文完成出隊(duì)。具體有兩種情況:超時(shí)或者被信號(hào)打斷。

10、如果設(shè)置了超期時(shí)間,那么在當(dāng)前上下文會(huì)定義hrtimer_sleeper的對(duì)象,如果的確是超期喚醒的話,在timer的上下文中會(huì)把hrtimer_sleeper中的task成員清掉(設(shè)置為NULL),通過(guò)這個(gè)可以判斷是否是超期喚醒。

11、如果當(dāng)前任務(wù)有pending的信號(hào),那說(shuō)明是被信號(hào)打斷。如果沒有pending信號(hào),那說(shuō)明是spurious wakeup,需要再嘗試一次futex入隊(duì)操作。

12、一般而言,如果被信號(hào)打斷,直接返回ERESTARTSYS,讓用戶空間程序自己決定怎么后續(xù)處理就OK了。但是有一種情況例外,那就是設(shè)置了timeout(即還沒有超期就被信號(hào)打斷),這種場(chǎng)景需要restart syscall。

五、Futex wake的流程為何?

相比f(wàn)utex_wait,futex_wake就比較簡(jiǎn)單了,其核心操作就是出隊(duì)和喚醒futex wait阻塞的任務(wù),具體流程如下:

1、首先通過(guò)hash key找到對(duì)應(yīng)的hash bucket,這個(gè)操作和futex_wait中是一樣的。

2、hash bucket中的鏈表上的futex阻塞任務(wù)對(duì)象(futex_q)只是由于hash key相同而走到一起的,實(shí)際上并非一定是對(duì)應(yīng)的futex word,因此我們需要遍歷鏈表進(jìn)行匹配。具體匹配的準(zhǔn)則就是三元組完全相等。

3、三元組相等只能說(shuō)明futex word是對(duì)應(yīng)上了,但是futex機(jī)制也提供了用戶可以控制喚醒的方法:比特匹配。在futex wait的時(shí)候,上層的應(yīng)用程序可以傳遞bitset參數(shù)來(lái)標(biāo)記自己(FUTEX_WAIT_BITSET),在futex wake的時(shí)候,應(yīng)用程序會(huì)傳遞bitset參數(shù)來(lái)通知內(nèi)核自己想要喚醒哪些線程(FUTEX_WAKE_BITSET)。對(duì)于FUTEX_WAIT和FUTEX_WAKE,bitset做了特殊處理,設(shè)置為FUTEX_BITSET_MATCH_ANY,即futex wake的時(shí)候可以喚醒任何阻塞在該futex word的線程。

4、除了bitset,futex wake還可以控制喚醒線程的個(gè)數(shù)。為了完成多個(gè)線程的喚醒,這里使用了喚醒隊(duì)列(wake queue)。當(dāng)找到匹配的futex_q的時(shí)候,將其從hash bucket的隊(duì)列中刪除,加入到喚醒隊(duì)列上來(lái)。需要注意的是:在進(jìn)行這些隊(duì)列操作的時(shí)候需要持有hash buck的自旋鎖。

5、完成指定數(shù)量的掃描之后會(huì)結(jié)束遍歷,調(diào)用wake_up_q將wake queue的任務(wù)逐個(gè)喚醒。

六、Futex requeue是什么鬼?

在講requeue流程之前我們需要先明白為何會(huì)有requeue這個(gè)op code。我們以java中的wait-notify機(jī)制來(lái)說(shuō)明這個(gè)問(wèn)題。我們有如下的java代碼:




Java中的Wait和notify的功能是native實(shí)現(xiàn),在虛擬機(jī)提供支持。Synchronized是java內(nèi)嵌鎖,在虛擬機(jī)對(duì)應(yīng)monitor lock(互斥鎖),A臨界區(qū)和B臨界區(qū)都由monitor lock保護(hù),確保了只有一個(gè)線程進(jìn)入。為了確保A、B臨界區(qū)的先后關(guān)系(A臨界區(qū)需要等待B臨界區(qū)的事件通知),我們引入了condition varible。在wait-notify場(chǎng)景中有兩個(gè)等待隊(duì)列:一個(gè)是monitor lock的等待隊(duì)列,另外一個(gè)是condition varible的等待隊(duì)列。而對(duì)于wait而言,它需要涉及兩個(gè)等待隊(duì)列的操作:一個(gè)是釋放monitor lock(喚醒其等待隊(duì)列的任務(wù)),一個(gè)是阻塞在條件變量上(把自己掛入其等待隊(duì)列)。如果沒有requeue,那么這樣的操作需要兩次futex的系統(tǒng)調(diào)用,有了futex requeue,一次futex就OK了。

了解了requeue的由來(lái),其流程也是非常的簡(jiǎn)單,特別是有了上面兩節(jié)futex wait和futex wake基礎(chǔ)。Requeue的流程如下(requeue有normal requeue和pi requeue,這里我們主要描述normal requeue的流程):

1、Requeue涉及兩個(gè)futex,分別用uaddr1和uaddr2表示。這里需要喚醒nr_wake個(gè)uaddr1上的線程,同時(shí)把其上的nr_requeue個(gè)等待任務(wù)對(duì)象轉(zhuǎn)移到uaddr2對(duì)應(yīng)的等待隊(duì)列上。首先調(diào)用get_futex_key獲取兩個(gè)futex的hash key,并根據(jù)hash key找到對(duì)應(yīng)的hash bucket(hash_futex函數(shù))

2、如果是FUTEX_CMP_REQUEUE,那么我們還需要校驗(yàn)uaddr1中的值。需要特別說(shuō)明的是:這里涉及內(nèi)核空間訪問(wèn)用戶空間的變量,讀操作是一個(gè)非常復(fù)雜的過(guò)程,具體參考get_futex_value_locked函數(shù)。這些邏輯和本文的主題關(guān)系不大,就不再贅述了。

3、遍歷uaddr1 等待隊(duì)列上的所有等待任務(wù)對(duì)象(futex_q),將nr_wake個(gè)futex_q通過(guò)mark_wake_futex暫存在wake_q喚醒隊(duì)列上。通過(guò)requeue_futex將uaddr1 等待隊(duì)列上nr_requeue個(gè)futex_q對(duì)象轉(zhuǎn)移到uaddr2的等待隊(duì)列上。注意,這些操作需要持有兩個(gè)hash bucket的自旋鎖。

4、調(diào)用wake_up_q函數(shù)喚醒之前掛入喚醒隊(duì)列的任務(wù)

文章篇幅過(guò)長(zhǎng) 下文繼續(xù)講解?

原文作者:內(nèi)核工匠




深度講解futex問(wèn)答(上)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
福州市| 淮滨县| 西青区| 乌恰县| 凌源市| 施秉县| 邯郸县| 聂拉木县| 新宁县| 西林县| 山阴县| 玛曲县| 论坛| 宝清县| 南昌县| 榕江县| 衡山县| 府谷县| 北碚区| 溧水县| 扬中市| 石门县| 揭西县| 若尔盖县| 剑河县| 乌恰县| 荣成市| 旅游| 兴仁县| 涟源市| 通山县| 隆安县| 赤水市| 扶余县| 天峻县| 兴义市| 忻城县| 双鸭山市| 常德市| 买车| 象州县|