对终端进行读写
在编写程序时,我们往往需要从终端读入数据。一种情况是需要连续地读入用户键入的选择项,这往往出现在数据库程序中。程序员往往会使用 getchar 函数来读取数据,继而判断输入的数据是否有效,从而做出反应。其实如此做带有很大的风险,一个实例程序如下
#include <stdio.h>
char *menu[] = { "a - add new record", "d - delete record", "q - quit", NULL };
int getchoice(char *choices[]){ int chosen = 0; int selected; char **option;
do { option = choices; while(*option){ printf("%s/n", *option); option++; } selected = getchar(); option = choices; while(*option){ if(option[0] == selected){ chosen = 1; break; } option++; } if(!chosen){ printf("Incorrect choice, select again./n"); } } while(!chosen);
return selected; }
int main() { int choice = 0;
do{ choice = getchoice(menu); printf("You choose %c/n", choice); } while('q' != choice);
exit(0); } |
实例程序中,用户需要键入“ A/ 回车 /Q/ 回车”才能做出选择。但这种处理有着很大的风险,读者可以自己测试一下。这也是初学者经常碰到的问题。
默认情况下,只有当用户按下回车键后,程序才能读到终端的输入。这种处理方式是规范模式或标准模式 。在这种模式下,所有的输入都给予行进行处理,在一个输入行完成前,终端接口负责管理所有的用户键盘输入,包括退格键,应用程序读不到用户输入的任何字符。
与标准模式相对的另一种模式为非标准模式 ,这种模式下,应用程序对用户输入字符的处理拥有更大的控制权。
在上述程序中, Linux 会暂存用户读入的内容,直到用户按下回车键,然后将用户选择的字符及紧随其后的回车符一起传送到程序。所以,每当你选择一个菜单时,程序就调用 getchar 函数处理该字符,而当程序在下一次循环再次调用 getchar 函数时,它会立刻返回一个回车符。一个解决方案是程序在每次读入数据前首先清空回车键之前的所有数据,典型代码如下:
do{ selected = getchar(); } while('/n' != selected); |
终端驱动程序和通用终端接口
有时,程序需要更加精细的终端控制能力,而不是仅通过简单的文件操作来完成对终端的一些控制。 Linux 提供了一组编程接口,这使得我们能够控制终端驱动程序的行为,从而允许我们对终端的输入和输出进行更好的控制。
有一组函数调用( GTI )用作控制终端,这组函数调用与用于读写数据的函数是分离的,这就使得读写数据的接口非常简洁,同时又保证用户可以对终端的行为进行更精细的控制。
termios 结构
通过设置 termios 结构中的值和使用一组函数调用,我们就可以对终端接口进行控制。
提示 :使用 termios 结构及相关的函数调用,需要包含 termios.h 头文件;同时需要包含 curses 函数库。
控制终端的操作模式有以下几种:输入模式、输出模式、控制模式、本地模式和特殊的控制字符。具体操作由 tcgetattr 函数和 tcsetattr 函数来完成。其中,本地模式是最常用,也是最重要的一种操作模式。
注意 :程序要将终端设置恢复到程序开始运行之前的状态,这一点是非常重要的。首先保存这些值,然后在程序结束时恢复它们,这永远是程序的职责。
输入模式控制输入数据在被传递给程序之前的处理方式。通过设置 termios 结构中的 c_iflag 成员的标志对它们进行控制。
输出模式控制输出字符的处理方式,即由程序发送出去的字符在传递到串行口或屏幕之前是如何处理的。通过设置 termios 结构中 c_oflag 成员的标志对输出模式进行控制。
控制模式控制终端的硬件特性。通过设置 termios 结构中的 c_cflag 成员的标志对控制模式进行配置。控制模式主要用于串行线连接调制解调器的情况。
本地模式控制终端的各种特性。通过设置 termios 结构中的 c_lflag 成员的标志对本地模式进行配置。其中最常用的两个标志是 ECHO 和 ICANON 。前者抑制键入字符的回显,后者将终端在两个截然不同的接收字符处理模式之间进行切换。如果设置了 ICANON 标志 ,就启用标准输入行处理模式,否则就启动非标准模式。
当用户键入类似 Ctrl-C 这样的组合键时,终端会采取一些特殊的处理方式。 termios 结构中的 c_cc 数组成员将各种特殊的控制字符映射到对应的支持函数。每个字符的位置是由一个宏定义的,但不限制这些字符必须是控制字符。
注意 :在两种不同的模式(标准模式和非标准模式)下, c_cc 数组的下标值有一部分是重叠的。出于这个原因,一定要注意不要将两种模式各自的下标值混淆。
可以通过 stty 命令 查询及修改终端模式。
通过 termios 结构我们还可以控制终端的传入和传出的速度(波特率)。
终端的输出
编写能够应付连接到 UNIX 系统上的各种不同类型终端的程序看上去是一件非常让人畏惧的事情。因为这样的程序必须针对各种类型的终端编写相应的代码。 termifo 软件包的出现解决了这一问题。在绝大多数现代的 UNIX 系统上,这个软件包和另一个软件包 curses 集成在一起。
注意 :在 Linux 系统上,在使用 termifo 软件包时可能需要包含 ncurses 库;该库实现了 curses 软件包的所有功能。
termifo 的功能标识由属性描述,它们被保存在一组编译好的 terminfo 文件中,而这些文件可以方便地在 /usr/lib/terinfo 或 /usr/share/terinfo 目录下找到。例如, VT100 终端的定义就放在文件 /usr/share/terminfo/v/vt100 中。你可以使用 infocmp 程序输出 terminfo 编译数据项的可读版本。
虚拟控制台
在 Linux 的典型安装中将配置 12 个虚拟控制台。虚拟控制台通过字符设备文件 /dev/ttyN 使用, tty 是 Teletype 的缩写,而 N 代表一个数字,从 1 开始。
通过 who 和 ps 命令,可以查看目前登录进系统的用户,以及目前在使用的虚拟控制台及其上运行的 shell 和程序。
Linux 系统一般在前六个虚拟控制台上运行一个 getty 进程,这样用户即可用同一个屏幕、键盘和鼠标在六个不同的虚拟控制台上登录。可以通过组合键 Ctrl+Alt+F<N> 在这六个不同的虚拟控制台之间进行切换。
如果 Linux 系统使用的是图形登录界面或者使用 startx 切入图形界面, X 视窗系统将使用第一个未使用的控制台,通常是 /dev/tty7 。
而伪终端 由字符设备文件 /dev/pty 使用,其中 pty 是 pseudo tty 的缩写。它与 tty 终端的区别在于伪终端没有对于的硬件设备。
运程登录的终端 由字符设备文件 /dev/pts/N 使用。