You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何poll()返回就绪状态后getchar()仍出现阻塞?

问题

我用C语言开发文本编辑器,用poll()判断输入是否就绪(未就绪时执行其他功能)。当有字符可用时(if (!poll(...))为假),调用getchar()读取处理字符。代码如下:

#include <poll.h>
#include <stdio.h>

int main(void) {

        input_set_tty_raw();
        
        char input_history[1000];
        int curr = 0;
        struct pollfd in = {.fd = 0, .events = POLLIN};
        
        while (1) {
                if (!poll(&in, 1, 20)) {  // arbitrary timeout for poll function
                        continue;
                }
                char c = getchar();

                /* problem code */
                if (input_history[curr - 1] == '\033' && c == '[') {
                        while (poll(&in, 1, 0)) {
                                getchar();
                        }
                        return input_restore_tty();
                }

                // handle inputs
                input_history[curr++] = c;
        }

        return input_restore_tty();
}

input_set_tty_raw()等效于执行$ stty raw,支持逐字符处理原始输入;input_restore_tty()用于恢复终端初始状态。

按下箭头键等按键时,stdin会传入特殊转义序列(比如箭头键对应27-91-6x字节序列),这类序列和删除键的转义序列共享\033[前缀。

因此在标记为problem code的条件块中,我检查前一个字符是否为\033且当前字符为[,若是则通过while循环读取剩余输入。测试发现\033和[之间有延迟,后续字符即时传入,所以内层poll()超时设为0,并加入return语句验证输入处理完成时机。

但代码出现阻塞:按下箭头键后程序未按预期返回,按下其他键才返回,说明阻塞发生在problem code块内的getchar()中。

已知poll()仅在有输入可用时返回非零值,为何会出现阻塞?该如何修复?

辅助函数代码:

#include <termios.h>

static struct termios tbufsave;
int input_set_tty_raw(void) {
        struct termios tbuf;
        if (tcgetattr(0, &tbufsave) == -1) {
                return 1;
        }

        tbuf = tbufsave;
        tbuf.c_iflag &= ~(INLCR | ICRNL | ISTRIP | IXON | BRKINT);
        tbuf.c_oflag &= ~OPOST;
        tbuf.c_lflag &= ~(ICANON | ISIG | ECHO);
        tbuf.c_cc[VMIN] = 1;
        tbuf.c_cc[VTIME] = 0;

        return tcsetattr(0, TCSANOW, &tbuf) == -1;   // 1 for error
}

int input_restore_tty(void) {
        return tcsetattr(0, TCSANOW, &tbufsave) == -1;   // 1 for error
}
原因分析

阻塞的核心原因有两点:

  1. 终端属性设置导致getchar()阻塞:你在input_set_tty_raw()中把VMIN设为1、VTIME设为0,这意味着getchar()会一直阻塞,直到至少有1个字符可读。内层poll(&in,1,0)是非阻塞检查,当转义序列的剩余字符还没全部到达终端缓冲区时,poll()返回0,循环终止,但此时如果后续再调用getchar(),就会因为VMIN=1的设置卡死,直到有新输入。
  2. 数组越界的未定义行为:curr初始值为0,第一次读取字符时input_history[curr-1]会访问input_history[-1],属于越界访问,可能干扰程序逻辑。
修复方案

1. 调整终端属性,消除getchar()阻塞

修改input_set_tty_raw()中的VMIN和VTIME,让getchar()变为非阻塞或超时返回:

// 在input_set_tty_raw()中替换原有设置
tbuf.c_cc[VMIN] = 0;  // 允许无字符时立即返回
tbuf.c_cc[VTIME] = 1; // 超时时间0.1秒,可按需调整

此时getchar()在无输入时会返回EOF,不会持续阻塞。

2. 修正转义序列处理逻辑

  • 先判断curr > 0,避免数组越界;
  • 读取剩余转义字符时,直接循环调用getchar()直到EOF,结合调整后的终端属性,不会阻塞:

修改后的问题代码块:

/* fixed code */
if (curr > 0 && input_history[curr - 1] == '\033' && c == '[') {
        char next;
        // 读取所有剩余转义字符,直到无输入
        while ((next = getchar()) != EOF) {
                // 可在此添加转义序列的具体处理逻辑
        }
        return input_restore_tty();
}

3. 优化外层输入读取逻辑

因为VMIN设为0,poll()返回有输入后,可能存在多个待读字符,需要循环读取避免遗漏,同时添加数组越界防护:

修改后的外层循环:

while (1) {
        if (!poll(&in, 1, 20)) {
                continue;
        }
        // 读取所有可用字符
        char c;
        while ((c = getchar()) != EOF) {
                /* fixed code */
                if (curr > 0 && input_history[curr - 1] == '\033' && c == '[') {
                        char next;
                        while ((next = getchar()) != EOF) {
                                // 处理转义序列后续字符
                        }
                        return input_restore_tty();
                }

                input_history[curr++] = c;
                // 防止数组越界
                if (curr >= (int)(sizeof(input_history)/sizeof(input_history[0]))) {
                        curr = (int)(sizeof(input_history)/sizeof(input_history[0])) - 1;
                }
        }
}
完整修正代码
#include <poll.h>
#include <stdio.h>
#include <termios.h>

static struct termios tbufsave;
int input_set_tty_raw(void) {
        struct termios tbuf;
        if (tcgetattr(0, &tbufsave) == -1) {
                return 1;
        }

        tbuf = tbufsave;
        tbuf.c_iflag &= ~(INLCR | ICRNL | ISTRIP | IXON | BRKINT);
        tbuf.c_oflag &= ~OPOST;
        tbuf.c_lflag &= ~(ICANON | ISIG | ECHO);
        tbuf.c_cc[VMIN] = 0;  // 修改为0,允许非阻塞读取
        tbuf.c_cc[VTIME] = 1; // 超时0.1秒

        return tcsetattr(0, TCSANOW, &tbuf) == -1;
}

int input_restore_tty(void) {
        return tcsetattr(0, TCSANOW, &tbufsave) == -1;
}

int main(void) {
        input_set_tty_raw();
        
        char input_history[1000];
        int curr = 0;
        struct pollfd in = {.fd = 0, .events = POLLIN};
        
        while (1) {
                if (!poll(&in, 1, 20)) {
                        continue;
                }

                char c;
                while ((c = getchar()) != EOF) {
                        /* fixed code */
                        if (curr > 0 && input_history[curr - 1] == '\033' && c == '[') {
                                char next;
                                while ((next = getchar()) != EOF) {
                                        // 可在此添加转义序列的具体处理逻辑
                                }
                                return input_restore_tty();
                        }

                        input_history[curr++] = c;
                        if (curr >= (int)(sizeof(input_history)/sizeof(input_history[0]))) {
                                curr = (int)(sizeof(input_history)/sizeof(input_history[0])) - 1;
                        }
                }
        }

        return input_restore_tty();
}

内容的提问来源于stack exchange,提问作者vs07

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:34:51