终端IO--unix环境高级编程读书笔记

     终端IO部分整体上读了两遍,感觉这一部分的内容又乱又碎,不太好理解。读完了之后,仍然感觉什么也没有学到。先做一个肤浅的记录,等到以后要用到的时候,再回来补充。

    1.终端IO的工作方式

     终端IO有两种不同的工作方式,规范方式输入处理和非规范方式输入处理:

(1)规范方式输入处理:终端以行为单位进行处理,对于每个读要求,终端驱动程序最多返回一行。
(2)非规范方式输入处理:输入字符不以行为单位进行处理。

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

     2.对终端设备进行操作

     关于终端IO的属性存放在一个  termios  的结构体中,这个结构体中的成员如下:

           tcflag_t c_iflag;      /* input modes */
           tcflag_t c_oflag;      /* output modes */
           tcflag_t c_cflag;      /* control modes */
           tcflag_t c_lflag;      /* local modes */
           cc_t     c_cc[NCCS];   /* special characters */

通过对这些数据成员的设置,可以来控制终端的属性。c_iflag  用来控制终端的输入属性,c_oflag  用来控制输出的属性,c_cflag  用来控制一些其他属性,c_lflag  用来控制驱动程序和用户之间的界面。c_cc  数组包含了所有可以更改的特殊字符,这在稍后会介绍到。
     对终端设备的操作函数基本如下:
       int tcgetattr(int fd, struct termios *termios_p);

       int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);

       int tcsendbreak(int fd, int duration);

       int tcdrain(int fd);

       int tcflush(int fd, int queue_selector);

       int tcflow(int fd, int action);

       speed_t cfgetispeed(const struct termios *termios_p);

       speed_t cfgetospeed(const struct termios *termios_p);

       int cfsetispeed(struct termios *termios_p, speed_t speed);

       int cfsetospeed(struct termios *termios_p, speed_t speed);

我们可以通过这几个函数来操作终端的属性。tcgetattr  函数用来获取终端的属性,终端的属性通过第二个参数返回。tcsetattr  函数用来设置终端的属性。这里需要注意的是最后四个用来设置和获得波特率的函数,它们是建立在前面两个函数的基础之上的。也就是说,如果想要获得波特率,需要先调用tcgetattr函数来获得终端的属性,然后在将获得的终端的属性作为参数传递给  cfgetispeed  函数或  cfgetospeed  函数。同样,当需要设置波特率时,需要先将波特率在  termios  结构体中设置好,然后调用  tcsetattr  函数。

     下面的一个例子用来禁止终端中的中断字符(即CTRL+C),并且将文件结束的字符更改为CTRL+B:

#include <termios.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
        struct termios term;
        long vdisable;

        if(isatty(STDIN_FILENO)==0)
        {
                printf("standard input is not terminal device\n");
                return -1;
        }

        if( (vdisable=fpathconf(STDIN_FILENO,_PC_VDISABLE))<0 )
        {
                printf("fpathconf error\n");
                return -1;
        }

        if(tcgetattr(STDIN_FILENO,&term)<0)
        {
                printf("tcgetattr error\n");
                return -1;
        }

        term.c_cc[VINTR] = vdisable;
        term .c_cc[VEOF] = 2;

        if(tcsetattr(STDIN_FILENO,TCSAFLUSH,&term)<0)
        {
                printf("tcsetattr error\n");
                return -1;
        }

        return 0;
}

     该程序先用  isatty  函数来测试  STDIN_FILENO  描述符所指向的文件是否是终端设备,如果测试的文件描述符是指向一个终端设备的话则返回1,否则返回0。

     然后使用函数  fpathconf  获取系统中的  _PC_VDISABLE  的值,将这个值保存在  c_cc  数组中的相应位置就可以禁止使用这个位置所代表的特殊字符。

     接着我们调用  tcgetattr  函数用来获取终端IO的属性,然后设置  c_cc  数组的  VINTR  的位置为  _PC_VDISABLE,表示禁止使用中断符号CTRL+C。而将VEOF的位置的值更改为2,即表示将文件的结束符号修改为CTRL+B,同理如果要修改为CTRL+A,则这个地方的值为1。

     最后使用  tcsetattr  函数设置修改过的属性,使这些属性生效。tcsetattr  函数的第二个参数用来表示设置属性之后,这些属性生效的时间,这个参数有三种选择:

TCSANOW:更改立即生效
TCSADRAIN:发送了所有输出之后,更改才生效。如果更改输出参数,则应该使用此选项。
TCSAFLUSH:发送了所有输出之后,更改才生效。这里和上面一个的不同是,当更改发生时,未读的所有输入数据都被删除(丢弃)。

     3.一些函数

     (1)ctermid  函数用来返回控制终端的名字,一般是  /dev/tty。它的函数原型如下:

       #include <stdio.h>

       char *ctermid(char *s);

它将控制终端的名字存放在由参数  s  指示的缓存中,如果  s  为空,则启动一个静态存储区,并将这个静态存储区的首地址返回给调用者。下面是  ctermid  函数的一个实现:
#include <stdio.h>
#include <string.h>

static char ctermid_name[L_ctermid];

char* ctermid(char* str)
{
        if(str==NULL)
        {
                str = ctermid_name;
        }
        return strcpy(str,"/dev/tty"); //strcpy returns str
}

     (2)isatty  函数的一种实现如下:

#include <termios.h>
#include <stdio.h>

int isatty(int fd)
{
        struct termios term;
        return (tcgetattr(fd,&term)!=-1);
}

可以用下面的程序来进行测试  isatty  函数:
#include <termios.h>
#include <stdio.h>

int isatty(int fd)
{
        struct termios term;
        return (tcgetattr(fd,&term)!=-1);
}

int main(void)
{
        printf("fd 0:%s\n",isatty(0)?"tty":"not a tty");
        printf("fd 1:%s\n",isatty(1)?"tty":"not a tty");
        printf("fd 2:%s\n",isatty(2)?"tty":"not a tty");

        return 0;
}

将程序编译为  a.out,运行程序,输出结果:
fd 0:tty
fd 1:tty
fd 2:tty

如果将标准输入和标准出错重定向,按照下面的方式运行程序:
./a.out</etc/passwd 2>/tmp/t

输出结果如下:
fd 0:not a tty
fd 1:tty
fd 2:not a tty

     (3)ttyname  返回在指定描述符上打开的终端设备的路径名,例如下面的一个程序可以演示一下它的使用:

#include <unistd.h>
#include <stdio.h>
int main(void)
{
        printf("fd 0:%s\n",isatty(0)?ttyname(0):"not a tty");
        printf("fd 1:%s\n",isatty(1)?ttyname(1):"not a tty");
        printf("fd 2:%s\n",isatty(2)?ttyname(2):"not a tty");

        return 0;
}

     4.终端窗口的大小

     linux  系统提供了一个跟踪终端大小的功能,内核为每个终端或者是伪终端保存了一个  winsize  结构体,这个结构体中保存了当前终端大小的信息,这个结构体如下:

struct winsize {
        unsigned short ws_row;
        unsigned short ws_col;
        unsigned short ws_xpixel;
        unsigned short ws_ypixel;
};

通过  ioctl  函数的  TIOCGWINSZ  命令可以取此结构体的当前值。当然也可以通过  ioctl  的  TIOCSWINSZ  命令可以将此结构体的新值存放到内核中,如果新值与存放在内核中的当前值不同,则会向前台进程组发送SIGWINCH信号,系统对此信号的默认处理是忽略该信号。当前终端窗口大小发生变化时,也会产生  SIGWINCH  信号。下面的程序演示了当终端的大小发生变化时,就打印当前终端的大小:
#include <signal.h>
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void pr_winsize(int fd)
{
        struct winsize size;

        if(ioctl(fd,TIOCGWINSZ,(char*)&size)<0)
        {
                printf("TIOCGWINSZ error\n");
                exit(-1);
        }

        printf("%d rows,%d columns\n",size.ws_row,size.ws_col);
}

static void sig_winch(int signo)
{
        printf("SIGWINCH received\n");
        pr_winsize(STDIN_FILENO);
        return;
}

int main(void)
{
        if(isatty(STDIN_FILENO)==0)
        {
                exit(1);
        }
        if(signal(SIGWINCH,sig_winch)==SIG_ERR)
        {
                printf("signal error\n");
                return -1;
        }

        pr_winsize(STDIN_FILENO);

        for(;;)
        {
                pause();
        }
}

将程序编译之后,运行,不断的改变终端的大小,就可以看到输出结果类似与下面:
55 rows,178 columns
SIGWINCH received
6 rows,63 columns
SIGWINCH received
6 rows,124 columns
SIGWINCH received
24 rows,124 columns
SIGWINCH received
53 rows,178 columns
SIGWINCH received
55 rows,178 columns

终端IO--unix环境高级编程读书笔记

上一篇:redis的其他功能


下一篇:函数