当前位置:嗨网首页>书籍在线阅读

09-非规范模式

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

62.6.2 非规范模式

一些应用程序(例如vi和less)在用户没有提供行终止符时也需要从终端中读取字符。非规范模式正是用于这个目的。在非规范模式下(关闭ICANON标志)不会处理特殊的输入。特别的一点是:输入不再装配成行,相反会立刻对应用程序可见。

在什么情况下一个非规范模式下的read()调用会完成?我们可以指定非规范模式下的read()调用在经历了一段特定的时间后,或者在读取了特定数量的字节后,又或者是两者兼有的情况下终止执行。termios结构体中的c_cc数组里有两个元素可用来决定这种行为:TIME和MIN。元素TIME(通过常量VTIME来索引)以十分之一秒为单位来指定超时时间。元素MIN(通过VMIN来索引)指定了被读取字节数的最小值。(MIN和TIME的设置对规范模式下的终端I/O不产生任何影响。)

参数MIN和TIME的精确操作和交互取决于它们各自是否包含有非零值。下面介绍了4种情况。注意,在所有4种情况中,如果在read()调用过程中已经读取了足够的字节数来满足MIN的要求,那么read()会立刻返回可用的字节数和所请求的字节数中较小的那个值。

MIN == 0,TIME == 0 (轮询读取)

如果在调用过程中有数据可用,那么read()将立刻返回可用的字节数和所请求的字节数中较小的那个值。如果没有数据可用,read()将立刻返回0。

这种情况可服务于一般的轮询请求,允许应用程序以非阻塞的方式检查输入是否存在。这种模式有些类似于为终端设定 O_NONBLOCK 标志(见5.9节)。但是,在设定O_NONBLOCK标志后,如果没有数据可读,那么read()会返回−1,伴随的错误码为EAGAIN。

MIN > 0,TIME == 0(阻塞式读取)

这种情况下read()会阻塞(有可能永远阻塞下去),直到请求的字节数得到满足或者读取到了MIN个字节,此时就返回这两者中较小的那一个。

像less这样的程序一般会将MIN设为1,而把TIME设为0。这使得程序不用在轮询中忙等从而浪费CPU时间,只要用户按下单个按键read()就能返回了。

如果将一个终端置于非规范模式,且将MIN设为1,TIME设为0,那么可以采用63章中描述的技术来检查用户是否已经在终端上输入了一个字符(而不是一整行)。

MIN == 0,TIME > 0(带有超时机制的读操作)

这种情况下当调用read()时会启动一个定时器。当至少有1字节可用,或者当经历了TIME个十分之一秒后,read()会立刻返回。在后一种情况下read()将返回0。

这种情况对同串行设备(比如调制解调器)打交道的程序来说很有用。程序可以发送数据给设备然后等待响应。假如设备没有响应,采用超时机制就能避免程序永远挂起。

MIN > 0,TIME > 0 (既有超时机制又有最小读取字节数的要求)

当输入的首个字节可用后,之后每接收到一个字节就重启定时器。如果满足读取到了MIN个字节,或者请求的字节数已经读取完毕,此时read()会返回两者间较小的那个值。或者当接收连续字节之间的时隙超过了TIME个十分之一秒,此时read()会返回0。由于定时器只会在初始字节可用后才启动,因此至少可以返回1字节。(这种情况下read()可能会永远阻塞下去。)

这种情况对于处理生成转义序列的终端按键十分有用。比如,在许多终端上,左箭头键产生的3字符序列由退格再加上 OD 组成。这些字符被连续快速地传输。应用程序在处理这样的字符序列时需要区分到底是用户按下了一个这样的按键还是自己慢慢地单独输入了这3个字符呢?这可以通过执行一次带有短超时的read()调用来解决,比方说将超时时间定为0.2秒。有一些版本的vi采用这种技术用在了它的命令模式上。(根据超时时间的长短,在这种应用中,我们可能需要通过快速输入前面提到的那个 3 字符序列来模拟出按下左箭头的情况。)

以可移植的方式修改并恢复MIN和TIME

历史上,某些UNIX的实现是互相兼容的。SUSv3中允许 VMIN 和 VTIME 的值可以分别等同于 VEOF 和 VEOL,这就意味着termios结构体中c_cc数组里的这些元素可能会产生冲突。(在Linux上,这些常量的值是各不相同的。)这种冲突是可能产生的,因为VEOF和VEOL在非规范模式下是不使用的。VMIN和VEOF可能有着相同的值,这一事实意味着进入非规范模式后程序需要特别谨慎,设置了MIN的值(通常为1)之后再返回到规范模式下。此时,EOF就不再是其之前的值 4 了(Ctrl-D)。有一种可移植的方法能够解决这个问题,可以在切换到非规范模式之前先保存一份 termios 设置的副本,然后使用这个保存的副本返回到规范模式下。