《APUE》chapter 18 Terminal I/O 学习笔记(加上自己的代码)

Terminal I/O 

Overview

Terminal I/O has two modes:


1.  Canonical mode input processing.  In this mode, terminal input is processed as lines.  The terminal driver returns at most one line per read request.


2.  Noncanonical mode  input  processing.  The input  characters  ar enot  assembled into lines


If we don’t do anything special, canonical mode is the default.


        We  can think of a terminal device as being controlled by a terminal driver,usually within  the  kernel. Each  terminal device  has  an  input  queue  and  an  output  queue, shown in Figur e18.1.



             All  the  terminal  device  characteristics  that  we  can  examine  and  change  are contained  in  a termios structure. This structure is defined  in  the  header <termios.h>,which we use throughout this chapter:

linux下面找到的termios结构体的定义

struct termios
  {
    tcflag_t c_iflag;           /* input mode flags */
    tcflag_t c_oflag;           /* output mode flags */
    tcflag_t c_cflag;           /* control mode flags */
    tcflag_t c_lflag;           /* local mode flags */
    cc_t c_line;                        /* line discipline */
    cc_t c_cc[NCCS];            /* control characters */
    speed_t c_ispeed;           /* input speed */
    speed_t c_ospeed;           /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
  };


/* c_cc characters */
#define VINTR 0
#define VQUIT 1
#define VERASE 2
#define VKILL 3
#define VEOF 4
#define VTIME 5
#define VMIN 6
#define VSWTC 7
#define VSTART 8
#define VSTOP 9
#define VSUSP 10
#define VEOL 11
#define VREPRINT 12
#define VDISCARD 13
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16


对于本章理解极其有帮助的图:






Special Input  Characters

  
           POSIX.1  defines  11characters  that  arehandled  specially  on  input. Implementations define additional special characters.


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

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

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

        if((vdisable = fpathconf(STDIN_FILENO,_PC_VDISABLE)) < 0)
        {
                printf("fpathconf error or _POSIX_VDISABLE not in effect\n");
                return 0;
        }

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

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

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

        return 0;
}



Note the following points regarding this program.


•Wemodify  the  terminal  characters  only  if  standardinput  is  a  terminal  device. We  callisatty (Section 18.9) to check this.


•Wefetch the _POSIX_VDISABLEvalue using fpathconf.


•The  functiontcgetattr (Section  18.4)  fetches  a termios structur efro mthe kernel.  After we’ve  modified  this  structure,  we  call tcsetattr to  set  the attributes.  The only attributes that change ar ethe ones we specifically modified.


•Disabling  the  interrupt  key  is  different  from  ignoring  the  interrupt  signal. The program  in  Figure18.10  simply  disables  the  special  character  that  causes  the terminal driver to generate SIGINT.Wecan still use the kill function to send the signal to the process.




Getting and  Setting  Terminal  Attributes


#include <termios.h>
int tcgetattr(int fd ,struct termios *termptr);
int tcsetattr(int fd ,int opt,const struct termios *termptr);
Both return: 0 if OK, −1 on error

           Both  functions  take  a  pointer  to  a termios structur eand  either  return  the  current terminal  attributes  or  set  the  terminal’s  attributes. Since  these  two  functions  operate only on terminal devices, errnois set to ENOTTY and −1 is returned if fd does not refer to a terminal device.


        The  argument opt for tcsetattr lets  us  specify  when  we  want  the  new  terminal attributes to take effect. This argument is specified as one of the following constants.


TCSANOW           The change occurs immediately.


TCSADRAIN         The change occurs after all output has been transmitted. This options hould be used if we                                   are changing the output parameters.


TCSAFLUSH         The  change  occurs  after  all  output  has  been  transmitted.


  Furthermore, when the change takes place, all input data that has not  been read is discarded (flushed).



       The  return  status  of tcsetattr can  be  confusing  to  use  correctly.This  function returns  OK  if  it  was  able  to perform any of  the  requested  actions,  even  if  it  couldn’t perform all the requested actions. If the function returns OK, it is our responsibility to see  whether  all  the  requested  actions  were performed.  This means  that  after  we call tcsetattr to set the desired attributes, we need to call tcgetattr and compare the actual terminal’s attributes to the desired attributes to detect any differences.



Terminal  Option  Flags




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

int main()
{
        struct termios term;

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

        switch(term.c_cflag & CSIZE)
        {
                case CS5 :
                printf("5 bits/byte\n");
                break;

                case CS6:
                printf("6 bits/byte\n");
                break;

                case CS7 :
                printf("7 bits/byte\n");
                break;


                case CS8 :
                printf("8 bits/byte\n");
                break;

                default :
                        printf("unknown bits/byte\n");
        }

        term.c_cflag &= ~CSIZE;
        term.c_cflag |= CS8;

        if(tcsetattr(STDIN_FILENO,TCSANOW,&term) < 0)
        {
                printf("tcsetattr error\n");
        }

        return 0;
}


flag optiont 太多了, 几乎可以和前面的signal有得一拼了。。。。不一一给出,忘记了就看书吧。。。



stty Command



jasonleaster@ubuntu:/usr/include/x86_64-linux-gnu/bits$ stty -a
speed 38400 baud; rows 41; columns 144; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke



NAME
       stty - change and print terminal line settings


SYNOPSIS
       stty [-F DEVICE | --file=DEVICE] [SETTING]...
       stty [-F DEVICE | --file=DEVICE] [-a|--all]
       stty [-F DEVICE | --file=DEVICE] [-g|--save]


DESCRIPTION
       Print or change terminal characteristics.


       -a, --all
              print all current settings in human-readable form


       -g, --save
              print all current settings in a stty-readable form




Baud Rate  Functions

        The  term baud  rate is  a  historical  term  that  should  be  referred  to  today  as  ‘‘bits  per second.’ ’Although  most  terminal  devices  use  the  same  baud  rate  for  both  input  and output,  the  capability  exists  to  set  the  two  rates  to  different  values,  if  the  hardware allows this.


#include <termios.h>
speed_t cfgetispeed(const 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: 0 if OK, −1 on error


The  return  value  from  the  two cfget functions  and  the speed argument  to  the  two cfset functions are one of the following constants:B50 , B75 , B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200,or B38400.The constant B0 means  ‘‘hang  up.’’If B0is  specified  as  the  output  baud  rate  when tcsetattr is called, the modem control lines are no longer asserted



我写这个笔记blog的目的一个是学了留点东西,方便自己查阅自己觉得重点的知识点,第二就是尽量。。。补全author 没有给出的demo

demo:

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

int main()
{
        struct termios term;
        speed_t speed;

        if(isatty(STDIN_FILENO) < 0)
        {
                printf("STDIN_FILENO is not a tty\n");
                return 0;
        }

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

        if((speed = cfgetispeed(&term)) < 0)
        {
                printf("cfgetispeed error\n");
        }

        printf("The input baud rate : %d\n",speed);

        if((speed = cfgetospeed(&term)) < 0)
        {
                printf("cfgetispeed error\n");
        }

        printf("The output baud rate : %d\n",speed);

        return 0;
}

jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out
The input baud rate : 15
The output baud rate : 15




Line Control  Functions



#include <termios.h>
int tcdrain(int fd );
int tcflow(int fd ,int action );
int tcflush(intfd ,int queue);
int tcsendbreak(int fd ,int duration);
All four return: 0 if OK,−1 on error


        The tcdrain function waits for all output to be transmitted. The tcflow function gives us control over both input and output flow control.  The action argument must be one of the following four values: 

TCOOFF  Output is suspended.


TCOON    Output that was previously suspended is restarted.


TCIOFF    The system transmits a STOP character,which should cause the terminal device to stop sending                        data.


TCION      The system transmits a STAR Tcharacter,which should cause the terminal device to resume sending                  data.


          The tcflush function lets us flush (throw away) either the input buffer (data that has been received by the terminal driver,which we have not read) or the output buffer (data  that  we  have  written,  which  has  not  yet  been transmitted). The queue argument must be one of the following three constants:


TCIFLUSH    The input queue is flushed.

TCOFLUSH  The output queue is flushed.

TCIOFLUSH  Both the input and the output queues ar eflushed




Terminal Identification

#include <stdio.h>
char *ctermid(char *ptr);
Returns: pointer to name of controlling terminal on success, pointer to empty string on error


If ptr is non-null, it is assumed to point to an array of at least L_ctermid bytes, and the name  of  the  controlling terminal  of  the  process  is  stored  in  the  array.The  constant L_ctermid is  defined  in <stdio.h>.If ptr is  a  null pointer ,the  function  allocates room  for  the  array  (usually  as  a  static  variable). Again,  the  name  of  the controlling terminal of the process is stored in the array
demo:

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

int main()
{
        char name[4096];

        printf("The return string of ctermid when parameter is name:%s\n",ctermid(name));

        printf("The return string of ctermid when parameter is NULL:%s\n",ctermid(NULL));

        return 0;
}

jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out
The return string of ctermid when parameter is name:/dev/tty
The return string of ctermid when parameter is NULL:/dev/tty


#include <unistd.h>
int isatty(int fd );
Returns: 1 (true) if terminal device, 0 (false) otherwise
char *ttyname(int fd );
Returns: pointer to pathname of terminal,NULL on error


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

int main()
{
        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;
}

jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out
fd 0:tty
fd 1:tty
fd 2:tty
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out < /dev/tty  2>/dev/null
fd 0:tty
fd 1:tty
fd 2:not a tty



下面是ttyname 函数实现,写的非常的好,用了两个多小时调试才看明白。。。

#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
//#include <termios.h>
#include <stdlib.h>

struct devdir
{
        struct devdir *d_next;
        char          *d_name;
};

static struct devdir    *head;
static struct devdir    *tail;

static char             pathname[_POSIX_PATH_MAX + 1];

char* tyy(int fd);

static char* searchdir(char* dirname,struct stat* fdstatp);

static void add(char * dirname);

static void cleanup(void);

static char* searchdir(char* dirname,struct stat* fdstatp);

static void add(char * dirname)
{
        struct devdir   *ddp;
        int             len;

        len = strlen(dirname);

        if((dirname[len - 1] == '.' && (dirname[len-2] == '/' || (dirname[len - 2] == '.' && dirname[len - 3] == '/'))))
        {
                return;
        }
        if(strcmp(dirname,"/dev/fd") == 0)
        {
                return;
        }

        ddp = malloc(sizeof(struct devdir));

        if(ddp == NULL)
        {
                return;
        }

        ddp->d_name = strdup(dirname);
        //attention! There is a malloc in strdup
        if(ddp->d_name == NULL)
        {
                free(ddp);
                return;
        }

        ddp->d_next = NULL;

        if(tail == NULL)
        {
                head = ddp;
                tail = ddp;
        }
        else
        {
                tail->d_next = ddp;
                tail = ddp;
        }
}

static void cleanup(void)
{
        struct devdir *ddp,*nddp;

        ddp = head;
        while(ddp != NULL)
        {
                nddp = ddp->d_next;
                free(ddp->d_name);
                free(ddp);
                ddp = nddp;
        }

        head = NULL;
        tail = NULL;
}

static char* searchdir(char* dirname,struct stat* fdstatp)
{
        struct stat     devstat;
        DIR             *dp;
        int             devlen;
        struct dirent*  dirp;

        strcpy(pathname,dirname);

        if((dp = opendir(dirname)) == NULL)
        {
                return NULL;
        }

        strcat(pathname,"/");

        devlen = strlen(pathname);
        while((dirp = readdir(dp)) != NULL)
        {
                strncpy(pathname + devlen,dirp->d_name,_POSIX_PATH_MAX - devlen);
                if(strcmp(pathname,"/dev/stdin") == 0 || strcmp(pathname,"/dev/stdout") == 0 || strcmp(pathname,"/dev/stderr") == 0)
                {
                        continue;
                }

                if(stat(pathname,&devstat) < 0)
                {
		           continue;
                }

                if(S_ISDIR(devstat.st_mode))
                {
                        add(pathname);
                        continue;
                }

                if(devstat.st_ino == fdstatp->st_ino && devstat.st_dev == fdstatp->st_dev)
                {
                        closedir(dp);
                        return pathname;
                }
        }

        closedir(dp);
        return NULL;
}

char* tty(int fd)
{
        struct stat   fdstat;
        struct devdir *ddp;
        char          *rval;

        if(isatty(fd) == 0)
        {
                return NULL;
        }

        if(fstat(fd,&fdstat) < 0)
        {
                return NULL;
        }

        if(S_ISCHR(fdstat.st_mode) == 0)
        {
                return NULL;
        }

        rval = searchdir("/dev",&fdstat);

        if(rval == NULL)
        {
                for(ddp = head; ddp != NULL ; ddp = ddp->d_next)
                {
                        if((rval = searchdir(ddp->d_name,&fdstat)) != NULL)
                        {
                                break;
                        }
                }
        }

        cleanup();
        return rval;
}

测试上面的tty 函数(也就是ttyname,由于避免和内部库命名函数冲突,我随便起了个名字tty)

/************************************************************
code writer : EOF
code date   : 2014.04.15 00:32
e-mail      : jasonleaster@gmail.com
code purpose:

        Just a demo for ourselves' ttyname--tty.There would 
be conflict with libc when we use our function's name 
as ttyname. So I change the name of the function into tty.
It's Nothing.

***********************************************************/
#include <stdio.h>
#include </Ad_Pro_in_Unix/chapter_18/pro_18_15.c>

int main()
{
        char* name;

        if(isatty(0))
        {
                name = tty(0);
                if(name == NULL)
                {
                        name = "undefined\n";
                }
        }
        else
        {
                name = "not a tty\n";
        }

        printf("fd 0 :%s\n",name);

        if(isatty(1))
        {
                name = tty(1);
                if(name == NULL)
                {
                        name = "undefined\n";
                }
        }
        else
        {
                name = "not a tty\n";
        }

        printf("fd 1 :%s\n",name);

        if(isatty(2))
        {
                name = tty(2);
                if(name == NULL)
                {
                        name = "undefined\n";
                }
        }
        else
        {
                name = "not a tty\n";
        }

        printf("fd 2 :%s\n",name); 
        return 0;

}



liuzjian@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out
fd 0 :/dev/pts/0
fd 1 :/dev/pts/0
fd 2 :/dev/pts/0


这三行简单的输出。。。我调了多久。。。。T-T

Canonical Mode


Canonical mode is simple: we issue a read, and the terminal driver returns when a line has been entered.  Several conditions cause the read to return.

•The read returns when the requested number of bytes have been read.  We don’t  have to read a complete line. If we read a partial line, no information is lost; the  next read starts where the previous read stopped.

•The read returns when a line delimiter is encountered.  Recall from Section 18.3  that  the  following  characters are interpreted  as  end  of  line  in  canonical  mode:  NL, EOL, EOL2, and EOF. Also, recall from Section 18.5 that if ICRNL is set and  if IGNCR is not set, then the CR character also terminates a line, since it acts just
like the NL character.  Of these five line delimiters, one(EOF ) is discarded by the terminal driver when  it’s processed.  The other  four  ar ereturned  to  the  caller  as  the  last  character  of  the line.

•The read also returns if a signal is caught and if the function is not automatically  restarted (Section 10.5).

getpass的一个实现:
#include <signal.h>
#include <stdio.h>
#include <termios.h>

#define MAX_PASS_LEN    8

char* getpass(const char* prompt)
{
        static char     buf[MAX_PASS_LEN + 1];
        char            *ptr;
        sigset_t        sig,osig;
        struct termios  ts,ots;
        FILE            *fp;
        int             c;

        if((fp = fopen(ctermid(NULL),"r+")) == NULL)
        {
                return NULL;
        }

        setbuf(fp,NULL);//unbuffer

        sigemptyset(&sig);
        sigemptyset(&osig);
        sigaddset(&sig,SIGINT);
        sigaddset(&sig,SIGTSTP);
        sigprocmask(SIG_BLOCK,&sig,&osig);

        tcgetattr(fileno(fp),&ts);
        ots = ts;
        ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
        tcsetattr(fileno(fp),TCSAFLUSH,&ts);
        fputs(prompt,fp);

        ptr = buf;
        while((c = getc(fp)) != EOF && c != '\n')
        {
                if(ptr < &buf[MAX_PASS_LEN])
                {
                        *ptr++ = c;
                }
        }

        *ptr = 0;//equivalent to ptr = NULL

        putc('\n',fp);
        tcsetattr(fileno(fp),TCSAFLUSH,&ots);
        sigprocmask(SIG_SETMASK,&osig,NULL);
        fclose(fp);

        return buf;
}


#include <stdio.h>
#include </Ad_Pro_in_Unix/chapter_18/pro_18_17.c>

int main()
{
        char *ptr;

        if((ptr = getpass("Enter password:")) == NULL)
        {
                printf("getpass error\n");
        }

        printf("password:%s\n",ptr);

        while(*ptr != 0)
        {
                *ptr++ = 0;
        }

        return 0;
}
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out
Enter password:
password:hellowor

注意我输入了helloworld但是输出只有hellowor,因为buffer只有9个字节大小

#define MAX_PASS_LEN    8
static char     buf[MAX_PASS_LEN + 1];


Noncanonical Mode

                In noncanonical  mode,  the  input  data  is  not  assembled  into lines.  The following  special  characters  (Section 18.3)  arenot  processed:  ERASE,  KILL, EOF ,NL, EOL, EOL2, CR, REPRINT ,STAT US, and WERASE.




       The  solution  is  to  tell  the  system  to  return  when  either  a  specified  amount  of  data has  been  read  or  after a  given  amount  of  time  has  passed. This  technique  uses  two variables  in  the c_cc array  in  the termios structure:  MIN  and  TIME. These  two elements of the array are indexed by the names VMIN and VTIME. MIN specifies the minimum number of bytes before a read returns.  TIME specifies the number of tenths of a second to wait for data to arrive. There are f our cases.








          由于可能年代问题,Unix定义的键盘信号和现在的键盘标准不同,我测试过在Ubuntu linux 3.0  lenove y480 intel i5 下面delete产生的并不是0177。。。
         于是这个demo就没有给出。。。如果以后能够解决就update。。



Terminal  Window Size


linux下面在ioctl-type.h 里面找到的结构体定义


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





        Other  than  storing  the  current  value  of  the  structure and  generating  a  signal when  the  value  changes, the kernel  does  nothing  else  with  this  structure. Interpreting the structure is entirely up to the application. This feature is provided to notify applications (such as the vi editor) when the window size  changes. When  it  receives the signal,  the  application  can  fetch  the  new  size  and redraw the screen



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

#ifndef TIOCGWINSZ
        #include <sys/ioctl.h>
#endif

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

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

        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);
}

int main()
{
        struct sigaction sa;

        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sa.sa_handler = sig_winch;

        if(isatty(STDIN_FILENO) == 0)
        {
                return 1;
        }

        if(sigaction(SIGWINCH,&sa,NULL) < 0)
        {
                printf("sigaction error\n");
        }

        pr_winsize(STDIN_FILENO);

        for(;;)
        {
                pause();
        }

        return 0;
}
一开始傻得要shi。。。我看书上demo不知道为什么rows 和 columns为什么会变。。。自己调整terminal的窗口大小,然后给程序signal。。。就OK了。。。
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ ./a.out &
[1] 6822
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ 41 rows, 144 columns
kill -WINCH 6822
SIGWINCH received
41 rows, 144 columns
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ kill -WINCH 6822


jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ kill -WINCH 6822
SIGWINCH received
26 rows, 100 columns
jasonleaster@ubuntu:/Ad_Pro_in_Unix/chapter_18$ kill -WINCH 
6822
SIGWINCH received
26 rows, 56 columns


记得kill 把这个background process给杀了。。。。嗯,不要手下留情。















  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值