select函數(shù)與stdio混用的不良后果 (原)
Posted on 2013-01-15 13:09 鑫龍 閱讀(993) 評(píng)論(1) 編輯 收藏 引用 所屬分類: 本人分析研究 轉(zhuǎn)載請(qǐng)注明 出自:http://www.shnenglu.com/mysileng/archive/2013/01/15/197284.html
今天在看UNP6.5節(jié),學(xué)習(xí)到了select與stdio混用的后果。特此進(jìn)程實(shí)驗(yàn)一番。再實(shí)驗(yàn)之前需明確一下幾點(diǎn):
1.stdio流的i/o函數(shù) 與 系統(tǒng)i/o函數(shù)不同。stdio流函數(shù)在用戶空間和內(nèi)核都有緩沖,系統(tǒng)i/o函數(shù)只在內(nèi)核有緩沖,用戶空間沒有。
2.stdio流的i/o函數(shù)緩沖機(jī)制:在面對(duì)文件時(shí)候用的是全緩沖,面對(duì)設(shè)備的時(shí)候用的行緩沖。(等下試驗(yàn)用的是鍵盤和屏幕),所以實(shí)驗(yàn)用的stdio函數(shù)采用行行緩沖。
3.select函數(shù)對(duì)于某一個(gè)描述符是否準(zhǔn)備好可讀可寫,是對(duì)內(nèi)核緩沖區(qū)中的數(shù)據(jù)是否達(dá)到某一個(gè)最低標(biāo)準(zhǔn),而不是用戶緩沖區(qū)。也就是說select函數(shù)不知道用戶緩沖區(qū)的存在。
首先寫了一個(gè)系統(tǒng)i/o函數(shù) 簡(jiǎn)單的select函數(shù)程序:

程序給select函數(shù)只設(shè)置的鍵盤的描述符。也就是說如果鍵盤的描述符準(zhǔn)備好了就不再阻塞。但是這里有一個(gè)問題,解除阻塞后,我們最多只從內(nèi)核緩沖區(qū)讀3個(gè)字節(jié),這個(gè)時(shí)候就會(huì)有兩個(gè)情況:
(1)內(nèi)核空間本來存儲(chǔ)的數(shù)據(jù)就小于等于3個(gè)字節(jié),全被讀走。那下次再次調(diào)用select函數(shù),應(yīng)該肯定會(huì)阻塞的,因?yàn)殒I盤輸入的內(nèi)核緩沖區(qū)已經(jīng)沒有數(shù)據(jù)了。
情況如下(內(nèi)核空間只有3個(gè)字節(jié):1 2 \n):

(2)如果內(nèi)核空間的數(shù)據(jù)多余3個(gè)字節(jié),但是因?yàn)樽疃嘀荒茏x3個(gè)字節(jié),就必將導(dǎo)致內(nèi)核中有數(shù)據(jù)讀不完。那么下次再遇到select函數(shù)的時(shí)候是否會(huì)阻塞呢?
情況見下:

當(dāng)我們輸入5個(gè)字符時(shí)候(1 2 3 4 \n),第一次read掉3個(gè)字符,內(nèi)核空間還剩下2個(gè)字符,然后再次碰到select函數(shù),默認(rèn)情況下如果鍵盤內(nèi)核空間字符數(shù)大于1,select是不會(huì)阻塞鍵盤描述符的。結(jié)果也印證了,又read了2個(gè)字節(jié),并沒有堵塞。
綜上所述select是可以看見內(nèi)核空間的緩沖區(qū)的。那到底能不能看見用戶空間緩沖區(qū)呢?我們換成stdio流的i/o函數(shù)繼續(xù)實(shí)驗(yàn)。
--------------------------------------------------------------------
stdio流的i/o函數(shù)使用select函數(shù)的程序如下:

程序用stdio流的getc函數(shù)從鍵盤讀數(shù)據(jù),運(yùn)行結(jié)果如下:

我們輸入5個(gè)字符(1 2 3 4 \n),結(jié)果只輸出了1個(gè)字符,然后就阻塞了。我們分析一下,首先輸入5個(gè)字符,這5個(gè)字符被放入用戶緩沖區(qū),因?yàn)樽詈笠粋€(gè)是換行符并且stdio面對(duì)設(shè)備使用行緩沖機(jī)制,所以這5個(gè)字符馬上接著被從用戶緩沖區(qū)刷入內(nèi)核緩沖區(qū)。然后調(diào)用select函數(shù),select函數(shù)發(fā)現(xiàn)內(nèi)核空間中有數(shù)據(jù),于是不阻塞返回。接著getc函數(shù)從用戶空間輸出緩沖區(qū)取一個(gè)字符,因?yàn)橛脩艨臻g輸出緩沖區(qū)沒有數(shù)據(jù),于是把內(nèi)核空間的數(shù)據(jù)調(diào)入一行給用戶空間輸出緩沖區(qū),然后getc返回。接著又碰上select函數(shù),因?yàn)閮?nèi)核緩沖空間的數(shù)據(jù)已經(jīng)被放入用戶空間輸出緩沖區(qū)了,所以內(nèi)核緩沖沒有數(shù)據(jù),那么select認(rèn)為鍵盤沒有準(zhǔn)備好,所以阻塞。雖然阻塞了,但需要注意的時(shí)候這個(gè)時(shí)候,用戶空間是有4個(gè)字符數(shù)據(jù)的,被select函數(shù)無視了。

接下來假設(shè)我們?cè)佥斎?個(gè)字符(1 \n),將會(huì)發(fā)生什么呢?

輸出一對(duì)東西,這是怎么回事,我們繼續(xù)分析。當(dāng)輸入2個(gè)字符(1 \n)的時(shí)候,內(nèi)核空間緩沖沒有數(shù)據(jù),用戶空間輸出緩沖有4個(gè)字符。2個(gè)字符根據(jù)上一段同樣原理,被刷入內(nèi)核空間緩沖區(qū)。select函數(shù)被調(diào)用,發(fā)現(xiàn)有2個(gè)字符,于是不阻塞返回。getc函數(shù)從用戶輸出緩沖取出一個(gè)字符,打印stardard... --2然后返回。再次循環(huán),調(diào)用select函數(shù)。關(guān)鍵來了,這里跟上次不一樣了。這個(gè)時(shí)候內(nèi)核空間的緩沖中還有上次遺留的2個(gè)字符,所以依然不阻塞返回,調(diào)用getc函數(shù)繼續(xù)打印。。。這里的關(guān)鍵是,getc函數(shù)。getc函數(shù)在用戶輸出緩沖中有數(shù)據(jù)的時(shí)候,不會(huì)把內(nèi)核空間緩沖中的數(shù)據(jù)移入用戶空間的輸出緩沖,使得內(nèi)核空間緩沖一直留有數(shù)據(jù)。這將會(huì)持續(xù)到用戶空間輸出緩沖的數(shù)據(jù)被取完為止。所以上述奇怪的打印結(jié)果就可以解釋的了。
綜上所述,select函數(shù)確實(shí)是看不見用戶空間緩沖的尋在的。
所以如果在使用select函數(shù)的時(shí)候,要謹(jǐn)慎使用stdio流函數(shù)。
今天在看UNP6.5節(jié),學(xué)習(xí)到了select與stdio混用的后果。特此進(jìn)程實(shí)驗(yàn)一番。再實(shí)驗(yàn)之前需明確一下幾點(diǎn):
1.stdio流的i/o函數(shù) 與 系統(tǒng)i/o函數(shù)不同。stdio流函數(shù)在用戶空間和內(nèi)核都有緩沖,系統(tǒng)i/o函數(shù)只在內(nèi)核有緩沖,用戶空間沒有。
2.stdio流的i/o函數(shù)緩沖機(jī)制:在面對(duì)文件時(shí)候用的是全緩沖,面對(duì)設(shè)備的時(shí)候用的行緩沖。(等下試驗(yàn)用的是鍵盤和屏幕),所以實(shí)驗(yàn)用的stdio函數(shù)采用行行緩沖。
3.select函數(shù)對(duì)于某一個(gè)描述符是否準(zhǔn)備好可讀可寫,是對(duì)內(nèi)核緩沖區(qū)中的數(shù)據(jù)是否達(dá)到某一個(gè)最低標(biāo)準(zhǔn),而不是用戶緩沖區(qū)。也就是說select函數(shù)不知道用戶緩沖區(qū)的存在。
首先寫了一個(gè)系統(tǒng)i/o函數(shù) 簡(jiǎn)單的select函數(shù)程序:

程序給select函數(shù)只設(shè)置的鍵盤的描述符。也就是說如果鍵盤的描述符準(zhǔn)備好了就不再阻塞。但是這里有一個(gè)問題,解除阻塞后,我們最多只從內(nèi)核緩沖區(qū)讀3個(gè)字節(jié),這個(gè)時(shí)候就會(huì)有兩個(gè)情況:
(1)內(nèi)核空間本來存儲(chǔ)的數(shù)據(jù)就小于等于3個(gè)字節(jié),全被讀走。那下次再次調(diào)用select函數(shù),應(yīng)該肯定會(huì)阻塞的,因?yàn)殒I盤輸入的內(nèi)核緩沖區(qū)已經(jīng)沒有數(shù)據(jù)了。
情況如下(內(nèi)核空間只有3個(gè)字節(jié):1 2 \n):

(2)如果內(nèi)核空間的數(shù)據(jù)多余3個(gè)字節(jié),但是因?yàn)樽疃嘀荒茏x3個(gè)字節(jié),就必將導(dǎo)致內(nèi)核中有數(shù)據(jù)讀不完。那么下次再遇到select函數(shù)的時(shí)候是否會(huì)阻塞呢?
情況見下:

當(dāng)我們輸入5個(gè)字符時(shí)候(1 2 3 4 \n),第一次read掉3個(gè)字符,內(nèi)核空間還剩下2個(gè)字符,然后再次碰到select函數(shù),默認(rèn)情況下如果鍵盤內(nèi)核空間字符數(shù)大于1,select是不會(huì)阻塞鍵盤描述符的。結(jié)果也印證了,又read了2個(gè)字節(jié),并沒有堵塞。
綜上所述select是可以看見內(nèi)核空間的緩沖區(qū)的。那到底能不能看見用戶空間緩沖區(qū)呢?我們換成stdio流的i/o函數(shù)繼續(xù)實(shí)驗(yàn)。
--------------------------------------------------------------------
stdio流的i/o函數(shù)使用select函數(shù)的程序如下:

程序用stdio流的getc函數(shù)從鍵盤讀數(shù)據(jù),運(yùn)行結(jié)果如下:

我們輸入5個(gè)字符(1 2 3 4 \n),結(jié)果只輸出了1個(gè)字符,然后就阻塞了。我們分析一下,首先輸入5個(gè)字符,這5個(gè)字符被放入用戶緩沖區(qū),因?yàn)樽詈笠粋€(gè)是換行符并且stdio面對(duì)設(shè)備使用行緩沖機(jī)制,所以這5個(gè)字符馬上接著被從用戶緩沖區(qū)刷入內(nèi)核緩沖區(qū)。然后調(diào)用select函數(shù),select函數(shù)發(fā)現(xiàn)內(nèi)核空間中有數(shù)據(jù),于是不阻塞返回。接著getc函數(shù)從用戶空間輸出緩沖區(qū)取一個(gè)字符,因?yàn)橛脩艨臻g輸出緩沖區(qū)沒有數(shù)據(jù),于是把內(nèi)核空間的數(shù)據(jù)調(diào)入一行給用戶空間輸出緩沖區(qū),然后getc返回。接著又碰上select函數(shù),因?yàn)閮?nèi)核緩沖空間的數(shù)據(jù)已經(jīng)被放入用戶空間輸出緩沖區(qū)了,所以內(nèi)核緩沖沒有數(shù)據(jù),那么select認(rèn)為鍵盤沒有準(zhǔn)備好,所以阻塞。雖然阻塞了,但需要注意的時(shí)候這個(gè)時(shí)候,用戶空間是有4個(gè)字符數(shù)據(jù)的,被select函數(shù)無視了。

接下來假設(shè)我們?cè)佥斎?個(gè)字符(1 \n),將會(huì)發(fā)生什么呢?

輸出一對(duì)東西,這是怎么回事,我們繼續(xù)分析。當(dāng)輸入2個(gè)字符(1 \n)的時(shí)候,內(nèi)核空間緩沖沒有數(shù)據(jù),用戶空間輸出緩沖有4個(gè)字符。2個(gè)字符根據(jù)上一段同樣原理,被刷入內(nèi)核空間緩沖區(qū)。select函數(shù)被調(diào)用,發(fā)現(xiàn)有2個(gè)字符,于是不阻塞返回。getc函數(shù)從用戶輸出緩沖取出一個(gè)字符,打印stardard... --2然后返回。再次循環(huán),調(diào)用select函數(shù)。關(guān)鍵來了,這里跟上次不一樣了。這個(gè)時(shí)候內(nèi)核空間的緩沖中還有上次遺留的2個(gè)字符,所以依然不阻塞返回,調(diào)用getc函數(shù)繼續(xù)打印。。。這里的關(guān)鍵是,getc函數(shù)。getc函數(shù)在用戶輸出緩沖中有數(shù)據(jù)的時(shí)候,不會(huì)把內(nèi)核空間緩沖中的數(shù)據(jù)移入用戶空間的輸出緩沖,使得內(nèi)核空間緩沖一直留有數(shù)據(jù)。這將會(huì)持續(xù)到用戶空間輸出緩沖的數(shù)據(jù)被取完為止。所以上述奇怪的打印結(jié)果就可以解釋的了。
綜上所述,select函數(shù)確實(shí)是看不見用戶空間緩沖的尋在的。
所以如果在使用select函數(shù)的時(shí)候,要謹(jǐn)慎使用stdio流函數(shù)。