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
#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.
Furthermore, when the change takes place, all input data that has not been read is discarded (flushed).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.
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
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
?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 justlike 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).
#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
#define MAX_PASS_LEN 8
static char buf[MAX_PASS_LEN + 1];
Noncanonical Mode
Terminal Window Size
struct winsize { unsigned short int ws_row; unsigned short int ws_col; unsigned short int ws_xpixel; unsigned short int ws_ypixel; };
#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