《unix高级环境编程》终端 I/O——终端 IO 基本概述

来源:互联网 发布:linux复制所有jpg文件 编辑:程序博客网 时间:2024/05/16 15:16

终端基本概念

终端 IO 是一种字符型设备,终端特殊设备文件一般有以下几种:

  1. 串行端口终端:是使用计算机串行端口连接的设备,计算机把每个串行端口都看作是一个字符设备。串行端口所对应的设备名称 /dev/ttySn(n表示从0开始的整数);
  2. 伪终端:是成对的逻辑终端设备,例如 /dev/ptyp3 和/ dev/ttyp3(在设备文件系统中分别是 /dev/pty/m3 和/ dev/pty/s3 ),它们与实际物理设备并不直接相关;
  3. 控制终端:是当前进程的控制终端的设备特殊文件 /dev/tty。可以使用命令”ps –ax”来查看进程与哪个控制终端相连使用命令”tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接;
  4. 控制台终端:计算机显示器通常被称为控制台终端(Console),它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等;

终端 IO 有两种不同的工作模式:

  1. 规范模式输入处理:在这种模式下,终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返回一行;
  2. 非规范模式输入处理:输入字符不以行为单位进行处理;

终端设备是由一般位于内核中的终端驱动程序控制,每个终端设备有一个输入队列和一个输出队列,如下图所示:


大多数 UNIX 系统在一个称为终端行规程的模块中进行规范处理。它位于内核通用读、写函数和实际设备驱动程序之间的模块,如下图所示:


操作终端的结构定义如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 终端IO */  
  2.   
  3. /* 终端IO的数据结构 */  
  4. #include <termios.h>  
  5. struct termios  
  6. {  
  7.     tcflag_t    c_iflag;    /* input flag */  
  8.     tcflag_t    c_oflag;    /* output flag */  
  9.     tcflag_t    c_cflag;    /* control flag */  
  10.     tcflag_t    c_lflag;    /* local flag */  
  11.     cc_t        c_cc[NCCS]; /* control characters */  
  12. };  

        该数据结构的输入标志由终端设备驱动程序用来控制字符的输入(剥除输入字节的第8位,允许输入奇偶校验等);输出标志则控制终端设备驱动的输出(执行输出处理、将换行符映射为 CR/LF 等);控制标志影响到 RS-232串行线(忽略调制解调器的状态线、每个字符的一个或两个停止位等);本地标志影响驱动程序和用户之间的接口(回送的开或关、可视的擦除字符、终端产生的信号的启用以及后台输出的作业控制停止信号等);有关各个标志的取值可以参考书上的表格。

特殊输入字符


终端设备操作函数

以下先给出终端的操作函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*_______________________________________ 
  2.  *   函数             说明 
  3.  *_______________________________________ 
  4.  * tcgetattr        获取属性 
  5.  * tcsetattr        设置属性 
  6.  *_______________________________________ 
  7.  * cfgetispeed      得到输入速度 
  8.  * cfgetospeed      得到输出速度 
  9.  * cfsetispeed      设置输入速度 
  10.  * cfsetospeed      设置输出速度 
  11.  *_______________________________________ 
  12.  * tcdrain          等待所有输出都被传输 
  13.  * tcflow           挂起传输或接收 
  14.  * tcflush          刷清未决输入和/或输出 
  15.  * tcsendbreak      送BREAK字符 
  16.  *_______________________________________ 
  17.  * tcgetpgrp        得到前台进程组ID 
  18.  * tcsetpgrp        设置前台进程组ID 
  19.  * tcgetsid         得到控制TTY的会话首进程的进程组ID 
  20.  *_______________________________________ 
  21.  * 
  22.  */  

针对这些终端操作函数进行分类讨论:

终端属性

可以通过以下两个函数获取和设置终端属性,该属性结构是 termios 结构,有了终端属性方便进一步的操作,其定义如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:获取和设置终端属性; 
  3.  * 返回值:若成功则返回0,出错则返回-1; 
  4.  * 函数原型: 
  5.  */  
  6. #include <termios.h>  
  7.   
  8. int tcgetattr(int filedes, struct termios *termptr);/* 获取终端属性 */  
  9.   
  10. int tcsetattr(int filedes, int opt, const struct termios *termptr);/* 设置终端属性 */  
  11. /* 
  12.  * 说明: 
  13.  * filed是终端设备描述符; 
  14.  * 
  15.  * opt参数可以指定为以下的值: 
  16.  * TCSANOW          更改立即生效; 
  17.  * TCSADRAIN        发送所有输出后更改才发生,若更改输出参数则应该使用此选项; 
  18.  * TCSAFLUSH        发送所有输出后更改才发生,更进一步,在更改发生时未读的所有输入数据都被删除; 
  19.  */  

测试程序:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include "apue.h"  
  2. #include <termios.h>  
  3.   
  4. int main()  
  5. {  
  6.     struct termios term;  
  7.     //获取termios结构  
  8.     if(tcgetattr(STDIN_FILENO,&term) < 0)  
  9.         err_sys("tcgetattr error");  
  10.     switch(term.c_cflag & CSIZE)  
  11.     {  
  12.     case CS5:  
  13.         printf("5 bits/byte\n");  
  14.         break;  
  15.     case CS6:  
  16.         printf("6 bits/byte\n");  
  17.         break;  
  18.     case CS7:  
  19.         printf("7 bits/byte\n");  
  20.         break;  
  21.     case CS8:  
  22.         printf("8 bits/byte\n");  
  23.         break;  
  24.     default:  
  25.         printf("Unknown bityes/byte\n");  
  26.     }  
  27.     term.c_cflag &= ~CSIZE;   //字符长度清0  
  28.     term.c_cflag |= CS5;          //设置为8 bites/byte  
  29.     if(tcsetattr(STDIN_FILENO,TCSANOW,&term) < 0)  
  30.         err_sys("tcsetattr error");  
  31.     exit(0);  
  32. }  
输出结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 8 bits/byte  

波特率函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:获取和设置终端波特率; 
  3.  * 函数原型: 
  4.  */  
  5. #include <termios.h>  
  6. /* 返回值:若成功则返回波特率值 */  
  7. speed_t cfgetispeed(const struct termios *termptr);/* 获取输入波特率 */  
  8.   
  9. speed_t cfgetospeed(const struct termios *termptr);/* 获取输出波特率 */  
  10. /* 返回值:若成功则返回0,出错则返回-1;*/  
  11. int cfsetispeed(struct termios *termptr, speed_t speed);/* 设置输入波特率 */  
  12.   
  13. int cfsetospeed(struct termios *termptr, speed_t speed);/* 设置输出波特率 */  

行控制函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:终端设备的行控制; 
  3.  * 返回值:若成功则返回0,若出错则返回-1; 
  4.  * 函数原型: 
  5.  */  
  6. #include <termios.h>  
  7.   
  8. int tcdrain(int filedes);  
  9. int tcflow(int filedes, int action);  
  10. int tcflush(int filedes, int queue);  
  11. int tcsendbreak(int filedes, int duration);  
  12. /* 
  13.  * 说明: 
  14.  * action参数取值如下: 
  15.  * TCOOFF       输出被挂起; 
  16.  * TCOON        重新启动以前被挂起的输出; 
  17.  * TCIOFF       系统发送一个STOP字符,将使终端设备暂停发送数据; 
  18.  * TCION        系统发送一个START字符,将使终端设备恢复发送数据; 
  19.  * 
  20.  * queue参数取值如下: 
  21.  * TCIFUSH      刷清输入队列; 
  22.  * TCOFUSH      刷清输出队列; 
  23.  * TCIOFUSH      刷清输入、输出队列; 
  24.  */  

终端标识

下面是一些对控制终端操作的函数,其定义如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 终端标识 */  
  2.   
  3. /* 
  4.  * 函数功能:确定控制终端的名字; 
  5.  * 返回值:若成功则返回指向控制终端名的指针,出错则返回指向空字符串的指针; 
  6.  * 函数原型: 
  7.  */  
  8. #include <stdio.h>  
  9. char * ctermid(char *ptr);  
  10. /* 
  11.  * 说明: 
  12.  * ptr非null,且指向长度至少为L_ctermid字节的数组,进程的控制终端名存放在该数组中; 
  13.  * ptr为null,则该函数为数组分配空间,进程的控制终端名也放在该数组中; 
  14.  */  
  15.   
  16. /* 
  17.  * 函数功能:控制终端的操作; 
  18.  * 函数原型: 
  19.  */  
  20. #include <unistd.h>  
  21. int isatty(int filedes);  
  22. /* 返回值:若为终端设备则返回1(真),否则返回0(假)*/  
  23.   
  24. char *ttyname(int filedes);  
  25. /* 返回值:指向终端路径名的指针,若出错则返回NULL */  
下面程序是测试控制终端的信息:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include "apue.h"  
  2. #include <termios.h>  
  3. static char ctermid_name[L_ctermid];  
  4. char *Mctermid(char *str)  
  5. {  
  6.     if(str == NULL)  
  7.         str = ctermid_name;  
  8.     return (strcpy(str,"/dev/tty"));  
  9. }  
  10. int main()  
  11. {  
  12.     char tername[50];  
  13.     char *name;  
  14.     ctermid(tername);  
  15.     printf("terminate name is: %s\n", tername);  
  16.   
  17.     Mctermid(tername);  
  18.     printf("Mterminate name is: %s\n", tername);  
  19.     printf("Test isatty...\n");  
  20.     printf("fd 0 is: %s\n",isatty(0)? "tty" : "not a tty");  
  21.     printf("fd 1 is: %s\n",isatty(1)? "tty" : "not a tty");  
  22.     printf("fd 2 is: %s\n",isatty(2)? "tty" : "not a tty");  
  23.     printf("Test ttyname...\n");  
  24.     if(isatty(0))  
  25.     {  
  26.         name = ttyname(0);  
  27.         if(name == NULL)  
  28.             name ="undefined";  
  29.     }  
  30.     else  
  31.         name = "not a tty";  
  32.     printf("fd 0 :%s\n", name);  
  33.     if(isatty(1))  
  34.     {  
  35.         name = ttyname(1);  
  36.         if(name == NULL)  
  37.             name ="undefined";  
  38.     }  
  39.     else  
  40.         name = "not a tty";  
  41.     printf("fd 1 :%s\n",name);  
  42.     if(isatty(2))  
  43.     {  
  44.         name = ttyname(2);  
  45.         if(name == NULL)  
  46.             name ="undefined";  
  47.     }  
  48.     else  
  49.         name = "not a tty";  
  50.     printf("fd 2 :%s\n",name);  
  51.     exit(0);  
  52. }  
输出结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ./tty < /dev/console 2> /dev/null  
  2. terminate name is: /dev/tty  
  3. Mterminate name is: /dev/tty  
  4. Test isatty...  
  5. fd 0 is: tty  
  6. fd 1 is: tty  
  7. fd 2 is: not a tty  
  8. Test ttyname...  
  9. fd 0 :/dev/console  
  10. fd 1 :/dev/pts/1  
  11. fd 2 :not a tty  

终端的窗口大小

内核为每个终端和伪终端保存了一个窗口大小结构 winszie,

  1. 用 ioctl 函数的 TIOCGWINSZ 命令可以获取此结构的当前值;
  2. 也可以通过 ioctl 的 TIOCSWINSZ 命令可以将此结构体的新值存放到内核中,如果新值与存放在内核中的当前值不同,则会向前台进程组发送 SIGWINCH 信号,系统对此信号的默认处理是忽略该信号;
  3. 当前终端窗口大小发生变化时,也会产生 SIGWINCH 信号;

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 终端的窗口大小 */  
  2.   
  3. /* 窗口大小的结构体 */  
  4. struct winsize  
  5. {  
  6.     unsigned short ws_row;      /* row, in characters */  
  7.     unsigned short ws_col;      /* columns, in characters */  
  8.     unsigned short ws_xpixel;   /* horizontal size, pixels (unused) */  
  9.     unsigned short ws_ypixel;   /* vertical size, pixels (unused) */  
  10. };  
下面程序实现打印当前终端的窗口大小:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include "apue.h"  
  2. #include <termios.h>  
  3. #include <sys/ioctl.h>  
  4. #include <signal.h>  
  5.   
  6. static void pr_winsize(int fd)  
  7. {  
  8.     struct  winsize size;  
  9.     if(ioctl(fd,TIOCGWINSZ,(char *)&size) < 0)  
  10.         err_sys("ioctl error");  
  11.     printf("%d rows, %d columns\n", size.ws_row, size.ws_col);  
  12. }  
  13. static void sig_winch(int signo)  
  14. {  
  15.     printf("SIGWINCH received\n");  
  16.     pr_winsize(STDIN_FILENO);  
  17. }  
  18. int main()  
  19. {  
  20.     if(isatty(STDIN_FILENO) == 0)  
  21.     {  
  22.         printf("STDIN_FILENO is not terminate device.\n");  
  23.         exit(1);  
  24.     }  
  25.     if(signal(SIGWINCH, sig_winch) == SIG_ERR)  
  26.         err_sys("signal error");  
  27.     pr_winsize(STDIN_FILENO);  
  28.     for( ; ;)  
  29.      pause();  
  30. }  
输出结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 改变终端的窗口大小时会发送信号 SIGWINCH */  
  2. 24 rows, 80 columns  
  3. SIGWINCH received  
  4. 24 rows, 79 columns  
  5. ^C  
0 0