11、noncanonical模式
Noncanonical模式通过关闭termios结构中的c_lflag域中的ICANON标记来指定。在noncanonical模式,输入的数据不会被收集成行,并且这些字符不会被处理:ERASE, KILL, EOF, NL, EOL, EOL2, CR, REPRINT, STATUS, WERASE。
如我们所说,canonical模式比较简单,系统每次返回一行。但是noncanonical模式中,系统如何知道什么时候给我们返回数据?如果每次返回一个字节,那么开销就会很大。(前面给出过我们一次read一个字节的开销,每次我们将返回的数据量增倍,相应的系统调用的开销就会减半)。系统不能总是每次返回多个字节,因为有时候我们直到我们开始读取之前,我们都无法知道需要读取多少数据。
有一个解决的方法就是,当已经读取了指定数目的数据或者过了一个指定的时间之后,告诉系统返回。这个技术使用两个变量,它们存放在termios结构中的c_cc数组中:MIN和TIME。这两个数组的元素通过名称VMIN和VTIME被索引到。
MIN指定了一个read返回之前的最小字节数目。TIME指定了等待数据到达的每个10分之一秒的数目。主要有4种情况:
-
Case A: MIN > 0, TIME > 0
TIME指定了一个交互字节计时器,这个计时器只有在第一个字节被接收到的时候会被启动。如果在计时器超时之前,MIN字节的数据被读取到了,那么read会返回MIN字节。如果计时器在接收到MIN个字节之前超时了,那么read会返回当前已经读取的字节数目。(如果超时了,那至少会返回1个字节,因为计时器在接收到一个字节的时候才会被启动)这个情况下,调用者会阻塞,直到收到第一个字节,如果调用read的时候数据已经可用,那么就好象read之后数据立即被接收到了。
-
Case B: MIN > 0, TIME == 0
read不会返回,直到MIN字节被接收到。这会导致read处于一种永远阻塞的状态。
-
Case C: MIN == 0, TIME > 0
TIME指定了一个读取计时器,这个计时器会在read被调用的时候启动。(和case A进行比较会发现,case A中的非零TIME表示一个交互字节计时器,这个计时器只有接收到第一个字节的时候才会被启动),read会在接收到一个单个字节的时候返回,或者计时器超时的时候返回。如果计时器超时了,那么read返回0。
-
Case D: MIN
= 0, TIME =
0如果一些数据是可用的,那么read返回请求的字节的数目。如果没有数据可用,那么read会立即返回0。
需要注意的是,对于以上这些情况,MIN只是一个最小值。如果应用程序请求的数据大于MIN字节,那么会可能接收到所有可能请求的数据。这也是C和D中的情况,即C和D中的MIN都是0。
下面的图表列出了noncanonical输入下的四种情况。这个图中,nbytes就是read的第3个参数(也就是返回的最大字节数目)。
noncanonical输入模式中的四种情况
MIN > 0 MIN == 0
+------------------------------------+------------------------------+
| A:read returns [MIN,nbytes] | C:read returns [1,nbytes] |
| before timer expires; | before timer expires; |
TIME > 0 | read returns [1,MIN) | read returns 0 |
| if timer expires. | if timer expires. |
| (TIME= interbyte timer | (TIME= read timer.) |
| Caller can block indefinitely.) | |
+------------------------------------+------------------------------+
| B:read returns [MIN,nbytes] | D:read returns [0,nbytes] |
TIME == 0 | when available. | immediately. |
| (Caller can block indefinitely.) | |
+------------------------------------+------------------------------+
我们需要注意的是POSIX.1允许下标VMIN和VTIME分别和VEOF与VEOL的值一样。其实,Solaris为了兼容以前的老版本的System V,就是这样做的。这导致了一个移植的问题。当从noncanonical转换到canonical模式的时候,我们现在必须也恢复VEOF和VEOL,如果VMIN和VEOF一样,并且我们没有恢复它们的值,那么当我们设置VMIN为经常使用的1的时候,end-of-file字符就变成了Control-A。回避这个问题的最简单的方法就是当进入到noncanonical模式的时候保存整个termios结构,回到canonical模式的时候恢复它。
例子
下面的程序定义了tty_cbreak和tty_raw函数,这函数设置终端为cbreak模式和raw模式(cbreak和raw来自终端驱动的版本7)。我们可以调用函数tty_reset来重新设置终端为它的原始模式(也就是调用这些函数之前的状态)。
网友评论