#Linux串口驱动程序-termios结构体中的VTIME和VMIN应用
参考
https://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html
最近在看Unix-linux系统编程一书,做到第六章的时候需要做一个标准输入的超时处理。如下图所示
需要改动的,是原书的play_again3.c,代码如下所示
功能就是,终端问你一句话(ASK),只有输入大小写的yn才可以跳出
两秒以内没有输入(超时),会蜂鸣器BEEP一下,由于我是虚拟机里面做实验,我改成打印一句oligay,最多超时三次。
终端有如下修改
- 取消了回显(你摁的键不会再显示出来)
- 进入了非常规模式(没有了buffer缓冲区,终端你摁一个键,马上就会输入进去。常规模式下需要摁enter,缓冲区的信息才会输入到程序中。这只是其中一个改变。。。)
- 设置VMIN等于1,一次只获取一个字符,这个键,后面讲
标准输入文件有如下的修改
- 开启O_NDELAY标志,关闭文件描述符的阻塞状态。
- 读取不到数据或是写入缓冲区已满会马上return
#include <stdio.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>#define ASK "Do you want another transaction"
#define TRIES 3
#define SLEEPTIME 2
#define BEEP puts("oligay")int get_response( char *, int );
void set_cr_noecho_mode();
void set_nodelay_mode();
int tty_mode( int how );
char get_ok_char();
void ctrl_c_handler(int);int main()
{int response;tty_mode(0);//保存本来的串口设置,以及保存标准输入的flagsset_cr_noecho_mode();//设置串口驱动程序set_nodelay_mode();//设置标准输入非阻塞response = get_response(ASK, TRIES);tty_mode(1);//恢复本来的设置return response;}
int get_response(char* question, int maxtries)
/** purpose: ask a question and wait for a y/n answer or maxtries* method : use getchar and ignore non y/n answers* returns: 0 => yes, 1 => no*/
{int input;printf( "%s (y/n)?", question);fflush(stdout); //force outputwhile (1) {sleep(SLEEPTIME); //等两秒input = tolower( get_ok_char() );if( input == 'y' )return 0;if( input == 'n' )return 1;if ( maxtries-- == 0)return 2;BEEP;}
}
void set_cr_noecho_mode()
/* purpose:put fd 0 into chr-by-chr mode* method change bits in termios*/
{struct termios ttystate;tcgetattr( 0, &ttystate);ttystate.c_lflag &= ~ICANON; //no bufferingttystate.c_lflag &= ~ECHO; //no echo eitherttystate.c_cc[VMIN] = 1; //输入一个字符,read就会返回,一次获取一个字符tcsetattr( 0, TCSANOW, &ttystate);
}
int tty_mode( int how )//save original tty settings and set
{static struct termios original_mode;static int original_flags;if( how == 0 ){tcgetattr( 0, &original_mode );original_flags = fcntl( 0, F_GETFL );}else{tcsetattr( 0, TCSANOW, &original_mode);fcntl( 0, F_SETFL, original_flags );}return 1;
}
char get_ok_char()
{char c;c = getchar();while( ( c = getchar() ) != EOF && strchr( "yYnN", c ) == NULL );return c;
}
void set_nodelay_mode()
{int termflags;termflags = fcntl(0, F_GETFL);termflags |= O_NDELAY;fcntl( 0, F_SETFL, termflags );
}
但是这样设置是有问题,非阻塞虽然允许了读入为空,但
- 调用getchar之前睡两秒,就算1s内输入,还是得等两秒
- 如果等待输入同时,按了ctrl+c,会触发SIGINT信号,导致进程中止,此时标准输入仍然处于非阻塞,shell调用read读命令行,什么都读不到,直接返回0。shell也会注销~~(但我的shell不会)~~。
#改为设置 VMIN 和 VTIME实现
设置这两个位,会有其他十分有用的用处
读取tty时,需要考虑timing因素,与读文件不同,因为读串口时不一定会有数据
比如以下场景
-
读取功能键,像F7,上箭头这种键是输入一个序列的字符,如下所示都是ESC键加 一堆序列,程序要分开功能键或者ESC,就是看输入ESC后,是不是有数据紧跟其后
- F7 - ESC [ 18 ~
- page up ESC [ 5 ~
-
高速输入,比如从modem读数据,需要知道什么时候输入超时
-
获取偶发,低量数据,
比如监控串行总线上的温度传感器,这些10-20个byte的数据没有明显的结束符,如果采用读取固定大小的数据,可能下一个温度信息会有少量的byte读不到,失去与传感器的同步性,设置最少
不同设置方法的不同作用
- VMIN = 0 and VTIME = 0
read函数会变为非阻塞模式。 - VMIN = 0 and VTIME > 0
纯计时读入,有数据读数据,没数据,计时,如果VTIME毫秒后仍无数据,read返回0。 - VMIN > 0 and VTIME > 0
如果VMIN byte的数据被read读入,或者读入第一个byte后计时超时,read都会返回。计时器只会在读入第一个byte后进行计时,没有数据输入的话就会导致阻塞。注意 VMIN = 1的话,计时器是没用的。 - VMIN > 0 and VTIME = 0
读够VMIN byte数据,read 返回,无超时处理,会阻塞
因此,最后的答案,其实设置VTIME = 20,VMIN 不设置就好了,答案不贴了,第一次写,不太熟悉Markdown,见谅