深入講解讀寫(xiě)信號(hào)量(下)
(3)樂(lè)觀自旋

① rwsem_can_spin_on_owner
rwsem_can_spin_on_owner用于判斷當(dāng)前任務(wù)是否可以進(jìn)行樂(lè)觀自旋

② rwsem_spin_on_owner
rwsem_spin_on_owner 是rwsem自旋實(shí)現(xiàn)的地方,從字面就可以看出,它會(huì)不停的去查詢(xún)owner的狀態(tài),實(shí)現(xiàn)自旋
與樂(lè)觀自旋owner state相關(guān)的狀態(tài)




樂(lè)觀自旋失敗總結(jié):
1.writer持鎖
a.reader/writer樂(lè)觀自旋失敗原因:need_resched() || writer不在cpu上 || nonspinable
2.reader持鎖
a.reader樂(lè)觀自旋失敗原因:nonspinable || (handoff標(biāo)記 && (need_resched() || rt_task ))
b.writer樂(lè)觀自旋失敗原因:nonspinable || need_reshced() || timeout || rt_task
Writer樂(lè)觀自旋嘗試持鎖

Reader樂(lè)觀自旋嘗試持鎖

樂(lè)觀自旋成功后:

3. read持鎖慢速路徑


4. read持鎖總結(jié)
1.reader持鎖前,沒(méi)有其它人持鎖,快速路徑中持鎖成功
2.reader持鎖前,有reader持鎖,但是沒(méi)有writer等待,沒(méi)有設(shè)置handoff標(biāo)記,reader持鎖數(shù)量沒(méi)有溢出,快速路徑中持鎖成功
3.reader持鎖前,有reader持鎖,但是有writer等待
a.樂(lè)觀自旋中,拿到了osq鎖,進(jìn)入樂(lè)觀自旋,沒(méi)有設(shè)置handoff標(biāo)記,則持鎖成功
b.樂(lè)觀自旋失敗,走慢速路徑,睡眠等待被喚醒
4.reader持鎖前,有writer持鎖,樂(lè)觀自旋失敗后,走慢速路徑,睡眠等待被喚醒
5. read釋放鎖

六、rwsem write
同read一樣,write持鎖也有3種狀態(tài)
快速路徑
中速路徑
慢速路徑

1. write持鎖快速路徑
write持鎖快速路徑比較簡(jiǎn)單,直接調(diào)用
atomic_long_try_cmpxchg_acquire(&sem->count, &tmp, RWSEM_WRITER_LOCKED)
只有當(dāng)鎖沒(méi)有人持有時(shí),才能成功
2. write持鎖中速路徑
與reader持鎖的實(shí)現(xiàn)是一模一樣的

3.write持鎖慢速路徑
(1)handoff
設(shè)置RWSEM_FLAG_HANDOFF 的作用:防止處于wait list第一個(gè)等待者餓死
因?yàn)榧尤肓薿sq樂(lè)觀自旋,存在一種情況,任務(wù)持鎖失敗,被掛入wait list中的第一個(gè),同時(shí)也存在處于rwsem樂(lè)觀自旋狀態(tài)的任務(wù),owner釋放了鎖,那么該鎖大概率會(huì)被樂(lè)觀自旋的任務(wù)搶走,如果持續(xù)的有樂(lè)觀自旋狀態(tài)任務(wù)來(lái)?yè)屾i,那么wait list中的任務(wù)可能會(huì)被餓死。
1.rwsem_try_read_lock_unqueued/rwsem_try_write_lock_unqueued時(shí)會(huì)直接返回
2.reader持鎖情況下,另外的reader再來(lái)持鎖時(shí),也不會(huì)成功
3.rwsem_down_read_slowpath 時(shí),如果wait list空的,且是writer持鎖或者設(shè)置了handoff則不能獲得鎖(正常是可以的,因?yàn)槭亲x者持鎖)
4.rwsem_try_write_lock 時(shí),可以直接返回
設(shè)置RWSEM_FLAG_HANDOFF的地方
1.在rwsem_down_write_slowpath 慢速路徑被喚醒后,如果滿(mǎn)足這個(gè)條件(wstate == WRITER_FIRST) && (rt_task(current) || time_after(jiffies, waiter.timeout),會(huì)將wstate 設(shè)置成WRITER_HANDOFF,然后調(diào)用rwsem_try_write_lock去拿鎖,若鎖還有人持有,則將RWSEM_FLAG_HANDOFF 標(biāo)志置上,這樣在其它任務(wù)正常的路徑上拿鎖就不會(huì)成功了。
2.在rwsem_mark_wake 流程中,如果是writer持鎖,wait list第一個(gè)等待者是reader且超過(guò)timeout的時(shí)間沒(méi)有拿到鎖,也會(huì)把RWSEM_FLAG_HANDOFF 標(biāo)志置上。

1.如果已經(jīng)設(shè)置了handoff 且 wstate == WRITER_NOT_FIRST,則直接返回,在rwsem_mark_wake 里,如果reader等的時(shí)間太長(zhǎng),會(huì)設(shè)置handoff bit,不能讓reader餓死。
鎖被持有的情況,如果設(shè)置了handeroff 或者不是WRITER_HANDOFF,則直接return false。否則把handoff標(biāo)置設(shè)上,然后也會(huì)return false,第二次進(jìn)來(lái)的時(shí)候,如果沒(méi)有人持鎖,這個(gè)線程就能持鎖成功,這是為了避免在這個(gè)線程被喚醒后,被spiner把鎖給偷了2次。
(2)慢速路徑過(guò)程

編輯切換為居中

編輯切換為居中
如果設(shè)置了WRITER_HANDOFF,則直接調(diào)用rwsem_spin_on_owner,如果返回的是無(wú)人持鎖,則直接去拿鎖,就不需要睡眠了。
如果WRITER_HANDOFF 已經(jīng)設(shè)置了,就不走后面的流程了,因?yàn)楹竺娴牧鞒桃彩菫榱嗽O(shè)置handoff標(biāo)記。
是第一個(gè)waiter 且 是rt task 或者 超時(shí)了,會(huì)設(shè)置writer_handoff標(biāo)記,這個(gè)標(biāo)記可以讓這個(gè)線程不會(huì)被spiner把鎖再次偷走為什么要判斷這個(gè),設(shè)想一個(gè)場(chǎng)景,假設(shè)該writer從睡眠被喚醒,說(shuō)明鎖被釋放后把它喚醒了,它本來(lái)可以去拿到鎖,但是失敗了,說(shuō)明被spiner偷走了為了防止這個(gè)現(xiàn)象再次發(fā)生,就要設(shè)置handoff 標(biāo)記,讓其它人偷不了鎖。
4. write釋放鎖

編輯切換為居中
七、總結(jié)
文中所講的有很大一部分都是源碼,很細(xì)節(jié)的東西。如果大家看的云里霧里的,不要懷疑自己。盡管文檔是我一字一句寫(xiě)出來(lái)的,并且反復(fù)回味了非常多遍,但是我在碰到很細(xì)節(jié)的問(wèn)題時(shí),仍然要回頭再去看文檔的描述才能弄清楚原因。對(duì)于讀寫(xiě)信號(hào)量,我的建議如下:
1.只想了解個(gè)大概??梢钥聪聄wsem的結(jié)構(gòu),osq的概念,搞懂reader/writer持鎖后,又有reader/writer進(jìn)來(lái)持鎖以及有樂(lè)觀自旋任務(wù)的情況下,會(huì)有哪些可能即可。
2.想深入弄懂原理。對(duì)著代碼好好研究一番。rwsem還是會(huì)復(fù)雜一些,弄清除了這個(gè),再看其它鎖的實(shí)現(xiàn)時(shí),應(yīng)該會(huì)更得心應(yīng)手了
原文作者:內(nèi)核工匠
