第6章 为用户编程:终端控制和信号

对磁盘文件和设备文件不加以区分的程序被称为软件工具,例如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);
}

信号是从内核发送给进程的一种简短消息。信号可能来自用户、其他进程或内核本身。进程可以告诉内核,在它收到信号时需要作出怎样的响应。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值