1、临界区
一段修改一个数据结构的代码如果运行时被打断将导致数据的不完整或损毁,则称这段代码为临界区。当程序处理信号时,必须决定哪一段代码为临界区,然后设法保护这段代码。保护临界区的最简单的办法就是阻塞或忽略那些处理函数将要使用或修改特定数据的信号。
2、阻塞信号:sigprocmask和sigsetops
在任何时候一个进程都有一些信号被阻塞,这个信号集被称为信号挡板(signal mask),通过sigprocmask可以修改这个被阻塞的信号集。sigprocmask根据所给信号集来修改当前被阻塞的信号集。
sigprocmask系统调用:
- sigprocmask():修改当前信号挡板,res=sigprocmask(int how,const sigset_t *sigs,sigset_t *prev),当how的值分别为SIG_BLOCK、SIG_UNBLOCK、SIG_SET时,*sigs所指定的信号将被添加、删除、替换。之前的信号挡板设置将被复制到*prev中。
用sigsetops构造信号集,一个sigset_t是一个抽象的信号集,可以通过一些函数来添加或删除信号。基本函数如下:
- sigempty(sigset_t *setp):清除由setp指向列表中的所有信号
- sigfillset(sigset_t *setp):添加所有的信号到setp指向的列表
- sigaddset(sigset_t *setp,int signum):添加signum到setp指向的列表
- sigdelset(sigset_t *setp,int signum):从setp指向的列表中删除signum所标识的信号
在修改一个tty驱动或者文件描述符的时候保存先前的设置,然后用保存的设置来恢复原来的信号挡板,这样就可以防止数据损毁。除非目的就是修改获取的资源,否则释放资源时恢复获取时的状态是个好习惯。
3、kill:从另一个进程发送的信号
信号来自间隔计时器、终端程序、内核或者进程,一个进程可以通过kill系统调用向另一个进程发送信号。
kill系统调用:
- kill():向一个进程发送信号,kill(pid_t pid,int sig),向目的进程pid发送信号sig,其中发送信号的进程的用户必须和目的进程的用户相同或者发送进程的用户是一个超级用户,所以一个进程也可以向自己发送信号。
4、输入信号:异步IO
本章的动画和游戏等待两类事件:计时器信号和键盘输入。设置间隔计时器的处理函数来控制动画,通过调用getch阻塞程序以等待键盘输入。除了阻塞还能像得到计时器信号那样通过信号来得到用户的输入,程序可以要求内核在得到输入时发送信号。Unix有两个异步输入系统。一种是当输入就绪时发送信号,另一种系统当输入被读入时发送信号。UCB中通过设置文件描述块的O_ASYNC位来实现第一种方法。第二种方法是POSIX标准,它调用aio_read。
(1)使用O_ASYNC
- 首先建立和设置在键盘输入时被调用的处理函数on_input
- 使用fcntl的F_SETOWN命令来告诉内核发送输入通知信号SIGIO给进程
- 通过调用fcntl来设置文件描述符0中的O_ASYNC位来打开输入信号
- 循环调用pause等待来自计时器或键盘的信号
当有一个从键盘来的字符到达,内核向进程发送SIGIO信号,SIGIO的处理函数使用标准的curses函数getch来读入这个字符。当计时器间隔超时,内核发送以前已经处理的SIGALRM信号。
(2)使用aio_read
相比设置文件描述符的O_ASYNC,使用aio_read更加灵活和复杂。
- 设置输入被读入时所调用的处理函数on_input
- 设置struct kbcbuf中的变量来指明等待什么类型的输入,当输入发生时产生什么信号。在程序中,需要从文件描述符0中读入一个字符,当字符被读入时希望收到SIGIO信号。
- 通过将以上定义的结构体传给aio_read来递交读入请求。和调用一般的read不同,aio_read不会阻塞进程,相反aio_read会在完成时发送信号
- 当用户输入字符,aio_read向进程发送SIGIO信号,响应处理信号被调用。最后实现处理函数,函数通过调用aio_return来得到输入的字符,然后处理这个字符。
5、异步输入的好处
本章的弹球游戏不需要异步输入,但操作系统需要。内核要运行程序而不能把时间浪费在等待用户输入上。内核设置当键盘得到输入时被调用的处理函数。内核从一个运行中的程序跳转到处理函数,处理输入,再跳回运行中的程序。在临界区,内核阻塞信号。
内核的异步输入是由硬件实现的,而进程的异步输入是由软件实现的。当用户按下一个键,一个电子信号被送到键盘端口,键盘端口产生一个真实的硬件信号,这个信号引发控制从视频游戏的运行中转到键盘的设备驱动。内核的设备驱动代码从输入端口读入字符,然后将读入的字符通过终端驱动进行处理。如果驱动的文件描述符被设置为异步输入,内核向进程发送信号。当进程继续运行时,控制转移到进程内的信号处理函数。
6、使用计时器和信号:视频游戏程序
bounce1d.c
在一条直线上控制动画,使用signal信号处理方式
#include<stdio.h>
#include<curses.h>
#include<signal.h>
#include<string.h>
#include<sys/time.h>
#define MESSAGE "hello"
#define BLANK " "
int row;
int col;
int dir;
void move_msg(int signum)
{
signal(SIGALRM,move_msg);
move(row,col);
addstr(BLANK);
col+=dir;
move(row,col);
addstr(MESSAGE);
refresh();
if(dir==-1&&col<=0)
dir=1;
else if(dir==1&&col+strlen(MESSAGE)>=COLS)
dir=-1;
}
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec,n_usecs;
n_sec=n_msecs/1000;
n_usecs=(n_msecs%1000)*1000L;
new_timeset.it_interval.tv_sec=n_sec;
new_timeset.it_interval.tv_usec=n_usecs;
new_timeset.it_value.tv_sec=n_sec;
new_timeset.it_value.tv_usec=n_usecs;
return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
int main()
{
int delay;
int newdelay;
char c;
initscr();
crmode();
noecho();
clear();
row=10;
col=10;
dir=1;
delay=200; //200ms=0.2seconds
move(row,col);
addstr(MESSAGE);
signal(SIGALRM,move_msg);
set_ticker(delay);
while(1)
{
newdelay=0;
c=getch();
if(c=='q') break;
if(c==' ') dir=-dir;
if(c=='f'&&delay>2) newdelay=delay/2;
if(c=='s') newdelay=delay*2;
if(newdelay>0)
set_ticker(delay=newdelay);
}
endwin();
return 0;
}
bounce2d.c
二维动画,使用signal信号处理方式
#include<curses.h>
#include<sys/time.h>
#include<signal.h>
#define BLANK ' '
#define DFL_SYMBOL 'o'
#define TOP_ROW 5
#define BOT_ROW 20
#define LEFT_EDGE 10
#define RIGHT_EDGE 70
#define X_INIT 10
#define Y_INIT 10
#define TICKS_PER_SEC 50 //1/50second
#define X_TTM 5 //移动间隔信号数
#define Y_TTM 8
struct ppball{
int y_pos,x_pos,
y_ttm,x_ttm,
y_ttg,x_ttg,
y_dir,x_dir;
char symbol;
};
struct ppball the_ball;
int bounce_or_lose(struct ppball *bp)
{
int return_val=0;
if(bp->y_pos==TOP_ROW)
{
bp->y_dir=1;
return_val=1;
}
else if(bp->y_pos==BOT_ROW)
{
bp->y_dir=-1;
return_val=1;
}
if(bp->x_pos==LEFT_EDGE)
{
bp->x_dir=1;
return_val=1;
}
else if(bp->x_pos==RIGHT_EDGE)
{
bp->x_dir=-1;
return_val=1;
}
return return_val;
}
void ball_move(int signum)
{
int y_cur,x_cur,moved;
signal(SIGALRM,SIG_IGN);
y_cur=the_ball.y_pos;
x_cur=the_ball.x_pos;
moved=0;
if(the_ball.y_ttm>0&&the_ball.y_ttg--==1)
{
the_ball.y_pos+=the_ball.y_dir;
the_ball.y_ttg=the_ball.y_ttm;
moved=1;
}
if(the_ball.x_ttm>0&&the_ball.x_ttg--==1)
{
the_ball.x_pos+=the_ball.x_dir;
the_ball.x_ttg=the_ball.x_ttm;
moved=1;
}
if(moved)
{
mvaddch(y_cur,x_cur,BLANK);
mvaddch(the_ball.y_pos,the_ball.x_pos,the_ball.symbol);
bounce_or_lose(&the_ball);
move(LINES-1,COLS-1);
refresh();
}
signal(SIGALRM,ball_move);
}
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec,n_usecs;
n_sec=n_msecs/1000;
n_usecs=(n_msecs%1000)*1000L;
new_timeset.it_interval.tv_sec=n_sec;
new_timeset.it_interval.tv_usec=n_usecs;
new_timeset.it_value.tv_sec=n_sec;
new_timeset.it_value.tv_usec=n_usecs;
return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
void set_up()
{
the_ball.y_pos=Y_INIT;
the_ball.x_pos=X_INIT;
the_ball.y_ttg=the_ball.y_ttm=Y_TTM;
the_ball.x_ttg=the_ball.x_ttm=X_TTM;
the_ball.y_dir=the_ball.x_dir=1;
the_ball.symbol=DFL_SYMBOL;
initscr();
crmode();
noecho();
signal(SIGINT,SIG_IGN);
mvaddch(the_ball.y_pos,the_ball.x_pos,the_ball.symbol);
refresh();
signal(SIGALRM,ball_move);
set_ticker(1000/TICKS_PER_SEC); //millisecs per ticker
}
void wrap_up()
{
set_ticker(0);
endwin();
}
int main()
{
int c;
set_up();
while((c=getchar())!='q')
{
if(c=='d') the_ball.x_ttm--;
else if(c=='a') the_ball.x_ttm++;
else if(c=='w') the_ball.y_ttm--;
else if(c=='s') the_ball.y_ttm++;
}
wrap_up();
return 0;
}
bounce_async.c
输入信号使用异步IO O_ASYNC的处理方式
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/time.h>
#include<curses.h>
#include<signal.h>
#include<fcntl.h>
#define MESSAGE "hello"
#define BLANK " "
int row=10;
int col=10;
int dir=1;
int delay=100;
bool done=false;
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec,n_usecs;
n_sec=n_msecs/1000;
n_usecs=(n_msecs%1000)*1000L;
new_timeset.it_interval.tv_sec=n_sec;
new_timeset.it_interval.tv_usec=n_usecs;
new_timeset.it_value.tv_sec=n_sec;
new_timeset.it_value.tv_usec=n_usecs;
return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
void on_input(int signum)
{
char c=getch();
if(c=='q' || c==EOF)
done=true;
else if(c==' ')
dir=-dir;
}
void on_alarm(int signum)
{
signal(SIGALRM,on_alarm);
mvaddstr(row,col,BLANK);
col+=dir;
mvaddstr(row,col,MESSAGE);
refresh();
if(dir==-1&&col<=0)
dir=1;
else if(dir==1&&col+strlen(MESSAGE)>=COLS)
dir=-1;
}
void enable_kbd_signals()
{
int fd_flags;
fcntl(0,F_SETOWN,getpid());
fd_flags=fcntl(0,F_GETFL);
fcntl(0,F_SETFL,(fd_flags|O_ASYNC));
}
int main()
{
initscr();
crmode();
noecho();
clear();
signal(SIGIO,on_input);
enable_kbd_signals();
signal(SIGALRM,on_alarm);
set_ticker(delay);
move(row,col);
addstr(MESSAGE);
while(done==false)
pause();
endwin();
return 0;
}
bounce_aio.c
输入信号使用异步IO aio_read的处理方式
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/time.h>
#include<curses.h>
#include<signal.h>
#include<aio.h>
#include<fcntl.h>
#define MESSAGE "hello"
#define BLANK " "
int row=10;
int col=10;
int dir=1;
int delay=100;
bool done=false;
struct aiocb kbcbuf;
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec,n_usecs;
n_sec=n_msecs/1000;
n_usecs=(n_msecs%1000)*1000L;
new_timeset.it_interval.tv_sec=n_sec;
new_timeset.it_interval.tv_usec=n_usecs;
new_timeset.it_value.tv_sec=n_sec;
new_timeset.it_value.tv_usec=n_usecs;
return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
void on_input(int signum)
{
char c;
char *cp=(char *)kbcbuf.aio_buf;
if(aio_error(&kbcbuf)!=0)
perror("reading failed");
else
if(aio_return(&kbcbuf)==1)
{
c=*cp;
if(c=='q' || c==EOF)
done=true;
else if(c==' ')
dir=-dir;
}
aio_read(&kbcbuf);
}
void on_alarm(int signum)
{
signal(SIGALRM,on_alarm);
mvaddstr(row,col,BLANK);
col+=dir;
mvaddstr(row,col,MESSAGE);
refresh();
if(dir==-1&&col<=0)
dir=1;
else if(dir==1&&col+strlen(MESSAGE)>=COLS)
dir=-1;
}
void setup_aio_buffer()
{
static char input[1];
kbcbuf.aio_fildes=0;
kbcbuf.aio_buf=input;
kbcbuf.aio_nbytes=1;
kbcbuf.aio_offset=0;
kbcbuf.aio_sigevent.sigev_notify=SIGEV_SIGNAL;
kbcbuf.aio_sigevent.sigev_signo=SIGIO;
}
int main()
{
initscr();
crmode();
noecho();
clear();
signal(SIGIO,on_input);
setup_aio_buffer();
aio_read(&kbcbuf);
signal(SIGALRM,on_alarm);
set_ticker(delay);
move(row,col);
addstr(MESSAGE);
while(done==false)
pause();
endwin();
return 0;
}