作者簡(jiǎn)介
朱輝,做過(guò)幾年模擬器,做過(guò)幾年GDB,在小米電視做過(guò)幾年Linux內(nèi)核優(yōu)化,主要圍繞MM。
現(xiàn)在在HyperHQ當(dāng)軟件工程師。
之前在我熱愛(ài)的公眾號(hào)Linuxer看到The precise meaning of I/O wait time in Linux 這篇文章,感覺(jué)寫的不錯(cuò),就是沒(méi)有落實(shí)到源碼上感覺(jué)稍微有點(diǎn)晦澀,于是自己讀了一下代碼。
當(dāng)task發(fā)生iowait的時(shí)候,內(nèi)核對(duì)他們的處理方法是將task切換出去,讓可運(yùn)行的task先運(yùn)行,而在切換出去前,會(huì)將其in_iowait設(shè)置為1,再次被喚醒的時(shí)候in_iowait被設(shè)置為原值。相關(guān)函數(shù)io_schedule,io_schedule_timeout,mutex_lock_io,mutex_lock_io_nested。
例如:
由此可見(jiàn)in_iowait表明了這個(gè)task是否在iowait。
另外要注意的是,這幾個(gè)切換函數(shù)除了mutex_lock_io,mutex_lock_io_nested會(huì)設(shè)置task運(yùn)行狀態(tài)為TASK_UNINTERRUPTIBLE外,內(nèi)核在調(diào)用io_schedule,io_schedule_timeout前都會(huì)設(shè)置task運(yùn)行狀態(tài)TASK_UNINTERRUPTIBLE。
在進(jìn)程切換函數(shù)__schedule在切換task的時(shí)候,如果被切換出的task的in_iowait為真,則會(huì)對(duì)這個(gè)CPU的運(yùn)行隊(duì)列rq結(jié)構(gòu)中的nr_iowait加1。
因?yàn)榍懊鎸?duì)task已經(jīng)被設(shè)置為TASK_UNINTERRUPTIBLE,則task需要被喚醒,對(duì)nr_iowait的減少操作也是在task喚醒函數(shù)來(lái)做的。
由此可見(jiàn)nr_iowait可以表明某CPU上是否有task在iowait,以及數(shù)量。
因?yàn)樘幱趇owait的task是TASK_UNINTERRUPTIBLE狀態(tài),其并不在就緒隊(duì)列中,所以其也沒(méi)有被CPU負(fù)載均衡到其他CPU的可能,所以nr_iowait也不需要處理負(fù)載均衡問(wèn)題。
當(dāng)累加系統(tǒng)idle時(shí)間的時(shí)候,如果CPU的nr_iowait為真,也就是當(dāng)前這個(gè)cpu有task在等待iowait,則記錄為iowait時(shí)間。
在打開(kāi)NO_HZ的內(nèi)核中,相關(guān)代碼在update_ts_time_stats。
而沒(méi)打開(kāi)的則在 account_idle_time。
當(dāng)相關(guān)/proc/stat接口被訪問(wèn)時(shí),get_iowait_time就會(huì)訪問(wèn)這個(gè)時(shí)間并返回。
綜上所述,iowait時(shí)間就是CPU idle時(shí)間,但是這時(shí)候CPU上不是完全沒(méi)TASK需要運(yùn)行,而是休眠的task中有一個(gè)或者若干個(gè)是iowait的task。
當(dāng)然idle和iowait的時(shí)候CPU上還有idle task。
最后推薦一篇阿里內(nèi)核組的文章作為擴(kuò)展閱讀Kernel Documents/new iowait calculation
比較有意思是這里:
+ wait_event_interruptible_hrtimeout(ctx->wait,
+ aio_read_events(ctx, min_nr, nr, event, &ret), until);
無(wú)論超時(shí)值until是什么值,都會(huì)調(diào)用wait_event_interruptible_hrtimeout,雖然是hrtimer實(shí)時(shí)性已經(jīng)很高,但是在用來(lái)實(shí)際處理wait的宏__wait_event_hrtimeout可以看到hrtimer初始化使用的是:
hrtimer_start_range_ns(&__t.timer, timeout,
current->timer_slack_ns,
HRTIMER_MODE_REL);
其中第三個(gè)參數(shù)current->timer_slack_ns是傳遞給hrtimer的觸發(fā)范圍,因?yàn)閔rtimer實(shí)時(shí)性高,但是頻繁觸發(fā)系統(tǒng)顯然受不了,所以每次hrtimer觸發(fā)都會(huì)將時(shí)間范圍內(nèi)的timer都處理掉(見(jiàn)__hrtimer_run_queues)。所以timeout+current->timer_slack_ns才是設(shè)置的hrtimer的最后觸發(fā)時(shí)間,current->timer_slack_ns的默認(rèn)值是50000,也就是代表50000納秒。也就是這個(gè)時(shí)鐘最久會(huì)在50000納秒后觸發(fā),當(dāng)然也可能被之前的hrtimer觸發(fā)。
所以在wait_event_interruptible_hrtimeout中,一旦ctx->wait未能就緒,即使設(shè)置超時(shí)時(shí)間為0,也很可能要調(diào)用一次schedule,這導(dǎo)致iowait時(shí)間相差很大,也還很大幅度傷害了性能。
而這個(gè)問(wèn)題也被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的時(shí)候,直接調(diào)用aio_read_events。應(yīng)該就不會(huì)再有那么明顯的iowait問(wèn)題了,另外也因此這個(gè)修復(fù)讓io_getevents的調(diào)用得到了超過(guò)百倍的性能提升。
當(dāng)然這個(gè)iowait不夠精確的原因還是存在,一旦因?yàn)樾枰l(fā)生task切換,還是會(huì)有不夠精確的問(wèn)題。
最后要吐槽一下aio的設(shè)計(jì),都aio了還需要wait嗎?
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1372瀏覽量
40282 -
Linux
+關(guān)注
關(guān)注
87文章
11296瀏覽量
209360 -
代碼
+關(guān)注
關(guān)注
30文章
4780瀏覽量
68539
原文標(biāo)題:朱輝(茶水): Linux Kernel iowait 時(shí)間的代碼原理
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論