本文系轉(zhuǎn)載,著作權(quán)歸作者所有酵幕。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)促绵,非商業(yè)轉(zhuǎn)載請注明出處。
作者: 朱輝(茶水)
來源: 微信公眾號linux閱碼場(id: linuxdev)
作者介紹
朱輝槽奕,個人主頁 http://teawater.github.io/几睛,微信公眾號茶水侃山(cschatcs)。
做過幾年模擬器粤攒,做過幾年GDB所森,在小米電視做過幾年Linux內(nèi)核優(yōu)化,主要圍繞MM夯接。
現(xiàn)在在HyperHQ當(dāng)軟件工程師焕济。
更新記錄
2017.12.15:
對擴展文章的問題描述進(jìn)行了精確化。
2017.12.10:
根據(jù)張驍和宋寶華老師的建議盔几,將結(jié)尾的錯誤進(jìn)行了修正吼蚁。
增加一篇擴展閱讀。
增加了對CPU負(fù)載均衡問題的講解问欠。
之前在我熱愛的公眾號Linux閱碼場看到The precise meaning of I/O wait time in Linux 這篇文章肝匆,感覺寫的不錯,就是沒有落實到源碼上感覺稍微有點晦澀顺献,于是自己讀了一下代碼旗国。
當(dāng)task發(fā)生iowait的時候,內(nèi)核對他們的處理方法是將task切換出去注整,讓可運行的task先運行能曾,而在切換出去前度硝,會將其in_iowait設(shè)置為1,再次被喚醒的時候in_iowait被設(shè)置為原值寿冕。相關(guān)函數(shù)io_schedule蕊程,io_schedule_timeout,mutex_lock_io驼唱,mutex_lock_io_nested藻茂。
例如:
由此可見in_iowait表明了這個task是否在iowait。
另外要注意的是玫恳,這幾個切換函數(shù)除了mutex_lock_io辨赐,mutex_lock_io_nested會設(shè)置task運行狀態(tài)為TASK_UNINTERRUPTIBLE外,內(nèi)核在調(diào)用io_schedule京办,io_schedule_timeout前都會設(shè)置task運行狀態(tài)TASK_UNINTERRUPTIBLE掀序。
在進(jìn)程切換函數(shù)__schedule在切換task的時候,如果被切換出的task的in_iowait為真惭婿,則會對這個CPU的運行隊列rq結(jié)構(gòu)中的nr_iowait加1不恭。
因為前面對task已經(jīng)被設(shè)置為TASK_UNINTERRUPTIBLE,則task需要被喚醒财饥,對nr_iowait的減少操作也是在task喚醒函數(shù)來做的换吧。
由此可見nr_iowait可以表明某CPU上是否有task在iowait,以及數(shù)量佑力。
因為處于iowait的task是TASK_UNINTERRUPTIBLE狀態(tài),其并不在就緒隊列中筋遭,所以其也沒有被CPU負(fù)載均衡到其他CPU的可能打颤,所以nr_iowait也不需要處理負(fù)載均衡問題。
當(dāng)累加系統(tǒng)idle時間的時候漓滔,如果CPU的nr_iowait為真编饺,也就是當(dāng)前這個cpu有task在等待iowait,則記錄為iowait時間响驴。
在打開NO_HZ的內(nèi)核中透且,相關(guān)代碼在update_ts_time_stats。
而沒打開的則在 account_idle_time豁鲤。
當(dāng)相關(guān)/proc/stat接口被訪問時秽誊,get_iowait_time就會訪問這個時間并返回。
綜上所述琳骡,iowait時間就是CPU idle時間锅论,但是這時候CPU上不是完全沒TASK需要運行,而是休眠的task中有一個或者若干個是iowait的task楣号。
當(dāng)然idle和iowait的時候CPU上還有idle task最易。
最后推薦一篇阿里內(nèi)核組的文章作為擴展閱讀Kernel Documents/new iowait calculation (http://link.zhihu.com/?target=http%3A//kernel.taobao.org/index.php%3Ftitle%3DKernel_Documents/new_iowait_calculation)
比較有意思是這里:
+ wait_event_interruptible_hrtimeout(ctx->wait,
+ aio_read_events(ctx, min_nr, nr, event, &ret), until);
無論超時值until是什么值怒坯,都會調(diào)用wait_event_interruptible_hrtimeout,雖然是hrtimer實時性已經(jīng)很高藻懒,但是在用來實際處理wait的宏__wait_event_hrtimeout可以看到hrtimer初始化使用的是:
hrtimer_start_range_ns(&__t.timer, timeout,\
current->timer_slack_ns,\
HRTIMER_MODE_REL);\
其中第三個參數(shù)current->timer_slack_ns是傳遞給hrtimer的觸發(fā)范圍剔猿,因為hrtimer實時性高,但是頻繁觸發(fā)系統(tǒng)顯然受不了嬉荆,所以每次hrtimer觸發(fā)都會將時間范圍內(nèi)的timer都處理掉(見__hrtimer_run_queues)归敬。所以timeout+current->timer_slack_ns才是設(shè)置的hrtimer的最后觸發(fā)時間,current->timer_slack_ns的默認(rèn)值是50000员寇,也就是代表50000納秒弄慰。也就是這個時鐘最久會在50000納秒后觸發(fā),當(dāng)然也可能被之前的hrtimer觸發(fā)蝶锋。
所以在wait_event_interruptible_hrtimeout中陆爽,一旦ctx->wait未能就緒,即使設(shè)置超時時間為0扳缕,也很可能要調(diào)用一次schedule慌闭,這導(dǎo)致iowait時間相差很大,也還很大幅度傷害了性能躯舔。
而這個問題也被5f785de588735306ec4d7c875caf9d28481c8b21進(jìn)行了修復(fù)驴剔,這段代碼改成了:
- wait_event_interruptible_hrtimeout(ctx->wait,
- aio_read_events(ctx, min_nr, nr, event, &ret), until);
+ if (until.tv64 == 0)
+ aio_read_events(ctx, min_nr, nr, event, &ret);
+ else
+ wait_event_interruptible_hrtimeout(ctx->wait,
+ aio_read_events(ctx, min_nr, nr, event, &ret),
+ until);
從而在until為0的時候,直接調(diào)用aio_read_events粥庄。應(yīng)該就不會再有那么明顯的iowait問題了丧失,另外也因此這個修復(fù)讓io_getevents的調(diào)用得到了超過百倍的性能提升。
當(dāng)然這個iowait不夠精確的原因還是存在惜互,一旦因為需要發(fā)生task切換布讹,還是會有不夠精確的問題。
最后要吐槽一下aio的設(shè)計训堆,都aio了還需要wait嗎描验?
更多精彩更新中……歡迎關(guān)注微信公眾號:linux閱碼場(id: linuxdev)