vrml场景实例代码_高并发的中断下半部tasklet实例解析
本文轉(zhuǎn)自AliDataOps
最近為了解決一個(gè)技術(shù)問(wèn)題,需要用到內(nèi)核里中斷下半部的tasklet機(jī)制,使用過(guò)程遇到了非常有趣的問(wèn)題。在解決問(wèn)題過(guò)程中,也逐步加深了對(duì)tasklet機(jī)制的理解。本文把這些收獲記錄下來(lái)和大家一起分享。
一、問(wèn)題發(fā)生的場(chǎng)景
出于排查磁盤(pán)IO方面問(wèn)題的原因,需要利用內(nèi)核tracepoint技術(shù)詳細(xì)地監(jiān)測(cè)磁盤(pán)IO的明細(xì)行為信息,如下git為代碼示例。
其中關(guān)鍵部分代碼摘要如下。
這里不必對(duì)tracepoint機(jī)制進(jìn)行深究,只需要了解blk_add_trace_rq_insert1回調(diào)函數(shù)對(duì)應(yīng)于linux內(nèi)核函數(shù)中的block_rq_insert靜態(tài)探針點(diǎn),block_rq_insert探針點(diǎn)在__elv_add_request內(nèi)核函數(shù)的開(kāi)頭處打點(diǎn)(trace_block_rq_insert處),如下代碼所示。每一次__elv_add_request函數(shù)的調(diào)用,都有一次blk_add_trace_rq_insert1回調(diào)函數(shù)與之對(duì)應(yīng)執(zhí)行。
代碼運(yùn)行后會(huì)出現(xiàn)如下的效果,可以看到我們成功的獲取了每一次IO請(qǐng)求的SIZE大小,并且還獲取了對(duì)應(yīng)的bio結(jié)構(gòu)體指針之一。
類(lèi)似中斷下半部的tasklet機(jī)制對(duì)中斷處理函數(shù)的延遲處理,下半部tasklet也可以應(yīng)用到tracepoint回調(diào)函數(shù)上,從而提升回調(diào)函數(shù)blk_add_trace_rq_insert1的并發(fā)處理能力。
為了本文中意思表達(dá)更加準(zhǔn)確,下文對(duì)tracepoint回調(diào)函數(shù)約定稱(chēng)為上半部處理函數(shù),對(duì)tasklet處理函數(shù)約定稱(chēng)為下半部處理函數(shù)。
二、丟失的tasklet下半部
初學(xué)tasklet時(shí),對(duì)它的理解并不深入。查閱國(guó)內(nèi)外各種kernel的經(jīng)典教材中的中斷下半部tasklet部分內(nèi)容,在介紹使用tasklet時(shí),都需要靜態(tài)或動(dòng)態(tài)創(chuàng)建一個(gè)全局tasklet全局變量。其中靜態(tài)創(chuàng)建方法是使用DECLARE_TASKLET宏的方法,動(dòng)態(tài)創(chuàng)建tasklet方法見(jiàn)如下代碼。
照葫蘆畫(huà)瓢,初步實(shí)現(xiàn)了如下代碼的tasklet代碼。
其中關(guān)鍵部分代碼摘要如下:
接下來(lái)執(zhí)行以上代碼,查看運(yùn)行效果。
滿(mǎn)懷希望的運(yùn)行,卻發(fā)現(xiàn)了一個(gè)令人意想不到的結(jié)果,tasklet的下半部處理函數(shù)調(diào)用次數(shù)遠(yuǎn)小于上半部處理函數(shù)的調(diào)用次數(shù)。
三、丟失tasklet的原因
針對(duì)這個(gè)部分下半部tasklet丟失的問(wèn)題,再次查閱kernel的經(jīng)典教材,在《Linux Kernel Development 3rd Edition》的8.3.2小節(jié)中發(fā)現(xiàn)了Robert Love寫(xiě)下的這么一段話(huà)。
After a tasklet is scheduled, it runs once at some time in the near future. If the same tasklet is scheduled again, before it has had a chance to run, it still runs only once.
那么問(wèn)題關(guān)鍵就聚焦在什么是“相同的tasklet(the same tasklet)”。為了搞清楚這個(gè)問(wèn)題,我們來(lái)分析一下tasklet_schedule()函數(shù)的源碼。
函數(shù)中關(guān)鍵部分是 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) 這一行代碼。設(shè)置tasklet類(lèi)型的結(jié)構(gòu)體對(duì)象t的state狀態(tài)屬性的TASKLET_STATE_SCHED位為1,同時(shí)返回tasklet類(lèi)型的結(jié)構(gòu)體對(duì)象t的state狀態(tài)屬性的TASKLET_STATE_SCHED位的原值。如果這個(gè)原值是1,就說(shuō)明這個(gè)tasklet類(lèi)型的結(jié)構(gòu)體對(duì)象已經(jīng)被調(diào)度到另一個(gè)CPU上去等待執(zhí)行了。考慮到一個(gè)tasklet結(jié)構(gòu)體對(duì)象同一時(shí)刻只能由一個(gè)CPU來(lái)執(zhí)行,因此tasklet_schedule()不做任何操作,就直接返回了。反之,如果原值是0,那么就會(huì)執(zhí)行__tasklet_schedule()調(diào)度函數(shù)。
順藤摸瓜,我們很容易找到tasklet執(zhí)行完畢后,清除TASKLET_STATE_SCHED位值的地方,如下代碼。
總結(jié)一下,通過(guò)分析tasklet_schedule()函數(shù)的源碼可知,一個(gè)tasklet就是指一個(gè)tasklet_struct結(jié)構(gòu)體的指針對(duì)象。
在本例的整個(gè)代碼中就聲明了一個(gè)my_tasklet結(jié)構(gòu)體指針對(duì)象,因此只有一個(gè)tasklet。所有的上半部處理函數(shù)和下半部處理函數(shù)的聯(lián)系都是通過(guò)一個(gè)相同的tasklet。當(dāng)前一次tasklet的下半部處理函數(shù)沒(méi)有得到及時(shí)執(zhí)行時(shí),后一次上半部處理函數(shù)中再次執(zhí)行tasklet_schedule進(jìn)行調(diào)度時(shí),很容易被內(nèi)核丟棄。
四、高并發(fā)的下半部tasklet
明確了導(dǎo)致問(wèn)題的原因,下面還要找到解決問(wèn)題的方法。
既然在整個(gè)代碼中只申請(qǐng)一個(gè)全局的tasklet結(jié)構(gòu)體指針對(duì)象會(huì)導(dǎo)致下半部丟失的問(wèn)題,那么我們我們可以考慮在上半部處理函數(shù)中每次都單獨(dú)申請(qǐng)一個(gè)tasklet結(jié)構(gòu)體指針對(duì)象。同時(shí)需要在tasklet下半部處理函數(shù)中及時(shí)釋放tasklet結(jié)構(gòu)體指針對(duì)象。為了下半部處理函數(shù)中及時(shí)釋放指針對(duì)象,還需要把上半部處理函數(shù)中聲明的tasklet結(jié)構(gòu)體指針對(duì)象傳遞給下半部處理函數(shù)。同時(shí)也要把上半部處理函數(shù)中獲取的內(nèi)核blk層request結(jié)構(gòu)體相關(guān)的信息傳遞給下半部處理函數(shù),便于在下半部處理函數(shù)中提取相關(guān)IO信息。
非常幸運(yùn)的是tasklet給我們提供了這樣一個(gè)傳參的方法,tasklet_init函數(shù)的第三個(gè)參數(shù)unsigned long data可以幫助我們實(shí)現(xiàn)傳參的目標(biāo)。
在64位操作系統(tǒng)中,unsigned long數(shù)據(jù)類(lèi)型可以用來(lái)存儲(chǔ)指針數(shù)據(jù)類(lèi)型的指針值本身,如下實(shí)例所示,在64位系統(tǒng)中無(wú)符號(hào)長(zhǎng)整型和指針類(lèi)型都占用8字節(jié)。
通過(guò)以上的初步分析,最終我們實(shí)現(xiàn)如下代碼。
其中關(guān)鍵部分代碼摘要如下。
接下來(lái)執(zhí)行以上代碼,查看運(yùn)行效果。
再次滿(mǎn)懷希望的運(yùn)行,這次tasklet的下半部處理函數(shù)調(diào)用次數(shù)等于上半部處理函數(shù)的調(diào)用次數(shù),完全符合預(yù)期。
下面對(duì)代碼進(jìn)行一些重點(diǎn)分析:
- 定義一個(gè)iodump_struct結(jié)構(gòu)體,其中包含struct tasklet_struct *tasklet結(jié)構(gòu)體成員和其他一些結(jié)構(gòu)體成員。tasklet結(jié)構(gòu)體指針成員用于在上半部和下半部間傳遞tasklet結(jié)構(gòu)體指針對(duì)象。
- 在上半部處理函數(shù)中聲明和初始化tasklet_struct和iodump_struct類(lèi)型的結(jié)構(gòu)體指針對(duì)象。
- 使用tasklet_init函數(shù)的第三個(gè)參數(shù),將iodump_struct類(lèi)型結(jié)構(gòu)體指針對(duì)象傳遞給下半部處理函數(shù)。
- 在下半部處理函數(shù)中解析出各個(gè)參數(shù),包括tasklet_struct類(lèi)型的結(jié)構(gòu)體指針對(duì)象。
- 完成下半部處理函數(shù)中的任務(wù)后,分別釋放iodump_struct類(lèi)型和tasklet_struct類(lèi)型的結(jié)構(gòu)體指針對(duì)象。
五、內(nèi)核中的常見(jiàn)實(shí)現(xiàn)
至此問(wèn)題已經(jīng)順利解決,但實(shí)現(xiàn)方案是否完美,還需要做一些思考。經(jīng)驗(yàn)豐富的同學(xué)都知道linux內(nèi)核代碼有2000多萬(wàn)行,其中很多模塊的代碼實(shí)現(xiàn)都十分經(jīng)典,是一部編程的百科全書(shū)。
按照這樣的思路,我們不難從內(nèi)核usb驅(qū)動(dòng)部分找到一段中斷下半部tasklet的經(jīng)典使用場(chǎng)景。
從usbatm的代碼實(shí)例中,我們可以了解到tasklet也是使用了tasklet_init的第三個(gè)參數(shù)實(shí)現(xiàn)了中斷上半部和下半部之間的參數(shù)傳遞。充分體現(xiàn)了tasklet_init函數(shù)第三個(gè)參數(shù)unsigned long data的強(qiáng)大作用。
細(xì)心的讀者可能會(huì)發(fā)現(xiàn),我們的concurrent_tasklet.git實(shí)例盡管支持了高并發(fā)的tasklet,但是也存在一些不足。由于每次上半部都會(huì)申請(qǐng)內(nèi)存,而下半部會(huì)釋放內(nèi)存。這樣頻繁申請(qǐng)和釋放內(nèi)存,也會(huì)存在一定的性能開(kāi)銷(xiāo)。而內(nèi)核驅(qū)動(dòng)usbatm部分就相對(duì)較好的解決了這個(gè)內(nèi)存頻繁申請(qǐng)和釋放的問(wèn)題。如果你的項(xiàng)目需要追求更加極致的并發(fā)性能,可以參考usbatm部分的代碼實(shí)例。
另一方面,內(nèi)核中有類(lèi)似傳參場(chǎng)景的地方還有很多,不過(guò)大都是通過(guò)void *類(lèi)型指針參數(shù)實(shí)現(xiàn)的,如下2處即是。
通過(guò)tasklet_init函數(shù)第三個(gè)參數(shù)unsigned long data的例子,告訴我們unsigned long類(lèi)型的傳參,也可以實(shí)現(xiàn)void *類(lèi)型傳參的作用。
總結(jié)
以上是生活随笔為你收集整理的vrml场景实例代码_高并发的中断下半部tasklet实例解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js 数据类型_js中检测数据类型的方法
- 下一篇: python词云cannot open