select() 响应标准输入但不响应 /dev/tty

select() responds to stdin but not to /dev/tty

这是一个与 select() does not seem to work on TTY 非常相似的问题,但我似乎没有在 FD_SET 上犯与那里的 OP 相同的错误。

在我的 Linux 框中(运行 Ubuntu 20.04 LTS for Desktop),我试图在两种阻塞模式下从控制台读取一个无缓冲的字符(调用 fgetc()无条件)和非阻塞模式(反复询问select()是否值得调用fgetc())。

如果我正在阅读 stdin,则两种模式都有效。但是,如果我想独立于 stdin 是否已重定向而从控制台显式读取,我的理解是我应该改为打开 "/dev/tty"。这也适用于阻塞模式,但不适用于非阻塞模式:select() 在每次超时结束时都会返回 0,尽管疯狂按键。我不确定为什么 stdin/dev/tty 在这方面会有不同的表现。他们的 termios 标志看起来是一样的。

$ gcc foo.c

$ ./a.out
c_iflag = 25862
c_oflag = 5
c_cflag = 191
c_lflag = 35387

Well?? 
read character 'g' in blocking mode from stdin

We're waiting... 0 0 
read character 'g' in non-blocking mode from stdin

$ ./a.out /dev/tty
c_iflag = 25862
c_oflag = 5
c_cflag = 191
c_lflag = 35387

Well?? 
read character 'g' in blocking mode from /dev/tty

We're waiting... 0 0 0 0 0 0 0 0 0 0 
too slow: read nothing in non-blocking mode from /dev/tty

$ ggggggggggg

如果我尝试使用 /dev/console/dev/tty0,我只会失败并返回 fopen() 中的空值 FILE *。这是来源 foo.c。我错过了什么?

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

int main( int argc, const char * argv[] )
{
    
    const char * name = "stdin";
    FILE * filePointer = stdin;
    int fileDescriptor;
    struct timeval timeout;
    struct termios term;
    fd_set rdset;
    int result, iCycle, nCycles = 10;
    char c;

    if( argc > 1 ) /* open the user-specified file instead of stdin */
    {
        name = argv[ 1 ];
        filePointer = fopen( name, "r" );
    }
    if( !filePointer ) return fprintf( stderr, "failed to open %s", name );
    
    fileDescriptor = fileno( filePointer );

    /* make things unbuffered, non-canonical, non-echoey */
    setvbuf( filePointer, NULL, _IONBF, 0 );
    tcgetattr( fileDescriptor, &term );
    printf("c_iflag = %d\n", term.c_iflag);
    printf("c_oflag = %d\n", term.c_oflag);
    printf("c_cflag = %d\n", term.c_cflag);
    printf("c_lflag = %d\n", term.c_lflag);
    term.c_lflag &= ~ICANON;
    term.c_lflag &= ~ECHO;
    tcsetattr( fileDescriptor, TCSANOW, &term );
    
    /* get an unbuffered character (blocking mode) */
    fprintf( stdout, "\nWell?? " );
    fflush( stdout );
    c = fgetc( filePointer );
    fprintf( stdout, "\nread character '%c' in blocking mode from %s\n", c, name );
    fflush( stdout );

    /* get an unbuffered character (non-blocking mode) */
    fprintf( stdout, "\nWe're waiting... " );
    fflush( stdout );
    for( iCycle = 0; iCycle < nCycles; iCycle++ )
    {
        FD_ZERO( &rdset );
        FD_SET( fileDescriptor, &rdset );
        timeout.tv_sec  = 1;
        timeout.tv_usec = 0;
        result = select( 1, &rdset, NULL, NULL, &timeout );
        if( result > 0 ) break;
        fprintf( stdout, "%d ", result );
        fflush( stdout );
        if( result > 0 ) break;
    }
    if( iCycle < nCycles )
    {
        c = fgetc( filePointer );
        fprintf( stdout, "\nread character '%c' in non-blocking mode from %s\n", c, name );
        fflush( stdout );
    }
    else
    {
        fprintf( stdout, "\ntoo slow: read nothing in non-blocking mode from %s\n", name );
        fflush( stdout );
    }
    
    /* restore canonicality (avoid having to type `reset` after every run) */
    term.c_lflag |= ICANON;
    term.c_lflag |= ECHO;
    tcsetattr( fileDescriptor, TCSANOW, &term );
    if( filePointer != stdin ) fclose( filePointer );
    return 0;
}

问题是 select() 的第一个参数。 select() 只检查每个 FD 集中的前 nfds 个描述符。由于您指定了 1,它只检查 FD 0 上的输入,而忽略用于 /dev/tty.

的 FD

改为:

select(fileDescriptor+1, &rdset, NULL, NULL, &timeout );