对磁盘文件和设备文件不加以区分的程序被称为软件工具,例如who,ls,sort,uniq,grep等。软件工具从标准输入读取字节,进行一些处理,然后将结果的字节流写到到标准输出。
3个标准的文件描述符STDIN,STDOUT,STDERR
用户程序的例子有vi,emacs,pine,more等,这些程序设置终端驱动程序的击键和输出处理方式,驱动程序有很多设置,但是用户程序常用到的有:
(1)立即响应击键事件
(2)有限的输入集
(3)输入超时
(4)屏蔽Ctrl + c
2.终端驱动程序的模式
/*rotate.c
*puurpose: use for showing tty mode
*/
#include<stdio.h>
#include<ctype.h>
int main()
{
int c;
while(( c = getchar()) != EOF)
{
if( c == 'z')
c = 'a';
else if(islower(c))
c++;
putchar(c);
}
}
~
1.规范模式:缓冲和编辑
上述程序执行结果说明:
(1)击键的同时字符在屏幕显示,但直到按下回车键,程序才收到输入;
(2)Ctrl-C键结束输入并终止程序。
rotate程序不做这些操作。缓冲,回显,编辑和控制处理都由驱动程序完成。
2.非规范处理
stty -icanon关闭了驱动程序中的规范模式处理,非规范模式没有缓冲,因此输入字符直接回显
stty -icanon -echo还关闭了回显模式,驱动程序不再显示输入的字符,输出仅来自程序
终端模式总结
a.规范模式
驱动程序输入的字符保存在缓冲区,仅在接收到回车键才将缓冲区的字符发送到程序,缓冲数据使驱动程序可以实现基本的编辑功能,如删除字符、整行。
b.非规范模式
缓冲和编辑功能被关闭,终端处理器仍旧进行特定字符的处理,如ctrl c及回车键和换行符之间的转换,但是用于删除等编辑键没有意义。
c.raw模式
每个处理步骤都被一个独立的位控制,程序可以随意关闭所有这些处理步骤。当所有处理都被关闭后,驱动程序将输入直接传递给程序。
3.编写一个用户程序:play_again.c
(1)play_again0.c
/* play_again0.c
* purpose: ask if user wants another transaction
* method: ask a question, wait for yes/no answer
* returns: 0=>yes, 1=>no
* better: eliminate need to press return
*/
#include<stdio.h>
#include<termios.h>
#define QUESTION "Do you want another transaction"
int get_response( char * );
int main()
{
int response;
response = get_response(QUESTION); /* get some answer */
return response;
}
int get_response(char *question)
/*
* purpose: ask a question and wait for a y/n answer
* method: use getchar and ignore non y/n answers
* returns: 0=>yes, 1=>no
*/
{
printf("%s (y/n)?", question);
while(1){
switch( getchar() ){
case 'y':
case 'Y': return 0;
case 'n':
case 'N':
case EOF: return 1;
}
}
}
上述程序存在两个问题,第一用户必须按回车键,程序才能接受数据。第二,当用户按下回车键时,程序接收整行的数据并对它进行处理。
(2)play_again1.c 及时响应
改进方法:关闭规范输入,使得程序在用户敲键的同时得到输入的字符
/* play_again1.c
* purpose: ask if user wants another transaction
* method: set tty into char-by-char mode, read char, return result
* returns: 0=>yes, 1=>no
* better: do no echo inappropriate input
*/
#include<stdio.h>
#include<termios.h>
#define QUESTION "Do you want another transaction"
int get_response(char *);
void set_crmode();
void tty_mode(int how);
int main()
{
int response;
tty_mode(0); /* save tty mode */
set_crmode(); /* set chr-by-chr mode */
response = get_response(QUESTION); /* get some answer */
tty_mode(1); /* restore tty mode */
return response;
}
int get_response(char *question)
/*
* purpose: ask a question and wait for a y/n answer
* method: use getchar and complain about non y/n answers
* returns: 0=>yes, 1=>no
*/
{
int input;
printf("%s (y/n)?", question);
while(1){
switch( input = getchar() ){
case 'y':
case 'Y': return 0;
case 'n':
case 'N':
case EOF: return 1;
default:
printf("\ncannot understand %c, ", input);
printf("Please type y or no\n");
}
}
}
void set_crmode()
/*
* purpose: put file descriptor 0 (i.e. stdin) into chr-by-chr mode
* method: use bits in termios
*/
{
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read curr. setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
/* how == 0 => save current mode, how == 1 => restore mode */
void tty_mode(int how)
{
static struct termios original_mode;
if ( how == 0 )
tcgetattr(0, &original_mode);
else
tcsetattr(0, TCSANOW, &original_mode);
}
上述程序非法输入的提示字符很多,可以关闭回显模式,丢掉不需要的字符,直到得到可接受的字符为止。
(3)play_again2.c------忽略非法键,关闭回显
/* play_again2.c
* purpose: ask if user wants another transaction
* method: set tty into char-by-char mode and no-echo mode
* read char, return result
* returns: 0=>yes, 1=>no
* better: timeout if user walks away
*
*/
#include<stdio.h>
#include<termios.h>
#define QUESTION "Do you want another transaction"
int get_response(char *);
void tty_mode(int how);
void set_cr_noecho_mode();
int main()
{
int response;
tty_mode(0); /* save mode */
set_cr_noecho_mode(); /* set -icanon, -echo */
response = get_response(QUESTION); /* get some answer */
tty_mode(1); /* restore tty state */
return response;
}
int get_response(char *question)
/*
* purpose: ask a question and wait for a y/n answer
* method: use getchar and ignore non y/n answers
* returns: 0=>yes, 1=>no
*/
{
printf("%s (y/n)?", question);
while(1){
switch( getchar() ){
case 'y':
case 'Y': return 0;
case 'n':
case 'N':
case EOF: return 1;
}
}
}
void set_cr_noecho_mode()
/*
* purpose: put file descriptor 0 into chr-by-chr mode and noecho mode
* method: use bits in termios
*/
{
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read curr. setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
/* how == 0 => save current mode, how == 1 => restore mode */
void tty_mode(int how)
{
static struct termios original_mode;
if ( how == 0 )
tcgetattr(0, &original_mode);
else
tcsetattr(0, TCSANOW, &original_mode);
}
上述程序还存在一个问题,如果顾客没有输入正确的y和n就离开,就一直处于超时状态,直到下一个顾客输入。所有如果用户程序包含超时特征会变得更安全。
(3)当调用getchar或者read从文件描述符读取输入时,这些调用通常会等待输入。阻塞不仅仅是终端连接的属性,而是任何一个打开文件的属性,程序可以使用fcntl或者open为文件描述符启动非阻塞输入。
play_again3.c-----使用非阻塞模式实现超时响应
/* play_again3.c
* purpose: ask if user wants another transaction
* method: set tty into chr-by-chr, no-echo mode
* set tty into no-delay mode
* read char, return result
* returns: 0=>yes, 1=>no, 2=>timeout
* better: reset terminal mode on Interrupt
*/
#include<stdio.h>
#include<termios.h>
#include<fcntl.h>
#include<string.h>
#define ASK "Do you want another transaction"
#define TRIES 3 /* max tries */
#define SLEEPTIME 2 /* time per try */
void tty_mode(int);
void set_cr_noecho_mode();
void set_nodelay_mode();
int get_response(char *, int);
char get_ok_char();
int main()
{
int response;
tty_mode(0); /* save current mode */
set_cr_noecho_mode(); /* set -icanon, -echo */
set_nodelay_mode(); /* noinput => EOF */
response = get_response(ASK, TRIES); /* get some answer */
tty_mode(1); /* restore orig mode */
return response;
}
int get_response( char *question , int maxtries)
/*
* purpose: ask a question and wait for a y/n answer or maxtries
* method: use getchar and complain about non-y/n input
* returns: 0=>yes, 1=>no, 2=>timeout
*/
{
int input;
printf("%s (y/n)?", question); /* ask */
fflush(stdout); /* force output */
while ( 1 ){
sleep(SLEEPTIME); /* wait a bit */
input = tolower(get_ok_char()); /* get next chr */
if ( input == 'y' )
return 0;
if ( input == 'n' )
return 1;
if ( maxtries-- == 0 ) /* outatime? */
return 2; /* sayso */
}
}
/*
* skip over non-legal chars and return y,Y,n,N or EOF
*/
char get_ok_char()
{
int c;
while( ( c = getchar() ) != EOF && strchr("yYnN",c) == NULL )
;
return c;
}
void set_cr_noecho_mode()
/*
* purpose: put file descriptor 0 into chr-by-chr mode and noecho mode
* method: use bits in termios
*/
{
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read curr. setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
void set_nodelay_mode()
/*
* purpose: put file descriptor 0 into no-delay mode
* method: use fcntl to set bits
* notes: tcsetattr() will do something similar, but it is complicated
*/
{
int termflags;
termflags = fcntl(0, F_GETFL); /* read curr. settings */
termflags |= O_NDELAY; /* flip on nodelay bit */
fcntl(0, F_SETFL, termflags); /* and install 'em */
}
/* how == 0 => save current mode, how == 1 => restore mode */
/* this version handles termios and fcntl flags */
void tty_mode(int how)
{
static struct termios original_mode;
static int original_flags;
if ( how == 0 ){
tcgetattr(0, &original_mode);
original_flags = fcntl(0, F_GETFL);
}
else {
tcsetattr(0, TCSANOW, &original_mode);
fcntl( 0, F_SETFL, original_flags);
}
}
程序使用fcntl为文件描述符开启O_NDELAY标志,开启非阻塞模式;在get_response中使用了sleep和计数器maxtries。
注意使用了一个fflush刷新缓存,因为终端程序不仅缓存输入还缓存输出,直到它收到一个换行符或者程序试图从终端读取输入,驱动程序才会缓冲输出。因此如果没有那一行,在调用getchar()之前,提示符将不会输出。
4.信号
1.ctrl - c做什么
输入ctrl-c程序便被终止了,终端程序在这里起了相应的作用。
生成信号的请求来自3个地方
(1)用户
用户通过输入ctrl-C、Ctrl-\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号
(2)内核
当进程执行出错时,内核给进程发送一个信号,例如非法段存取、浮点数溢出,或是一个非法机器指令。内核也利用信号通知进程特定事件的发生。
(3)进程
一个进程可以通过系统调用kill给另一个进程发送信号。一个进程可以和另一个进程通过信号通信。
由进程的某个操作产生的信号称为同步信号,例如被除零。由像用户击键这样的进程外的事件引起的信号被称为异步信号。
查看signal(7)联机帮助,常用信号如下:
Signal Value Action Comment
──────────────────────────────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
中断信号SIGINT(Ctrl-C),退出信号SIGQUIT(Ctrl-\);停止信号SIGSTOP(Ctrl-Z)。其中SIGSTOP和SIGKILL信号不可以被忽略和捕捉
进程处理信号的3种方式:
(1)接受默认处理(通常是消亡)
手册上列出了每个信号的默认处理,SIGINT的默认处理是消亡。
signal(SIGINT, SIG_DEL);
(2)忽略信号
程序可以通过以下调用来告诉内核,它需要忽略SIGINT信号。
signal(SIGINT, SIG_INT);
(3)调用一个函数
signal(signum, functionname);
/* sigdemo1.c - shows how a signal handler works.
* - run this and press Ctrl-C a few times
*/
#include<stdio.h>
#include<signal.h>
void f(int); /* declare the handler */
void main()
{
int i;
signal( SIGINT, f ); /* install the handler */
for(i=0; i<5; i++ ){ /* do something else */
printf("hello\n");
sleep(1);
}
}
void f(int signum) /* this function is called */
{
printf("OUCH!\n");
}
捕捉信号来处理play_again4.c程序
/* play_again4.c
* purpose: ask if user wants another transaction
* method: set tty into chr-by-chr, no-echo mode
* set tty into no-delay mode
* read char, return result
* resets terminal modes on SIGINT, ignores SIGQUIT
* returns: 0=>yes, 1=>no, 2=>timeout
* better: reset terminal mode on Interrupt
*/
#include<stdio.h>
#include<termios.h>
#include<fcntl.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>
#define ASK "Do you want another transaction"
#define TRIES 3 /* max tries */
#define SLEEPTIME 2 /* time per try */
void tty_mode(int);
void set_cr_noecho_mode();
void ctrl_c_handler(int);
void set_nodelay_mode();
int get_response(char *, int);
char get_ok_char();
int main()
{
int response;
void ctrl_c_handler(int);
tty_mode(0); /* save current mode */
set_cr_noecho_mode(); /* set -icanon, -echo */
set_nodelay_mode(); /* noinput => EOF */
signal( SIGINT, ctrl_c_handler ); /* handle INT */
signal( SIGQUIT, SIG_IGN ); /* ignore QUIT signals */
response = get_response(ASK, TRIES); /* get some answer */
tty_mode(1); /* reset orig mode */
return response;
}
int get_response( char *question , int maxtries)
/*
* purpose: ask a question and wait for a y/n answer or timeout
* method: use getchar and complain about non-y/n input
* returns: 0=>yes, 1=>no
*/
{
int input;
printf("%s (y/n)?", question); /* ask */
fflush(stdout); /* force output */
while ( 1 ){
sleep(SLEEPTIME); /* wait a bit */
input = tolower(get_ok_char()); /* get next chr */
if ( input == 'y' )
return 0;
if ( input == 'n' )
return 1;
if ( maxtries-- == 0 ) /* outatime? */
return 2; /* sayso */
}
}
/*
* skip over non-legal chars and return y,Y,n,N or EOF
*/
char get_ok_char()
{
int c;
while( ( c = getchar() ) != EOF && strchr("yYnN",c) == NULL )
;
return c;
}
void set_cr_noecho_mode()
/*
* purpose: put file descriptor 0 into chr-by-chr mode and noecho mode
* method: use bits in termios
*/
{
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read curr. setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
void set_nodelay_mode()
/*
* purpose: put file descriptor 0 into no-delay mode
* method: use fcntl to set bits
* notes: tcsetattr() will do something similar, but it is complicated
*/
{
int termflags;
termflags = fcntl(0, F_GETFL); /* read curr. settings */
termflags |= O_NDELAY; /* flip on nodelay bit */
fcntl(0, F_SETFL, termflags); /* and install 'em */
}
/* how == 0 => save current mode, how == 1 => restore mode */
/* this version handles termios and fcntl flags */
void tty_mode(int how)
{
static struct termios original_mode;
static int original_flags;
static int stored = 0;
if ( how == 0 ){
tcgetattr(0, &original_mode);
original_flags = fcntl(0, F_GETFL);
stored = 1;
}
else if ( stored ) {
tcsetattr(0, TCSANOW, &original_mode);
fcntl( 0, F_SETFL, original_flags);
}
}
void ctrl_c_handler(int signum)
/*
* purpose: called if SIGINT is detected
* action: reset tty and scram
*/
{
tty_mode(1);
exit(2);
}
信号是从内核发送给进程的一种简短消息。信号可能来自用户、其他进程或内核本身。进程可以告诉内核,在它收到信号时需要作出怎样的响应。