C 迷你系列(六)select 與 stdio 混用所帶來的問題

引言

在 《UNIX 網(wǎng)絡(luò)編程》一書 135 頁的末尾提到關(guān)于 select 與 stdio 相關(guān)函數(shù)混用的問題唐础。這里我把它單獨(dú)拿出來,以一個(gè)簡單的例子說明一下。避免之后的使用中出現(xiàn)類似的問題贷洲。

問題根源

兩者的緩沖區(qū):

  • 系統(tǒng) I/O 在內(nèi)核空間中存在緩沖,而在用戶空間沒有晋柱;
  • stdio 系列函數(shù)除了在內(nèi)核空間中有緩存优构,在用戶空間也有緩沖;

緩沖區(qū)類型:

  • 全緩沖(大部分緩沖都是這類型)
  • 行緩沖(例如:stdio雁竞、stdout)
  • 無緩沖(例如:stderr)

而具體的問題則是出現(xiàn)在 select 只會(huì)檢測內(nèi)核空間中的緩沖區(qū)钦椭,無法感知用戶空間中的緩沖區(qū)。當(dāng)數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間的時(shí)候,即使該描述符對應(yīng)的緩存空間有數(shù)據(jù)彪腔,select 也不會(huì)再給通知侥锦。如圖:


image.png

示例

  • 正常輸出

#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>

#define BUFFER 3
#define BUFFER_LEN (BUFFER - 1)

int main()
{
    int n;
    fd_set rset;
    char buffer[BUFFER];
    FD_ZERO(&rset);
    for (;;)
    {
        FD_SET(fileno(stdin), &rset);
        select(fileno(stdin) + 1, &rset, NULL, NULL, NULL);
        n = read(fileno(stdin), buffer, BUFFER_LEN);
        printf("讀取到:[%d] 字節(jié),內(nèi)容為:[%s]\n", n, buffer);
        memset(buffer, 0, sizeof(buffer));
    }
}

--- input
123456

--- output
讀取到:[2] 字節(jié)德挣,內(nèi)容為:[12]
讀取到:[2] 字節(jié)恭垦,內(nèi)容為:[34]
讀取到:[2] 字節(jié),內(nèi)容為:[56]
讀取到:[1] 字節(jié)盲厌,內(nèi)容為:[
]

?? Tips

我們分配 3 字節(jié)大小的緩沖區(qū)署照,然后再每次讀取玩緩沖中的數(shù)據(jù)之后,將緩沖中的數(shù)據(jù)清空吗浩,避免影響輸出建芙。當(dāng)我們輸入:123456 并按回車換行時(shí)(實(shí)際:123456\n),內(nèi)容依次輸出了懂扼。最后的 1 字節(jié)內(nèi)容就是最后的換行符禁荸。

我們分析一下從我們輸出完并按下回車到顯示時(shí),都發(fā)生了什么:

  1. 輸入回車之后阀湿,數(shù)據(jù)從用戶緩沖復(fù)制到了內(nèi)核緩沖(行緩沖)赶熟;
  2. select 檢測到 stdin 對應(yīng)的內(nèi)核緩沖有數(shù)據(jù)可讀的時(shí)候,解除阻塞陷嘴;
  3. read 函數(shù)取 2 個(gè)字節(jié)的數(shù)據(jù)到 buffer 中映砖;
  4. printf 將 buffer 中的數(shù)據(jù)顯示出來次和,并進(jìn)行下次循環(huán)抖韩,阻塞到 select端考;
  5. 由于內(nèi)核中還有數(shù)據(jù)未讀完径筏,select 再次解除阻塞棍厌,直至數(shù)據(jù)取完為止童本;
  • 混用時(shí)的問題

#include <stdio.h>
#include <sys/select.h>

int main()
{
    int n;
    fd_set rset;
    FD_ZERO(&rset);
    for (;;)
    {
        FD_SET(fileno(stdin), &rset);
        select(fileno(stdin) + 1, &rset, NULL, NULL, NULL);
        n = getc(stdin);
        printf("內(nèi)容為:[%c]\n", n);
    }
}

---
intput: 123456
output: 內(nèi)容為:[1]
intput: 9
output: 內(nèi)容為:[2]
output: 內(nèi)容為:[3]
output: 內(nèi)容為:[4]
output: 內(nèi)容為:[5]
output: 內(nèi)容為:[6]
output: 內(nèi)容為:[
output: ]
output: 內(nèi)容為:[9]

我們發(fā)現(xiàn)輸出已經(jīng)出現(xiàn)問題了原探,我們繼續(xù)分析一下該問題是怎么造成的:

  1. 當(dāng)我們輸入 123456 之后柴底,數(shù)據(jù)由用戶空間緩沖復(fù)制到了內(nèi)核緩沖秒拔;
  2. select 檢測到有數(shù)據(jù)可讀莫矗,解除阻塞;
  3. getc 函數(shù)從用戶緩沖中取 1 字節(jié)數(shù)據(jù)砂缩,發(fā)現(xiàn)緩沖中無數(shù)據(jù)可讀作谚,于是將內(nèi)核中的數(shù)據(jù)復(fù)制到用戶緩沖,并取 1 字節(jié)作為輸出梯轻;
  4. 此時(shí)由于數(shù)據(jù)已經(jīng)全部復(fù)制到了用戶緩沖食磕,所以 select 進(jìn)入阻塞狀態(tài)(即使用戶空間的緩沖中有數(shù)據(jù)可讀);
  5. 當(dāng)輸出 9 并回車時(shí)喳挑,該數(shù)據(jù)又被復(fù)制到了內(nèi)核空間(行緩沖)彬伦,select 解除阻塞滔悉;
  6. getc 函數(shù)從用戶緩沖中取出 1 字節(jié)數(shù)據(jù)輸出(由于用戶緩沖中有數(shù)據(jù),所以 getc 便不會(huì)再從內(nèi)核中復(fù)制數(shù)據(jù))单绑;
  7. 由于內(nèi)核中有數(shù)據(jù)回官,所以 select 便再解除阻塞,getc 再取 1 字節(jié)直到 9 被復(fù)制到用戶緩沖并輸出為止搂橙;

?? Tips

仔細(xì)看最后的輸出歉提,你會(huì)發(fā)現(xiàn) 9 之后的換行符還留在用戶空間緩沖中,該數(shù)據(jù)只能等下次再有數(shù)據(jù)輸出到內(nèi)核空間中才會(huì)得到輸出区转。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載苔巨,如需轉(zhuǎn)載請通過簡信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末废离,一起剝皮案震驚了整個(gè)濱河市侄泽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜻韭,老刑警劉巖悼尾,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肖方,居然都是意外死亡闺魏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門俯画,熙熙樓的掌柜王于貴愁眉苦臉地迎上來析桥,“玉大人,你說我怎么就攤上這事艰垂∨牍牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵材泄,是天一觀的道長。 經(jīng)常有香客問我吨岭,道長拉宗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任辣辫,我火速辦了婚禮旦事,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘急灭。我一直安慰自己姐浮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布葬馋。 她就那樣靜靜地躺著卖鲤,像睡著了一般肾扰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛋逾,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天集晚,我揣著相機(jī)與錄音,去河邊找鬼区匣。 笑死偷拔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亏钩。 我是一名探鬼主播莲绰,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姑丑!你這毒婦竟也來了蛤签?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤彻坛,失蹤者是張志新(化名)和其女友劉穎顷啼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昌屉,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钙蒙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了间驮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躬厌。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖竞帽,靈堂內(nèi)的尸體忽然破棺而出扛施,到底是詐尸還是另有隱情,我是刑警寧澤屹篓,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布疙渣,位于F島的核電站,受9級(jí)特大地震影響堆巧,放射性物質(zhì)發(fā)生泄漏妄荔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一谍肤、第九天 我趴在偏房一處隱蔽的房頂上張望啦租。 院中可真熱鬧,春花似錦荒揣、人聲如沸篷角。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恳蹲。三九已至虐块,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阱缓,已是汗流浹背非凌。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荆针,地道東北人敞嗡。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像航背,于是被迫代替她去往敵國和親喉悴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 多線程和多進(jìn)程的應(yīng)用場景 多線程模型適用于I/O密集型場景玖媚,因?yàn)镮/O密集型場景因?yàn)镮/O阻塞導(dǎo)致頻繁切換箕肃,線程只...
    雪上霜閱讀 574評(píng)論 0 0
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2018-03-27】 更新日志 日期更新內(nèi)容備注2018-03-27回顧以前的知...
    一字馬胡閱讀 494評(píng)論 0 3
  • Java是一門跨平臺(tái)的語言,在運(yùn)行時(shí)通過Java虛擬機(jī)調(diào)用操作系統(tǒng)的相關(guān)系統(tǒng)函數(shù)今魔,也就是說底層都是操作系統(tǒng)的相關(guān)程...
    史圣杰閱讀 411評(píng)論 0 0
  • 幾年前的一個(gè)下午错森,公司里碼農(nóng)們正在安靜地敲著代碼吟宦,突然很多人的手機(jī)同時(shí)“嗶嗶”地響了起來。本來以為發(fā)工資了涩维,都挺高...
    AI喬治閱讀 737評(píng)論 0 7
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月殃姓,有人笑有人哭,有人歡樂有人憂愁瓦阐,有人驚喜有人失落蜗侈,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評(píng)論 28 53