C 迷你系列(六)select 与 stdio 混用所带来的问题

引言

在 《UNIX 网络编程》一书 135 页的末尾提到关于 select 与 stdio 相关函数混用的问题。这里我把它单独拿出来,以一个简单的例子说明一下。避免之后的使用中出现类似的问题。

问题根源

两者的缓冲区:

  • 系统 I/O 在内核空间中存在缓冲,而在用户空间没有;
  • stdio 系列函数除了在内核空间中有缓存,在用户空间也有缓冲;

缓冲区类型:

  • 全缓冲(大部分缓冲都是这类型)
  • 行缓冲(例如:stdio、stdout)
  • 无缓冲(例如:stderr)

而具体的问题则是出现在 select 只会检测内核空间中的缓冲区,无法感知用户空间中的缓冲区。当数据从内核空间复制到用户空间的时候,即使该描述符对应的缓存空间有数据,select 也不会再给通知。如图:


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] 字节,内容为:[%s]\n", n, buffer);
        memset(buffer, 0, sizeof(buffer));
    }
}

--- input
123456

--- output
读取到:[2] 字节,内容为:[12]
读取到:[2] 字节,内容为:[34]
读取到:[2] 字节,内容为:[56]
读取到:[1] 字节,内容为:[
]

📚 Tips

我们分配 3 字节大小的缓冲区,然后再每次读取玩缓冲中的数据之后,将缓冲中的数据清空,避免影响输出。当我们输入:123456 并按回车换行时(实际:123456\n),内容依次输出了。最后的 1 字节内容就是最后的换行符。

我们分析一下从我们输出完并按下回车到显示时,都发生了什么:

  1. 输入回车之后,数据从用户缓冲复制到了内核缓冲(行缓冲);
  2. select 检测到 stdin 对应的内核缓冲有数据可读的时候,解除阻塞;
  3. read 函数取 2 个字节的数据到 buffer 中;
  4. printf 将 buffer 中的数据显示出来,并进行下次循环,阻塞到 select;
  5. 由于内核中还有数据未读完,select 再次解除阻塞,直至数据取完为止;
  • 混用时的问题

#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("内容为:[%c]\n", n);
    }
}

---
intput: 123456
output: 内容为:[1]
intput: 9
output: 内容为:[2]
output: 内容为:[3]
output: 内容为:[4]
output: 内容为:[5]
output: 内容为:[6]
output: 内容为:[
output: ]
output: 内容为:[9]

我们发现输出已经出现问题了,我们继续分析一下该问题是怎么造成的:

  1. 当我们输入 123456 之后,数据由用户空间缓冲复制到了内核缓冲;
  2. select 检测到有数据可读,解除阻塞;
  3. getc 函数从用户缓冲中取 1 字节数据,发现缓冲中无数据可读,于是将内核中的数据复制到用户缓冲,并取 1 字节作为输出;
  4. 此时由于数据已经全部复制到了用户缓冲,所以 select 进入阻塞状态(即使用户空间的缓冲中有数据可读);
  5. 当输出 9 并回车时,该数据又被复制到了内核空间(行缓冲),select 解除阻塞;
  6. getc 函数从用户缓冲中取出 1 字节数据输出(由于用户缓冲中有数据,所以 getc 便不会再从内核中复制数据);
  7. 由于内核中有数据,所以 select 便再解除阻塞,getc 再取 1 字节直到 9 被复制到用户缓冲并输出为止;

📚 Tips

仔细看最后的输出,你会发现 9 之后的换行符还留在用户空间缓冲中,该数据只能等下次再有数据输出到内核空间中才会得到输出。

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容

  • 多线程和多进程的应用场景 多线程模型适用于I/O密集型场景,因为I/O密集型场景因为I/O阻塞导致频繁切换,线程只...
    雪上霜阅读 574评论 0 0
  • 作者: 一字马胡 转载标志 【2018-03-27】 更新日志 日期更新内容备注2018-03-27回顾以前的知...
    一字马胡阅读 496评论 0 3
  • Java是一门跨平台的语言,在运行时通过Java虚拟机调用操作系统的相关系统函数,也就是说底层都是操作系统的相关程...
    史圣杰阅读 411评论 0 0
  • 几年前的一个下午,公司里码农们正在安静地敲着代码,突然很多人的手机同时“哔哔”地响了起来。本来以为发工资了,都挺高...
    AI乔治阅读 739评论 0 7
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,537评论 28 53