第11章 终端I/O
11?1〓引言? 在操作系统中,终端I/O处理是个非常繁琐的部分,Unix也不例外。在很多版本 的Unix手 册中,终端I/O的手册页常常是最长部分之一。? 在七十年代后期,Unix终端I/O处理发展成两种不同的风格。一种是系统Ⅲ在V ersion 7 的基础上进行了很多改变而形成的,这种风格由系统Ⅴ沿续下来;另一种则是Ver sion 7的 风格,它正成为贝克莱类系统的标准组成部分。如同信号一样,POSIX.1在这两种 风格的基 础上制定了终端I/O标准。本章将介绍POSIX.1的终端函数,以及SVR4和4?3+B SD的增加 部分。? 终端I/O的用途很广泛,包括:终端、计算机之间的直接连接、调制解调器、打 印机等等 ,所以它就变得非常复杂。在后面的若干章中,我们开发了两个例示终端I/O的 程序:一 个是与Postscript打印机进行通信(第十七章),另一个是涉及调制解调器以及远程 的计算机 登录(第十八章)。? 11?2〓综述? 终端I/O有两种不同的工作方式:? 1?规范方式输入处理。在这种方式中,终端输入以行为单位进行处理。对于每个 读要求, 终端驱动程序最多返回一行。? 2?非规范方式输入处理。输入字符不以行为单位进行装配。? 如果不作特殊处理,则规范方式是默认方式。例如:若Shell的标准输入、输出是 终端,在 用read和Write将标准输入复制到标准输出时,则终端以规范方式进行工作,每次 read最多 返回一行。处理整个屏幕的程序,例如Ui编辑程序,使用非规范方式,其原因是其 命令是由 不以新行符终止的一个或几个字符组成的。另外,该编辑程序使用了若干特殊字符 作为编辑 命令,所以它也不希望系统对特殊字符进行处理。例如,Control-D字符通常是终 端的文件 结束符,但在Ui中它是向下滚动半个屏幕的命令。? Version 7和BSD类的终端驱动程序支持三种终端输入方式:? (a)精细加工方式(Cooked mode)(输入装配成行,并对特殊字符进行处理),(b)原 始方式(输 入不装配成行,也不对特殊字符进行处理),(c)cbreak方式输入不装配成行,但对 某些特殊 字符进行处理)。程序11?10显示了将终端设置为cbreak或原始方式的POSIX?1函 数? POSIX?1定义了11个特殊输入字符,我们可以改变其中9个。在本章中已经用到了 其中几个 字符,例如:文件结束符(通常是:Control-D),挂起字符(通常是:Control-Z)。 ? 11?3节对其中每个字符都进行说明。? 终端设备是由一般位于系统核内的终端驱动程序所控制的。每个终端设备有一个输 入队列, 一个输出队列,这如图11?1中所示。? 图11?1〓终端设备的输入、输出队列的逻辑结构? 对此图要说明下列几点:? · 如果需要回送,则在输入队列和输出队列之间有一个隐含的连接。? · 输入队列的长度MAX-INPUT(见图2?5)是有限值,当一个特定设备的输入队列已 经填满时 ,系统作何种处理是依赖于实现的。当此发生时,大多数Unix系统回送响铃字符。 ? · 在图中没有显示另一个输入限制MAX-CANON,它是在一个规范输入行中的最大字 节数。? · 虽然输出队列通常也是有限长度,但是程序不能存取定义其长度的常数。这是 因为当输 出队列要填满时,系统核使写进程睡眠直至写队列中有可用的空间,所以程序无需 关心该队 列的长度。? · 我们将说明如何使用tcflush函数刷清输入或输出队列。与此相类似,在说明t csetallr 函数时,我们将会了解到如何通知系统仅在输出队列空时改变一个终端的属性。( 例如,正 在改变输出属性时可能就要这样做。)我们也能通知系统,当它正在改变终端属性 时,丢弃 在输入队列中的任何东西。(如果我们正在改变输入属性,或者在规范和非规范方 式之间进 行转换,则可能希望这样做,以免以错误的方式对以前输入的字符进行解释。)?
大多数Unix系统在一个称为终端行规程的模块中进行规范处理。它是位于系统核类 属读、写 函数和实际设备驱动程序之间的模块,这示于图11?2中。? 图11?2〓终端行规程? 在12?4节中讨论流I/O系统以及在第十九章讨论伪终端时还将使用此图。? 所有我们可以检测和更改的终端设备特性都包含在termios结构中。该结构在头文 件s.h>中定义,本章经常使用这一头文件。? struct termios {? tcflag-t c-iflag;〓〓〓/*输入标志*/? tcflag-t c-iflag;〓〓〓/*输出标志*/? tcflag-t c-iflag;〓〓〓/*控制标志*/? tcflag-t c-iflag;〓〓〓/*本地标志*/? cc-t c-cc[NCCS];/*控制字符*/? };? 粗略而言,输入标志由终端设备驱动程序用来控制输入特性(剥除输入字节的第8位 ,允许输 入奇偶校验等等),输出标法则控制输出特性(执行输出处理,将新行映照为CR/LF 等),控制 标志影响到RS-232串行线(忽略调制解调器的状态线,每个字符的一个或二个停止 位等等), 本地标志影响驱动程序和用户之间的界面(回送的开或关,虚拟的擦除符,允许终 端产生的 信号,对后台作业输出的控制停止信号等)。? 类型tcflag-t的长度是以保持每个标志值。它经常被定义为unsigned long。c-cc 数组包含 了所有我们可以更改的特殊字符。NCC3是该数组的长度,其典型值在11-18之间(大 多数Unix 实现支持的特殊字符较POSIX?1所定义的11个更多)。cc-t类型的长度是以保持每 个专用字 符,它典型的是unsigned char。? 系统V的早期版本有一个名为的头文件,一个名为termio的数据结构, 为了区别 于这些名字,POSIX?1在这些名字后加了一个s。? 图11?3列出了所有我们可以更改以影响终端设备特性的终端标志。注意,POSIX? 1定义的 标志是SVR4和4?3+BSD都支持的,但是它们还各有自己的扩充部分。这些扩充部分 与这两个 系统的各自历史发展过程有关。在11?5节中将详细讨论这些标志值。? 给出了图11?3中的所有选择项后,如何再能检测和更改终端设备的这些特性呢?图 11?4摘 要列出了POSIX?1所定义的对终端设备进行操作的各个函数。(在9?7节中已说明 了tcgetpg rp和tcsetpgrp函数)。? 图11?3〓终端标志? 图11?4〓POSIX?1终端I/O函数摘要? 注意,对终端设备,POSIX?1没有使用ioctl,代之它使用了图11?4中示出的12个 函数。所 以这样做的理由是:对于终端设备的ioctl函数,其最后一个参数的数据类型随执 行动作的 不同而不同。? 虽然只有12个函数对终端设备进行操作,但是应当理解的是,图11?4中头两个函 数tcgetat tr和tcsetattr处理大约50种不同的标志(图11?3)。对于终端设备有大量选择项可 供使用, 对于一个特定设备(终端、调制解调器、激光打印机等等)又要决定所需的选择项, 这些都使 对终端设备的处理变得复杂起来。? 图11?4中列出的12个函数之间的关系示于图11?5中。? 图11?5〓与终端有关的函数之间的关系? POSIX?1没有规定在termios结构中何处存放波特率信息,这是一个依赖于实现的 特性。很 多较早的系统将此信息存放在c-cflag字段中。4?3+BSD则在此结构中有两个分开 的字段〖 CD2〗一个存放输入速度,另一个则存放输出速度。? 11?3〓特殊输入字符? POSIX?1定义了11个在输入时作特殊处理的字符。SVR4另外加了6个特殊字符,4? 3+BSD则 加了7个。图11?6摘要列出了这些特殊字符。? 图11?6〓终端特殊输入字符摘要? 在POSIX?1的11个特殊字符中,我们可将其中9个更改为几乎任何值。不能更改的 两个特殊 字符是新行符和回车符(?\?n和?\?r),有些实施也不允许更改STOP和START字 符。为了 进行修改,只要更改termios结构中,c-cc数组的相应项。该数组中的元素都用名 字作为下 标进行引用,每个名字都以字母V开头(见图11?6中的第3列)。? POSIX?1可选地允许我们禁止使用这些字符。若-POSIX-VDISABLE有效,则-POSIX -DISABLE 的值可存放在c-cc数组的相应项中的禁止使用该特殊字符。可以用pathconf和fpa theonf函 数查询此特征(2?5?4节)。? FIPS151-1要求支持-POSIX-VDISABLE。? SVR4和4?3+BSD也支持此特性。SVR4将-POSIX-VDISABLE定义为0,而4?3+BSD则将 其定义为 八进制数377。? 某些早期的Unix系统所用的方法是:若相应的特殊输入字符是O,则禁止使用该字 符。? 实例? 在详细说明各特殊字符之前,先看一个更改特殊字符的程序。程序1?1禁用中断字 符,并将 文件结束符设置为Control-B。? P332〓? 程序11?1〓禁止中断字符和更改文件结束字符? 对此程序要说明下列几点:? 1?仅当标准输入是终端设备时才修改终端特殊字符。isattg(见图11?9节)用于进 行这种检 测。? 2?用fpathconf取-POSIX-VDISABLE值。? 3?函数tcgetattr(11?4节)从系统核存取termios结构。在修改了此结构后,调用 tcsetatt r设置属性,这样就进行了我们所希望的修改。? 4?禁止使用中断键与忽略中断信号是不同的。程序11?1所做的是禁止使用使终端 驱动程序 产生SIGINT的特殊字符。我们仍可使用kill函数向进程发送该信号。? 下面较详细地说明各个特殊字符。我们称这些字符为特殊输入字符,但是其中有两 个字符, STOP和START(Control-S和Control-D)在输出时也对它们进行特殊处理。注意,这 些字符中 的大多数在被终端驱动程序识别并进行特殊处理后都被丢弃,并不将它们传送给执 行读终端 操作的进程。例外的字符是新行符(NL,EOL,EOL2)和回车符(CR)。? CR〓POSIX?1的回车符。不能更改此字符。以规范方式进行输入时识别此字符。当 设置了IC ANON(规范方式)和ICRNL(将CR映照为NL)以及没有设置IGNCR(忽略CR)时,将CR转换 成NL,并 产生与NL符相同的作用。? 此字符返回给读进程(多半是在转换成NL后)。? DISCARD〓SVR4和4?3+BSD的删除符。在扩充方式下(IEXTEN),在输入中识别此字 符。在输 入另一个删除符之前或删除文件被清除之前(见FLUSHO选项)此字符使后续输出都被 删除。在 处理后此字符即被删除,不送向读进程。? DSUSP〓SVR4和4?3+BSD的延迟-挂起作业控制字符。在扩充方式下,若作业控制被 挂起并且 ISIG标志被设置,则在输入中识别此字符。与SUSP字符的相同处是:延迟-挂起字 符产生SIG TSTP信号,它被送至前台进程组中的所有进程(见图9?7)。但是并不是键入此字符 时,而是 在一个进程读控制终端时,此延迟-挂起字符才送向进程组。在处理后,此字符即 被删除, 不送向读进程。? EOF〓POSIX?1的文件结束符。以规范方式进行输入时识别此字符。当键入此字符 时,等待 被读的所有字节都立即传送给读进程。如果没有字节等待读,则返回O。在行首输 入一个EOF 符是向程序指示文件结束的正常方式。在处理后,此字符即被删除,不送向读进程 。? EOL〓POSIX?1附加的行定界符,与NL作用相同。以规范方式进行输入时识别此字 符。? 通常不使用此字符。此字符返回给读进程。? EOL2〓SVR4和4?3+BSD的附加行定界符与NL作用相同。以规范方式输入时识别此字 符。? ERASE〓POSIX?1的擦除字符(退格)。以规范方式输入时识别此字符。它擦除行中 的前一个 字符,但不会超越行首字符擦除上一行中的字符。在处理后此字符即被擦除,不送 向读进程 。? INTR〓POSIX?1的中断字符。若设置了ISIG标志,则在输入中识别此字符。它产生 SIGINT信 号,该信号被送至前台进程组中的所有进程(见图9?7)。在处理后,此字符即被删 除,不送 向读进程。? KILL〓POSIX?1的kill(消灭)字符。(名字"消灭"在这里又一次被误用,它应被称 为行擦 除符。)以规范方式输入识别此字符。它擦除整个1行。在处理后,此字符即被删除 ,不送向 读进程。? NL〓POSIX?1的新行字符,它也被称为行定界符。不能更改此字符。以规范方式输 入时识别 此字符。此字符返回给读进程。? QUIT〓POSIX?1的退出字符。若设置了ISIG标志,则在输入中识别此字符。它产生 SIGQUIT 信号,该信号又被送至前台进程组中的所有进程(见图9?7)。在处理后,此字符即 被删除, 水送向读进程。? 回忆图10?1,INTR和QUIT之间的区别是:QUIT字符不仅按默认终止进程,而且也 产生core 文件。? REPRINT〓SVR4和4?3+BSD再打印字符。以扩充规范方式(设置了IEXTEN和ICANON标 志)进行 输入时识别此字符。它使所有末读的输入被输出(再回送)。在处理后,此字符即被 删除,不 送向读进程。? START〓POSIX?1的起动字符。若设置了IXON标志则在输入中识别此字符,若设置 IXOFF标志 ,则作为输出自动产生此字符。在IXON已设置时接收到的START字符使停止的输出 (由以前输 入的STOP字符造成)重新起动。在此情形下,在处理后,此字符即被删除。不送向 读进程。 ? 在XOFF标志设置时,若输入不会使输入缓存溢出,则终端驱动程序自动地产生一S TART字符 以恢复以前被停止的输入。? STATUS〓4?3+BSD的状态-要求字符。以扩充、规范方式进行输入时识别此字符。 它产生SIG INFO信号,该信号又被送至前台进程组中的所有进程(见图9?7)。另外,如果没有 设置NOKE RNINFO标志,则有关前台进程组的状态信息也显示在终端上。在处理后,此字符即 被删除, 不送向读进程。? STOP〓POSIX?1的停止字符。若设置了IXON标志,则在输入中识别此字符,若IXO FF标志已 设置则作为输出自动产生此字符。在IXON已设置时接收到的STOP字符停止输出。在 此情形下 ,在处理后删除此字符,不送向读进程。当输入一个RESTART字符后,停止的输出 重新起动 。? 在IXOFF设置时,终端驱动程序自动地产生一个STOP字符以防止输入缓存溢出。?
SUSP〓POSIX?1挂起作业控制字符。若支持作业控制并且ISIG标志已设置,则在输 入中识别 此字符。它产生SIGTSTP信号,该信号又被送至前台进程组的所有进程(见图9?7) 。在处理 后,此字符即被删除,不送向读进程。? WERASE〓SVR4和4?3+BSD的字擦除字符。以扩充、规范方式进行输入时识别此字符 。它使前 一个字被擦除。首先,它向后跳过任一白空字符(空格或制表符),然后向后越过前 一记号, 使光标处在前一个记号的第一个字符位置上。通常,前一个记号在碰到一个白空字 符时即终 止。但是,可用设置ALTWERASE标志来改变这一点。? 此标志使前一个记号在碰到第一个非字母、数字符时即终止。在处理后,此字符即 被删除, 不送向读进程。? 需要为终端设备定义的另一个"字符"是BREAK。BREAK实际上并不是一个字符,而是 在异步 串行数据传送时发生的一个条件。依赖于串行界面,可以有多种方式通知设备驱动 程序发生 了BREAK条件。大多数终端有一个标记为BREAK的键,用其可以产生BREAK条件,这 就使得很 多人认为BREAK就是一个字符。对于异步串行数据传送,BREAK是一个0值的二进位 序列,其 继续时间长于要求发送一个字节的时间。整个0值二进位序列被视为是一个BREAK。 在11?8 节中将说明如何发送一个BREAK。? 11?4〓获得和设置终端属性? 使用函数tcgetattr和tcsetattr可以获得或设置termios。这样也就可以检测和修 改各种终端选择标志和特殊字符,以使终端按我们所希望的方式进行操作。? #include? int tegetattr(int filedes,struct,termios * termptr);? int tcsetattr(int filedes,int opt,const struct termios * termptr);? 两个函数返回:看成功为0,出错为-1Both return:0if OK,-1 on error? 这两个函数都有一个指向termios结构的指针作为其参数,它们或者返回当前终端 的属性,或者设置该终端的属性。因为这两个函数只对终端设备进行操作,所以若 filedes并不引用一个终端设备则出错返回,error设置为ENOTTY。? tcsetattr的参数opt使我们可以指定在什么时候新的终端属性才起作用。opt可以 指定为下列常数中的一个:? TCSANOW〓更改立即发生。? TCSADRAIN〓发送了所有输出后更改才发生。若更改输出参数则应使用此选项。? TCSAFLUSH〓发送了所有输出后更改才发生。要进一步,在更改发生时未读的所有 输入数据都被删除(刷清)。 tcsetattr函数的返回值易于产生混淆。如果它执行了任何一种所要求的动作,即 使未能执 行,所有要求的动作,它也返回0(表示成功)。如果该函数返回0,则我们有责任检 查该函数 是否执行了所有要求的动作。这就意味着,在调用tcsetattr设置所希望的属性后 ,需调用t cgetattr,然后将实际终端属性与所希望的属性相比较,以检测两者是否有区别。 ? 11?5〓终端选择标志? 本节对图11?3中列出的各个终端选择标志按字母顺序作进一步说明,也指出该选 择项出现 在四个终端标志字段中的哪一个,以及该选择项是否是POSIX?1定义的,或是受到 SVR4或4 ?3+BSD支持的。? 所有列出的选择标志(除屏蔽标志外)都用一个或几个二进制位表示,而屏蔽标志则 定义从个 二进制位。屏蔽标志有一个定义名,每个值也有一个名字。例如,为了设置字符长 度,首先 用字符长度屏蔽标志CSIZE将表示字符长度的各二进制位清0,然后设置下列值之一 :CS5、C S6、CS7或CS8。? 由SVR4支持的6个延迟值也有屏蔽标志:BSDLY、CRDLY、FFDLY、NLDLY、TABDLY和 VTDLY。对 于每个延迟值的长度请参阅termio(7)手册页(AT&T [1999])。如果指定了一 个延迟, 则OFILL和OFDEL标志决定是否终端进行实施延迟或是只是传输填充字符。? 实例? 程序11?2例示了使用屏蔽标志取或设置一个值。? 程序11?1〓tcgetattr的实例? 下面说明各选择标志? ALTWERASE〓(c-lflag,4.3+BSD)此标志设置时,若输入了WERASE字符,则使用一 个替换的 字擦除算法。它不是向后移动到前一个白空字符为止,而是向后移动到第一个非字 母、数字 符为止。? BRKINT〓(c-iflag,POSIX.1)若此标志设置,而IGNBRN末设置,则在接到BREAK时 ,输入、 输出队列被刷清,并产生一个SIGINT信号。如果此终端设备是一个控制终端,则将 此信号送 给前台进程组各进程。? 如果IGNBRK和BRKINT都没有设置,但是设置了PARMRK,则BREAK被读作为三个字节 序列?\ ?377,?\?0和?\?0,如果PARMRK也没有设置,则BREAK被志作为单个字符"? \?0。? BSDLY〓(c-oflag,SVR4)退格延迟屏蔽,此屏蔽的值是BS0或BS1。? CCTS-OFLOW〓(c-cflag,4.3+BSD)输出的CTS流控制(见练习11?4)。? CIGNORE〓(c-cflag,4.3+BSD)忽略控制标志。? CLOCAL〓(c-cflag,POSIX?1)如若设置,则忽略调制解调器状态线。这通常 意味着该 设备是本地连接的。若此标志未设置,则打开一个终端设备常常会阻塞到调制解调 器回应。 ? CRDLY〓(c-oflag,SVR4)回车延迟屏蔽。此屏蔽的值是CR0、CR1,CR2和CR3。? CREAD〓(c-cflag,POSIX?1)如若设置,则接收装置被启用,可以接收字符。? CRTS-IFLOW〓(c-cflag,4.3+BSD)输入的RTS流控制(见练习11?4)。? CSIZE〓(c-cflag,POSIX.1)此字段是一个屏蔽标志,它指明发送和接收的每个字 节的二进 位数。此长度不包括可能有的奇偶校验位。由此屏蔽定义的字段值是CS5、CS6、C S7和CS8, 分别表示每个字节包含5,6,7和8个二进制位。? CSTOPB〓(c-cflag,POSIX.1)如若设置,则使用两个二进制位作为停止位,否则只 使用一个 二进制位作为停止位。? ECHO〓(c-lflag,POSIX.1)如若设置,则将输入字符回送到终端设备。在规范方式 和非规范 方式下都可以回送字符。? ECHOCTL〓(c-lflag,SCR4t 4.3+BSD),如若设置并且ECHO也设置,则除ASCII TAB、 ASCII NL 、START和STOP字符外,其它ASCI控制符(ASCI字符集中的0~037)都被回送为 ^X,其中 ,X是相应控制字符代码值加0100所构成的字符。这就意味着ASCII Control-A字符 (01)被回 送为^A。ASCII DELETE字符(0177)是回送为^?。如若此标志未设置,则ASCII控制 字符按其 原样回送。如同ECHO标志,在规范方式和非规范方式下此标志对控制字符回送都起 作用。? 应当了解的是:某些系统回送EOF字符产生的作用有所不同,其原因是EOF的典型值 是Contro l-D,而这是ASCII EOT字符,它可能使某些终端挂断。请查看有关手册。? ECHOE〓(c-lflag,POSIX?1)如若设置并且ICANON也设置,则ERASE字符从显示中 擦除当前 行中的最后一个字符。这通常是在终端驱动程序中写三个字符序列:退格,空格, 退格实现 的。? 如若支持WERASE,则ECHOE用一个或若干个上述三字符序列擦除前一个字。? 如若支持ECHOPRT标志,则在这里所说明的ECHOE动作是认为ECHOPRT标志没有设置 。? ECHOK〓(c-lflag,POSIX.1)如若设置并且ICANON也设置,则KILL字符从显示中擦除 当前行, 或者输出NL字符(以强调已擦除整个行)。如若支持ECHOKE标志,则这里的说明是认 为ECHOKE 标志没有设置。? ECHOKE〓(d-lflag,SVR4和4?3+BSD)如若设置并且ICANON也设置,则回送KILLP字 符的方式 是擦去行中的每一个字符。擦除每个字符的方法则由ECHOE和ECHOPRT标志选择。?
ECHONL〓(c-lflag,POSIX.1)如若设置并且ICANON也设置,即使没有设置ECHO也回 送NL字符 。? ECHOPRT〓(c-lflag,SCR4和4?3+BSD)如若设置并且ICANON和IECHO也都设置,则E RASE字符( 以及WERASE字符,若受到支持)使所有正被擦除的字符按它们被擦除的方式打印。 在硬拷贝 终端上这常常是有用的,这样可以确切地看到哪些字符正被擦去。? FFDLY〓(c-oflag,SCR4)换页延迟屏蔽。此屏蔽标志值是FF0或FF1。? FLUSHO〓(c-lflag,SCR4和4?3+BSD)如若设置,则刷清输出。当键入DISCARD字符 时设置此 标志,当键入另一个DISCARD字符时,此标志被清除。设置或清除此终端标志也可 设置或清 除此条件。? HUPCL〓(c-cflag,POSIX.1)如若设置,则当最后一个进程关闭此设备时,调制解调 器控制线 降至低电平(也就是调制解调器的连接断开)。? ICANON〓(c-lflag,POSIX.1)如若设置,则按规范方式工作(11?10节)。这使下列 字符起作 用:EOF、EOL、EOL2、ERASE、KILL、REPRINT,STATUS和WERASE。输入字符被装配 成行。? 在至少接到MIN个字节或已超过TIME值之前,read将不返回。详细情况见11?11节 。? CRNL〓(c-iflag,POSIX.1)如若设置并且IGNCR未设置,即将接收到的CR字符转换 成一个NL 字符。? IEXTEN〓(e-lflag,POSIX.1)如若设置,则识别并处理扩充的、实现定义的特殊字 符。? IGNBRK〓(c-iflag,POSIX.1)在设置时,忽略输入中的BREAK条件。关于BREAK条件 是产生信 号还是被读作为数据,请见BRKINT。? IGNCR〓(c-iflag,POSIX.1)如若设置,忽略接收到的CR字符。若此标志未设置,而 设置了IC RNL标志则将接收到的CR字符转换成一个NL字符。? IGNPAR〓(c-iflag,POSIX.1)在设置时,忽略带有结构错误(非BREAK)或奇偶错的 输入字节 。? IMAXBEL〓(c-iflag,SVR4和4?3+BSD)当输入队列满时响铃。? INLCR〓(c-iflag,POSIX.1)如若设置,则接收到的NL字符转换成CR字符。? INPCK〓(c-iflag,POSIX.1)当设置时,使输入奇偶校验起作用。如若未设置INPC K,则使输 入奇偶校验不起作用。? 奇偶"产生和检测"和"输入奇偶性检验"是不同的两件事。奇偶位的产生和检测是由 PARE NB标志控制的。设置该标志后使串行界面的设备驱动程序对输出字符产生奇偶位, 对输入字 符则验证其奇偶性。标志PARODD决定该奇偶性应当是奇还是偶。如果一个其奇偶性 为错的字 符已经来到,则检查INPCK标志的状态。若此标志已设置,则检查IGNPAR标志(以决 定是否应 忽略带奇偶错的输入字节),若不应忽略此输入字节,则检查PARMRK标志以决定向 读进程应 传送那种字符。? ISIG〓(c-lflag,POSIX.1)如若设置,则判别输入字符是否是要产生终端信号的特 殊字符(IN TR,QUIT,SUSP和DSUSP),若是,则产生相应信号。? ISTRIP〓(c-iflag,PUSIX.1)当设置时,有效输入字节被剥离为7个两进制位。当此 标志未设 置时,则保留全部8位。? IUCLC〓(c-iflag,SVR4)将输入的大写字符映照为小写字符。? IXANY〓(c-iflag,SVR4和4?3+BSD)使任一字符都能重新起动输出。? IXOFF〓(c-iflag,POSIX.1)如若设置,则使起动-停止输入控制起作用。当终端驱 动程序发 现输入队列将要填满时,它输出一个STOP字符。此字符应当由发送数据的设备识别 ,并使该 设备暂停。此后,当已对输入队列中的字符进行了处理后,该终端驱动程序将输出 一个STAR T字符,使该设备恢复发送数据。? IXON〓(c-iflag,POSIX.1)如若设置,则使起动-停止输出控制起作用。当终端驱动 程序接收 到一个STOP字符时,输出暂停。在输出暂停时,下一个START字符恢复输出。如若 未设置此 标志,则START和STOP字符由进程读作为一般字符。? MDMBUF〓(c-cflag,4.3+BSD)按照调制解调器的戴波标志进行输出流控制。? NLDLY〓(c-oflag,SVR4)新行延迟屏蔽。此屏蔽的值是NL0和NL1。? NOFLSH〓(c-lflag,POSIX.1)按系统默认,当终端驱动程序产生SIGSUSP信号和SIG QUIT信号 时,输入、出队列都被刷新。另外,当它产生SIGSUSP信号时,输入队列被刷新。 如若设置 了NOFLSH标志,则在这些信号产生时,不对输入、出队列进行刷新。? NOKERNINFO〓(c-lflag,4.3+BSD)当设置时,此标志阻止STATUS字符使前台进程组 的状态信 息显示在终端上。但是不论本标志是否设置,STATUS字符使SIGINFO信号送至前台 进程组中 的所有进程。? OCRNL〓(c-oflag,SVR4)如若设置,将输出的CR字符映照为NL。? OFDEL〓(c-oflag,SVR4)如若设置,则输出填充字符是ASCII DEL,否则它是ASCI I L,见OF ILL标志。? OFILL〓(c-oflag,SVR4)如若设置,则为实现延迟,发送填充字符(ASCII DEL或AS CII NUL, 见OFDEL标志),而不使用时间延迟。见6个延迟屏蔽:BSDLY,CRDLY,FFDLY,NLD LY,TABDL Y以及VTDLY。? OLCUC〓(c-oflag,SCR4)如若设置,将小写字符映照为大写。? ONLCR〓(c-oflag,SVR4和4?3+BSD)如若设置,将输出的NL字符映照为CR-NL。? ONLRET〓(c-oflag,SVR4)如若设置,则输出的NL字符将执行回车功能。? ONOCR〓(c-oflag,SVR4)如若设置,则在0列不输出CR。? ONOEOT〓(c-oflag,4.3+BSD)如若设置,则在输出中删除EOT字符(^D)。在将Contr ol-D解释 为挂断的终端上这可能是需要的。? OPOST〓(c-oflag,POSIX.1)如若设置,则进行实现定义的输出处理。关于c-oflag 字的各种 实现定义标志,见图11?3。? OXTABS(c-oflag,4?3+BSD)如若设置,制表符在输出中被扩展为空格。这与将水 平制表延 迟(TABDLY)设置为XTABS或TAB3产生同样效果。? PARENB〓(c-cflag,POSIX.1)如若设置,则对输出字符产生奇偶位,对输入字符则 执行奇偶 性检验。若PARODD已设置,则奇偶校验是奇校验,否则是偶校验。也见INPCK、IG NPAR和PAR MRK标志部分。? PARMRK〓(c-iflag,POSIX.1),当设置时,并且IGNPAR未设置,则结构性错(非BRE AK)和奇偶 错的字节由进程读作为三个字符序列?\?377,?\?0和X,其中X是接收到的具有 错误的字节。如若ISTRIP未设置,则一个有效的?\?377被传送给进程时为\377,? \377。如若IGNPAR和PARMR都未设置,则结构性错和奇偶错的字节都被读作为一个 字符?\0。? PARODD〓(c-cflag,POSIX.1)如若设置,则输出和输入字符的奇偶性都是奇。否则 奇偶性为偶。注意,PARENB标志控制奇偶性的产生和检测。? PENDIN〓(c-lflag,SCR4和4?3+BSD)如若设置,则在下一个字符输入时,尚未读 的任何输入都由系统重新打印。这一动作与我们键入REPRINT字符时的作用相类似。? TABDLY〓(c-oflag,SVR4)水平制表延迟屏蔽。此屏蔽的值是TAB0、TAB1、TAB2或T AB3。? XTABS的值等于TAB3。此值使系统将制表符扩展成空格。系统假定制表符所扩展的 空格数到屏幕上最近一个8的倍数处为止。我们不能更改此假定。? TOSTOP(c-lflag,POSIX.1)如若设置,并且该实现支持作业控制,则将信号SIGTTO U送到试图 与控制终端的一个后台进程的进程组。按默认,此信号暂停该进程组中所有进程。 如果写控制终端的进程忽略或阻直线信号,则终端驱动程序不产生此信号。? VTDLY〓(c-oflag,SCR4)垂直制表延迟屏蔽。此屏蔽的值是VT0或VT1。? XCASE〓(c-lflag,SCR4)如若设置,并且ICANON也设置,则认为终端是大写终端, 所以输入 都变换为小写。为了输入一个大写字符,在其前加一个'?\?'。相类似,输出一 个大写 字符也在其前加一个'?\?'。(这一标志已经过时,现在几乎所有终端都支持大、 小写字 符)。? 11?6〓stty命令? 上节说明的所有选择项,在程序中都可用tcgetattr和tcsetattr函数(11?4节)进 行检查和 更改。在命令行中则用stty(1)命令进行检查和更改。stty(1)命令是图11?4中所 列的头6个 函数的界面。如果以-a选项执行此命令,则显示所有终端选择项:? $ stty -a? speed 9600 baud;34 rows;80 columns;? lflags:icanon isig iexten echo echoe echok echoke-echon1 echoct1 -echop rt -altwe rase -noflsh -tostop -mdmbuf -flusho -pendin -nokerninfo -extproc? iflags:istrip icrn1 -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk brk int -inpc k -ignpar -parmrk? oflags:opost onlcr -oxtabs? cflags:cread cs7 parenb -parodd hupcl -clocal -cstopb -crtscts? cchars:discard=^O;dsusp=^Y;eof=^D;eol=;? eol2=;erase=^H;intr=^?;kill=^U;inext=^V;? quit=^?\?;reprint=^R;start=^Q;status=^t;stop=^S;? susp=^Z;werase=^W;? 若在选项名前有一个连字符,表示该选项禁用。最后四行显示各终端特殊字符的设 置(11?3 节)。第1行显示当前终端窗口的行数和列数,我们将在11?12节中对此进行讨论。 ? 因为stty命令是一条用户命令,而不是一个操作系统函数,所以它由POSIX?2说明 。? 系统V的stty在标准输入进行操作,将任何输出写到标准输出上。Version 7和BSD 系统则在 标准输出上进行操作,将任何输出写到标准出错文件上。POSIX?2的最近草案采用 系统V的 方法,4?3+BSD也这样做。? Version 7的stty手册页是1页,SVR4版本的stty手册页则是6页。终端驱动程序趋 向于使用 愈来愈多的选择项。? 11?7〓波特率函数? 波特率(baud rate)是一个历史沿用的术语,现在它指的是"每秒二制位数"。虽然 大多数 终端设备对输入和输出使用同一波特率,但是只要硬件许可,可以将它们设置为两 个不同值 。? # include? speed-t cfgetispeed9const struct termios * termptr);? speed-t cfgetospeed(const struct termios *termptr);? Both return:baud rate value? 两个函数返回:波特率值? int cfsetispeed(struct termios *termptr,speed-t speed);? int cfsetospeed(struct termios *termptr,speed-t speed;? Both return:0if oK,-1 onerror? 两个函数返回:若成功为0,出错为-1。? 两个cfget函数的返回值,以及两个cfset函数的speed参数都是下列常数之一:?
B50、B75、B110、B134、B150、B200、B300、B600 、B12 00、B1800、B2400、B4800、B9600、B19200或B3 8400 。常数B0表示"挂断"。在调用tcsetattr时将输出波特率指定为B0,则调制解调器 的控制 线就不再起作用。? 为了使用这些函数应当理解输入、出波特率是存放在图11?5所示的设备termios结 构中的。 在调用任一cfget函数之前,先要用tcgetattr获得设备的termios结构。与此类似 ,在调用 任一cfset函数后,应将波特率设置到termios结构中。为使这种更改影响到设备, 应当调用 tcsetattr函数。? 如果所设置的波特率有错,则在调用tcsetattr之前,不会发现这种错误。? 1?8〓行控制函数? 下列四个函数提供了终端设备的行控制能力。其中,参数filedes引用一个终端设 备,否则 出错返回,erno设置为ENOTTY。? # include? int tcdrain(int filedes);? int tcflow(int filedes,int action);? int tcflush(int filedes,int queue);? int tcsendbreak(int filedes,int duration);? All four return:0 if OK,-1 on error? 四个函数返回:若成功为0,出错为-1? tcdrain函数等待所有输出都被发送。tcflow使我们对输入和输出流控制进行控制 。action 参数应当是下列四个值之一。? TCOOFF〓输出被挂起。? TCOON〓以前被挂起的输出被重新起动。? TCIOFF〓系统发送一个STOP字符。这将使终端设备暂停发送数据。? TCION〓系统发送一个START字符。这将使终端恢复发送数据。? tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或 输出缓存( 用户程序已经写,但尚未发送)。queue参数应当是下列三个常数之一:? TCIFLUSH〓刷清输入队列。? TCOFLUSH〓刷清输出队列。? TCIOFLUSH〓刷清输入、输出队列。? tcsendbreak函数在一个指定的时间区间内发送连续的0二进位流。若duration参数 为0,则 此种发送延续0?25-0?5秒之间。POSIX.1说明若duration非0,则发送时间依赖于 实现。? SCR4 SVID说明若duration非0,则不发送0二进位。但是,SCR4手册页中说,若du ration非0 ,则tcsendbreak的行为与tcdrain一样。另一个系统手册页则说,若duration非0 ,则传送0 二进位的时间是duration X N,其中N在0?25-0?5秒之间。从中可见,如何处理 这种条件 还会统一样式。? 11?9〓终端标识? 历史沿袭至今,在大多数Unix系统中,控制终端的名字是/dev/tty。POSIX?1提 供了一个 运行时函数,可被调用来决定控制终端的名字。? # include? char * ctermid(char *ptr);? Returns:(see following)? 返回:见下? 如若ptr是非null,则它被认为是一个指针,指向长度至少为L-ctermid字节的数组 ,进程的 控制终端名存放在该数组中。常数L-ctermid定义在中。若ptr是一个空 指针,则 该函数为数组(通常作为静态变量)分配空间。同样,进程的控制终端名存放在该数 组中。? 在这两种情况中,该数组的起始地址作为函数值返回。因为大多数Unix系统都使用 /dev/tty 作为控制终端名,所以此函数的主要作用是帮助提高向其它操作系统的可移植性。 ? 实例〖CD2〗ctermid函数? 程序11?3是POSIX?1 ctermid函数的一个实现。? P346? 程序11?3〓POSIX?1 ctermid函数的实现? 另外两个与终端标识有关的函数是isatty和ttyname。如果文件描述符引用一个终 端设备, 则isatty返回真,而ttyname则返回在该文件描述符上打开的终端设备的路径名。 ? #include? int isatty(int filedes);? Returns:1(true)if terminal device,0(false)otherwise? 返回:若为终端设备为1(真),否则为0(假)? char * ttyname(int filedes);? Returns:pointer to pathname of terminal,NULL on error? 返回:指向终端路径名的指针,出错为NULL? 实例〖CD2〗isatty函数? 如程序11?4所示,isatty函数是很容易实现的。其中只使用了一个终端专用的函 数tcgetat tr,并取其返回值。? P346? 程序11?4〓POSIX?1 isatty函数的实现? P347? 程序11?5〓测试isatty函数? 我们用程序11?5测试isatty函数,得到:? $ a.out? fd 0:tty? fd 1:tty? fd 2:tty? $ a.out /dev/null? fd 0:not a tty? fd 1:tty? fd 2:not a tty? 实例〖CD2〗ttyname函数? ttyname函数(程序11?6)稍长一点,因为它要搜索所有设备表项,寻找正配项。其 方法是读 /dev目录,寻找具有相同设备号和i-node编号的表项。回忆4?23节,每个文件系 统有一个 唯一的设备号(stat结构中的st-dev字段,见4?2节),文件系统中的每个目录项有 一个唯一 的i-node号(stat结构中的st-ino字段)。在此函数中我们假定当找到一个匹配的设 备号和匹 配的i-node号时,就找到了所希望的目录项。我们也可验证这二个表项与st-rdev 字段(终端 设备的主、次设备号)相匹配,以及该目录项是一个字符特殊文件。但是,因为我 们已经验 证了文件描述符参数是一个终端设备以及一个字符特殊设备,而且在Unix系统中, 匹配的设 备号和i-node号是唯一的,所以不再需要作另外的比较。? 用程序11?7测试这一实现。运行程序11?7得到:? $ a.out /dev/null? fd 0/dev/console? fd 1:/dev/ttyp3? fd 2:not a tty? P348? 程序11?6〓POSIX?1 ttyname函数的实现? P349? 程序11?7〓测试Hyname函数? 11?10〓规范方式? 规范方式是简单的〖CD2〗我们发一个读请求,当一行已经输入后,终端驱动程序 即返回。 许多条件造成读返回。? · 所要求的字节数已读到时读即返回。我们无需读一个完整的行。如果读了部分 行,那么 也不会丢失任何信息〖CD2〗下一次读从前一次读的停止处开始。? · 当读到一个行定界符时,读返回。回忆11?3节,在规范方式中,下列字符被解 释为"行 结束":NL、EOL、EOL2和EOF。另外,在11?5节中也曾说明,如若已设置ICRNL, 但未设置 IGNCR,则CR字符的作用与NL字符一样,所以它也终止一行。? 在这五个行定界符中,其中只有一个EOF符在终端驱动程序对其进行处理即被删除 。其它四 个字符则作为该行的最后一个字符返回调行者。? · 如果捕捉到信号而且该函数并不自动再起动(10?5节),则读也返回。? 实例〖CD2〗getpass函数? 下面说明一个函数getpass,它读入一个用户在终端上键入的口令字。此函数由Un ix login( 1)和crypt(1)程序调用。为了读口令字,该函数必须禁止回送,但仍可使终端以规 范方式进 行工作,因为用户在键入口令字后,一定要按回车键,这样也就构成了一个完整行 。程序11 ?8是一个典型的Unix实现。? P350? 程序11?8〓getpass函数的实现? 在此例中,有很多点应当考虑。? · 调用函数ctermid打开控制终端,而不是直接将/dev/tty写在程序中。? · 我们只是读、写控制终端,如果不能以读、写方式打开此设备则出错返回。在 有些系统 中也使用一些其它约定。在4?3+BSD中,如果不能以读、写方式打开控制终端,则 getpass 从标准输入读,写到标准出错文件中。SVR4则总是写到标准出错文件中,但只从控 制终端读 。? · 我们阻塞两个信号SIGINT和SIGTSTP。如果不这样做,则在输入INTR字符时就会 使程序终 止,并使终端仍处于禁止回送状态。与此相类似,输入SUSP字符时将使程序暂停, 并且在禁 止回送状态下返回到shell。在禁止回送时,我们选择了阻塞这两个信号。在读口 令字期间 如果发生了这两个信号,则它们被保持,直到getpass返回前才解除对它们的阻塞 。也有其 它方法来处理这些信号。某些getpass版本忽略SIGINT(保存它以前的动作),在返 回前则将 其动作恢复为以前的值。这就意味着在该信号被忽略期间所发生的这种信号都丢失 。其它版 本捕捉SGINT(保存它以前的动作),如果捕捉到此信号,则在复置终端状态和信号 动作后, 用kill函数发送此信号。没有一个getpass版本捕捉、忽略或阻塞SIGQUIT,所以键 入QUIT字 符就会使程序夭折,并且极可能终端仍处于禁止回送状态。? · 要了解某些shell,例如Kornshell在以交互方式输入时都使终端处于回送状态 。这些she ll是提供命令行编辑的shell,因此在每次输入一条交互命令时都处理终端状态。 所以如果 在这种shell下调用此程序,并且用QUIT字符使其夭折,则这种shell可以恢复回送 状态。不 提供命令行编辑的shell,例如Bourne shell和Cshell将使程序夭折,并使终端仍 处于不回 送状态。如果我们对终端做了这种操作,则stty命令能使终端回复到回送状态。?
· 我们使用标准I/O读、写控制终端。我们特地将流设置为不带缓存的,否则在流 的读、写 之间可能会有某些相互作用(这样我们就需调用fflush)。我们也可使用不带缓存的 I/O(第三 章),但是在这种情况下就要用read来实现getc。? · 我们最多只取8个字符作为口令字。输入的多余字符则被忽略。? 程序11?9调用getpass并且打印我们所输入的。这只是为了验证ERASE和KILL字符 在正常工 作(如同它们在规范方式下应该的那样)。? 程序11?9〓调用getpass函数? 调用getpass函数的程序完成后,为了安全起见,应清除存放过用户键入的文本口 令字的存 储区。如果该程序会产生其他用户能坊的core文件(回忆10?2节,core的系统默认 许可权使 每个用户都能读它),或者如果某个其它进程设法能够读该进程的存储空间,则它 们就能读 到口令字。? 11?11〓非规范方式? 将termios结构中c-lflag字段的ICANON标志关闭就使终端处于非规范方式。在非规 范方式中 ,输入数据不装配成行。不处理下列特殊字符:ERASE、KILL、EOF、NL、EOL、EO L2、CR、R EPRINT、STATUS和WERASE。? 如前所述,规范方式是容易的〖CD2〗系统每次返回一行。但在非规范方式下,系 统怎样才 能知道在什么时候将数据返回给我们呢?如果它一次返回一个字节,那么系统开销 就很大。( 回忆图3?1,从中可以看到每次读一个字节的开销会多大。每次使返回的数据加倍 ,就使系 统调用的开销减半。)在起动读数据之前,我们往往不知道要读多少数据,所以系 统不能总 是返回多个字节。? 解决方法是:当已读了指定量的数据后,或者已经过了给定量的时间后,即通知系 统返回。 这种技术使用了termios结构中c-cc数组的两个变量:MIN和TIME。C-CC数组中的这 两个元素 的下标名为:VMIN和VTIME。? MIN说明一个read返回前的最小字节数。TIME说明等待数据到达的分秒数(秒的1/ 10为分 秒)。有下列四种情形:? A:MIN>0,TIME>0? TIME说明一个字节间的计时器,在接到第一个字节时才起动它。在该计时器超时之 前,若已 接到MIN个字节,则read返回MIN个字节。如果在接到MIN个字节之前,该计时器已 超时,则r ead返回已接收到的字节(因为只有在接到第一个字节时才起动,所以在计时器超时 时,至少 返回1个字节)。在这种情形中,在接到第一个字节之前,调用者阻塞。如果在调用 read时数 据已经可用,则这如同在read后,数据立即被接收到一样。? B:MIN>0,TIME==0? 已经接到了MIN个字节时,read才返回。这可以造成会限期的阻塞read。? C:MIN==0,TIME>0? TIME指定了一个调用read时起动的读计时器。(与A相比较,两者是不同的)。在接 到1个字节 或者该计时器超时时,read即返回。如果是计时器超时,则read返回0。? D:MIN==0,TIME==0? 如果有数据可用,则read最多返回所要求的字节数。如果无数据可用,则read立即 返回0。 ? 在所有这些情形中,MIN只是最小值。如果程序要求的数据多于MIN个字了,那是它 是可能接 收到所要求的字节数的。这也适用于MIN==0的情形A和B。? 图11?7摘要列出非规范方式下的四种不同情形。在图中,nbytes是read的第三个 参数(返回 的最大字节数)。? P353? 图11?7〓非规范输入的四种情形? POSIX?1允许下示VIME和VTIME的值分别与VEOF和VEOL相同。确实,SVR4就是这样 做的。这 样就提供了向系统V早期版本的兼容性。问题是从非规范方式转换为规范方式时, 必须恢复V EOF和VEOL,如果不这样做,那么VMIN等于VEOF,并且它已被设置为典型值1,于是 文件结束 字符就变成Control-A。解决这一问题的最简方法是:在转入非规范方式时将整个 termios结 构保存起来。在以后再转回规范方式时恢复它。? 实例? 程序11?10定义了函数tty-break和tty-raw,它们将终端分别设置为cbreak和raw (原始)方 式(术语cbreak和raw来自于Version 7的终端驱动程序)。tty-reset函数的功能是 将终端恢 复为以前的工作方式。其中还提供了另外两个函数:tty-atexit,tty-termios。t ty-atexit 可被登记为终止处理程序,以保证exit恢复终端工作方式。tty-termios则返回一 个指向原 先的规范方式termios结构的指针。在第十八章的调制解调器拨号程序中将使用所 有这些函 数。? P355? 程序11?10〓将终端方式设置为原始或cbreak方式? 我们对cbreak方式的定义是:? · 非规范方式。如本节开始处所述,这种方式不对某些输入特殊字符进行处理。 这种方式 仍对信号进行处理,所以用户可以键入任一终端产生的信号。调用者应当捕捉这些 信号,否 则这种信号就可能终止程序,并且终端将仍处于cbreak方式。? 作为一般规则,在写更改终端方式的程序时,应当捕捉大多数信号,以便在程序终 止前恢复 终端方式。? · 关闭回送(ECHO)标志? · 每次输入一个字节。为此将MIN设置为1,将TIME设置为0。这是图11?7中的情 形B。至少 有一个字节可用时,read再返回。? 我们对原始方式的定义是:? · 非规范方式。另外,还关闭了对信号产生字符(ISIG)和扩充输入字符的处理(I EXTEN)。 关闭BRKINT,这样就使BREAK字符不再产生信号。? · 关闭回送(ECHO)标志。? · 关闭ICRNL、INPCK、ISTRIP和IXON标志。于是:不再将输入的CR字 符变换为 NL(ICRNL),使输入奇偶校验不起作用(INPCK),不再剥夺输入字节的第八位(STRI P),不进 行输出流控制(IXON)。? · 八位字符(DS8),不产生奇偶位,不进行奇偶性检测(PARENB)。? · 禁止所有输出处理(OPOST)。? ·每次输入一个字节(MIN=1,TIME=0)。? 程序11?11测试原始和cbreak方式。运行程序11?11可以观察这两种终端工作方式 的工作情 况。? $ a.out? Enter raw mode characters,terminate with DELETE? 4? 33? 133? 62? 63? 60? 1? 键入DELETE? Enter cbreak mode characters,terminate with SIGINT? 1 键入Control-A? 10 键入退格? signal caught 键入中断? P357? 程序11?11〓测试原始和cbreak工作方式? 在原始方式,输入的字符是Control-D(04)和特殊功能键F7。在所用的终端上,此 功能键产 生6个字符:ESC(033),[(0133),2(062),3(063),0(0 60)和Z (0172)]。注意,在原始方式下关闭了输出处理(~OPOST),所以在每个字 符后没有 得到回车符。另外也要注意的是,在cbreak方式下,不对输入特殊字符进行处理( 所以对Con trol-D、文件结束符和退格等不进行特殊处理),但是对终端产生的信号则进行处 理。? 11?12〓终端的窗口大小? SVR4和贝克莱系统都提供了一种功能,用其可以对当前终端窗口的大小进行跟踪, 在窗口大 小发生变化时,使系统核通知前台进程组。系统核为每个终端和伪终端保存一个w insize结 构。? struct winsize {? unsigned short ws-row; /*以字符表示的行数*/? unsigned short ws-col; /*以字符表示的列数*/? unsigned short ws-xpixel; /*水平长度(象素)(未使用)*/? unsigned short ws-ypixel; /*垂直长度(象素)(未使用)*/? 此结构的作用是:? 1?用ioctl的TIOCGWINSZ命令可以取此结构的当前值。? 2?用ioctl的TIOCSWINSZ命令可以将此结构的新值存放到系统核中。如果此新值与 存放在系 统核中的当前值不同,则向前台进程组发送SIGWINCH信号。(注意,从图10?1中可 以看出, 此信号的系统默认动作是忽略它)。? 3?除了存放此结构的当前值以及在此值改变时产生一个信号以外,系统核对该结 构不进行 任何其它操作。对结构中的值进行解释完全是应用程序的工作。? 提供这种功能的目的是,当窗口大小发生变化时通知应用程序(例如V1编辑程序)。 应用程序 接到此信号后,它可以取得窗口大小的新值,然后重画屏幕。? 实例? 程序11?12打印当前窗口大小,然后睡眠。每次窗口大小改变时,就捕捉到SIGWI NCH信号, 然后打印新的窗口大小。我们必须用一个信号终止此程序。? P359? 程序11?12〓打印窗口大小? 在一个带窗口的终端上运行此程序得到:? $ a.out? 35 rows,80 columns 起始长度? SIGWINCH received 更改窗口大小:捕捉到信号? 40 rows,123 columns? SIGWINCH received 再一次? 42 rows,33 columns? ^? $ 键入中断以终止? 11?13〓termcap,terminfo和Curses? termcap的意思是终端性能(terminal capability),它涉及到文本文件/etc/term cap和一套 读此文件的例程。termcap这种技术是在贝克莱为了支持Vi编辑器而发展起来的。 termcap文 件包含了对各种终端的说明:终端支持哪些功能(行、列数、是否支持退格等),如 何使终端 执行某些操作(清屏,将光标移动到指定位置等)。把这些信息从需要编译的程序中 取出来并 把它们放在易于编辑的文本文件中,这样就使得vi能在很多不同的终端上运行。?
然后,支持termcap文件的一套例程也从vi编辑程序中抽取出来,放在一个单独的 curses(光 标)库中。为使这套库可被要进行屏幕处理的任何程序使用,增加了很多功能。?
termcap这种技术不是很完善的。当越来越多的终端被加到该数据文件中时,为了 找到一个 特定的终端就需使用较长的时间扫描此文件。此数据文件也只用两个字符的名字来 标识不同 的终端属性。这些缺陷导致开发另一种新技术〖CD2〗terminfo及与其相关的curs es库。在t erminfo中,终端说明基本上是文本说明的编译版本,在运行时易于快速定位。te rninfo由S VR2开始使用,此后所有系统V版本都使用它。? 系统V使用terminfo,而4?3+BSD则使用termcap。? Goodheard[1991]对terminfo和curses库进行了详细说明。Strang,Mui和O′ Reilly[ 1991]则对termcap和terminfo进行了说明。? 不论是termcap还是terminfo都致力于本章所述及的问题〖CD2〗更改终端的方式、 更改终端特殊字符、处理窗口大小等等。它们所提供的是在各种终端上执行典型操 作(清屏,移动光标)的方法。另一方面,在本章所述问题方面curses,能提供更详细 的帮助。curses提供了很多函数,包括:设置原始方式、设置cbreak方式、打开和关 闭回送等等。但是curser是为字符终端设计的,而当前的趋势则是向以象素为基础的 图形终端发展。?
11?14〓摘要? 终端有很多特征和选择项,其中大多数都可按需进行改变。在本章中,我们说明了 很多更改终端操作的函数〖CD2〗特殊输入字符和选择标志的函数,介绍了可为终 端设备设置的各个终端特殊字符以及很多选择项。? 终端的输入方式有两种〖CD2〗规范的(每次一行)和非规范的。本章中包含了若干 这两种工作方式的实例,也提供了一些函数,它们在POSIX.1终端选择项和较早的 BSD cbreak和原始方式之间进行变换。我们也在本章中说明了如何取用和改变终端 的窗口大小。第十七和十八章包含了终端I/O的另外一些实例。?
| | |