1. 信号基础
信号是软件中断,提供了一种处理异步事件的方法。由于产生信号的事件对进程而言是随机出现的,所以进程不能简单的测试一个变量来判别是否出现了一个信号,而是必须告诉内核“在此信号出现时,请执行以下操作”。一般按以下三种方式操作:
1) 忽略此信号。
2) 捕捉信号
3) 执行系统默认动作。
2. signal函数
简单的signal接口定义如下:
void (*signal(int signo, void (*func)(int)))(int);
signo参数是信号名。func的值是常量SIG_IGN,SIG_DFL或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示 忽略此信号。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,则在信号发生时,调用此函数(信号处理函数)
#include <signal.h>
#include <stdio.h>
static void sig_usr( int );
int main( void )
{
if ( signal( SIGUSR1, sig_usr ) == SIG_ERR )
printf("can't catch SIGUSR1\n");
if ( signal( SIGUSR2, sig_usr ) == SIG_ERR )
printf("can't catch SIGUSR2\n");
for ( ; ; )
pause();
return 0;
}
static void sig_usr( int signo )
{
if ( signo == SIGUSR1 )
printf("received SIGUSR1\n");
else if ( signo == SIGUSR2 )
printf("received SIGUSR2\n");
else{
printf("received signal %d\n", signo );
}
}
程序输出:
3. 中断的系统调用
早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其error被设置为EINTR。低速系统调用是可能会使进程永远阻塞的一类系统调用,它们包括:
1) 在读某些类型的文件(管道,终端设备以及网络设备)时,如果数据并不存在则可能会使调用者永远阻塞。
2) 在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞。
3) 打开某些类型文件,在某种条件发生之前也可能会使调用者阻塞。
4) pause和wait函数
5) 某些ioctl操作
6) 某些进程间通信函数
与被中断的系统调用相关的问题是必须显式的处理出错返回。
4. 可重入函数
不可重入函数的含义是:如果信号处理程序和主程序中调用相同的函数,则会造成内部指针出错。通常不可重入的函数原因如下:1)已知它们使用静态数据结构。2)它们调用malloc或free。3)它们是标准I/O函数。
#include <stdio.h>
#include <pwd.h>
#include <signal.h>
static void my_alarm( int signo )
{
struct passwd *rootptr;
printf("in signal handler\n");
if ( ( rootptr = getpwnam( "root" ) ) == NULL )
printf("getpwnam(root) error\n");
alarm( 1 );
}
int main( void )
{
struct passwd *ptr;
signal( SIGALRM, my_alarm );
alarm(1);
// sleep( 2 );
printf("out signal handler\n");
for( ; ; ){
if (( ptr = getpwnam("leichaojian")) == NULL)
printf("getpwnam error\n");
if ( strcmp(ptr->pw_name,"leichaojian") != 0 )
printf("return value corrupted! pw_name=%s\n", ptr->pw_name);
}
return 0;
}
正常情况下,程序应该每个一秒输出:in signal handler,但实际上却是中断,停留在终端阻塞上了:
5. SIGCLD语义
对于SIGCLD早期处理如下:
1) 如果进程特地的设置该信号的配置为SIG_IGN,则调用进程的子进程将不会产生僵死进程。
2) 如果将SIGCLD的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果有则调用SIGCLD处理程序。
6. kill和raise函数
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
#include <signal.h>
int kill( pid_t pid, int signo );
int raise( int signo );
raise( signo ) == kill( getpid(), signo )
kill的pid参数有4种不同的情况:
1) pid>0: 将该信号发送给进程ID为pid的进程
2) pid==0: 将该信号发送给与发送进程属于同一进程组的所有进程,而且发送进程具有向这些进程发送信号的权限。
3) pid<0: 将该信号发送给其进程组ID等于的绝对值,而且发送进程具有向其发送信号的权限。
4) pid==-1: 将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。
7. alarm和pause函数
使用alarm函数可以设置一个计时器,在将来某个特定的时间该计时器会超时。当计时器超时时,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。
1) sleep的简单而不完整的实现
#include <signal.h>
#include <unistd.h>
static void sig_alrm( int signo )
{
}
unsigned int sleep1( unsigned int nsecs )
{
if ( signal( SIGALRM, sig_alrm ) == SIG_ERR )
return ( nsecs );
alarm( nsecs );
pause();
return ( alarm( 0 ) );//turn off timer,return unslept time
}
~
此函数有以下三个问题:
1) 如果在调用sleep1之前,调用者已设置了闹钟,则它会被sleep1函数中的第一次alarm调用擦除。
2) 该程序中修改了对SIGALRM的配置。如果编写了一个函数供其他函数调用,则在该函数被调用时先要保存原配置,在该函数返回前再恢复原配置。
3) 在第一次调用alarm和调用pause之间有一个竞争条件。在繁忙的系统种可能在pause调用之前超时了,则调用者永远被挂起。
改进版:
#include <signal.h>
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
static jmp_buf env_alrm;
static void sig_alrm( int signo )
{
printf("begin\n");
longjmp( env_alrm, 1 );
printf("end\n");
}
unsigned int sleep2( unsigned int );
static void sig_int( int );
int main( void )
{
unsigned int unslept;
if ( signal( SIGINT, sig_int ) == SIG_ERR )
printf("signal error\n");
unslept = sleep2( 5 );
printf("sleep2 returned:%u\n", unslept );
exit( 0 );
}
unsigned int sleep2( unsigned int nsecs )
{
if ( signal( SIGALRM, sig_alrm ) == SIG_ERR )
return ( nsecs );
if ( setjmp( env_alrm ) == 0 ){
alarm( nsecs );
printf("pause execute!\n" );
pause();
}
return ( alarm( 0 ) );
}
static void sig_int( int signo )
{
int i, j;
volatile int k;
printf("\nsig_int starting\n");
for ( i = 0; i < 300000; i++ )
for ( j = 0; j < 40000; j++ )
k += i * j;
printf("sig_int finished\n");
}
注意:sleep2中,就算alarm和pause之间存在竞争,并且超时,我们也可以通过longjmp来跳出来。程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out
pause execute!
begin
sleep2 returned:0
alarm可用于对可能阻塞的操作设置时间上限值:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define MAXLINE 4096
static void sig_alrm( int );
int main( void )
{
int n;
char line[ MAXLINE ];
if ( signal( SIGALRM, sig_alrm ) == SIG_ERR )
printf("signal error\n");
alarm( 10 );
if ( ( n = read( STDIN_FILENO, line, MAXLINE ) ) < 0 )
printf( "read error\n" );
alarm( 0 );
write( STDOUT_FILENO, line, n );
exit( 0 );
}
static void sig_alrm( int signo )
{
printf("one signal\n");
}
如果在alarm和read之间存在竞争条件,并且阻塞了,则read可能永远阻塞。
改进版:
#include <signal.h>
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#define MAXLINE 4096
static void sig_alrm( int );
static jmp_buf env_alrm;
int main( void )
{
int n;
char line[ MAXLINE ];
if ( signal(SIGALRM, sig_alrm) == SIG_ERR )
printf("signal error\n");
if ( setjmp( env_alrm) != 0)
printf("read timeout");
alarm(10);
if ( ( n = read( STDIN_FILENO, line, MAXLINE)) < 0)
printf("read error\n");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
static void sig_alrm( int signo )
{
longjmp( env_alrm, 1 );
}
8. 信号集
五个处理信号集的函数:
#include <signal.h>
int sigemptyset( sigset_t *set ); //清除set中所有信号
int sigfillset( sigset_t *set ); //初始化set指向的信号集
int sigaddset( sigset_t *set, int signo ); //增加信号
int sigdelset( sigset_t *set, int signo ); //删除信号
int sigismember( const sigset_t *set, int signo ); //判断是否存在signo信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static void sig_quit( int );
int main( void )
{
sigset_t newmask, oldmask, pendmask;
if ( signal( SIGQUIT, sig_quit ) == SIG_ERR )
printf( "can't catch SIGQUIT" );
sigemptyset( &newmask );
sigaddset( &newmask, SIGQUIT ); //将SIGQUIT信号增加到新的信号集中
//保留旧信号集
if ( sigprocmask( SIG_BLOCK, &newmask, &oldmask ) < 0 )
printf( "SIG_BLOCK error" );
sleep( 5 );
//返回新的信号集
if ( sigpending( &pendmask ) < 0 )
printf( "sigpending error" );
//判断新的信号集中是否有SIGQUIT信号
if ( sigismember( &pendmask, SIGQUIT ) )
printf("\nSIGQUIT pending\n" );
//重新使用旧的信号集
if ( sigprocmask( SIG_SETMASK, &oldmask, NULL ) < 0 )
printf("SIG_SETMASK error");
printf("SIGQUIT unblocked\n");
sleep( 5 );
exit( 0 );
}
static void sig_quit( int signo )
{
printf("caught SIGQUIT!\n");
if ( signal( SIGQUIT, SIG_DFL ) == SIG_ERR )
printf("can't reset SIGQUIT");
}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out
^\^\
SIGQUIT pending
caught SIGQUIT!
SIGQUIT unblocked
9. sigsetjmp和siglongjmp函数
可恢复被屏蔽的信号
#include <stdio.h>
#include <setjmp.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
static void sig_usr1( int ), sig_alrm( int );
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
int main( void )
{
if ( signal( SIGUSR1, sig_usr1 ) == SIG_ERR )
printf("signal SIGUSR1 error\n");
if ( signal( SIGALRM, sig_alrm ) == SIG_ERR )
printf("signal SIGALRM error\n");
printf("starting main:\n");
if ( sigsetjmp( jmpbuf, 1)){
printf("ending main:\n");
exit( 0 );
}
canjump = 1;
for ( ; ; )
pause();
}
static void sig_usr1( int signo )
{
time_t starttime;
if ( 0 == canjump )
return;
printf("starting sig_usr1:\n");
alarm( 3 );
starttime = time( NULL );
for ( ; ; )
if ( time( NULL ) > starttime + 5 )
break;
printf("finishing sig_usr1\n");
canjump = 0;
siglongjmp( jmpbuf, 1 );
}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out &
[2] 3890
leichaojian@ThinkPad-T430i:~$ starting main:
kill -USR1 3890
starting sig_usr1:
leichaojian@ThinkPad-T430i:~$ in sig_alrm:
finishing sig_usr1
ending main:
[2]- Done ./a.out
10. sigsuspend函数
#include <signal.h>
int sigsuspend( const sigset_t *sigmask );
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信 号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
1) 保护临界区不被信号中断
以下程序保证SIGINT信号被悬挂起来。
#include <signal.h>
#include <stdio.h>
#include <errno.h>
void pr_mask( const char *str )
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if ( sigprocmask( 0, NULL, &sigset ) < 0 )
printf("sigprocmask error\n");
printf("%s", str);
if ( sigismember( &sigset, SIGINT)) printf("SIGINT ");
if (sigismember( &sigset, SIGQUIT)) printf("SIGQUIT ");
if (sigismember( &sigset, SIGUSR1)) printf("SIGUSR1" );
if (sigismember(&sigset, SIGALRM)) printf("SIGALRM");
printf("\n");
errno = errno_save;
}
static void sig_int( int );
int main( void )
{
sigset_t newmask, oldmask, waitmask;
pr_mask( "program start:" );
if ( signal( SIGINT, sig_int ) == SIG_ERR )
printf("signal(SIGINT) error\n");
sigemptyset( &waitmask );
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT );
if ( sigprocmask( SIG_BLOCK, &newmask, &oldmask) < 0)
printf("SIG_BLOCK error");
pr_mask("in critical region:");
if ( sigsuspend( &waitmask) != -1 )
printf("sigsuspend error\n");
pr_mask("after return from sigsuspend\n");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error\n");
pr_mask("program exit:");
exit(0);
}
static void sig_int( int signo )
{
pr_mask("\nin sig_int:");
}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out
program start:
in critical region:SIGINT
^C
in sig_int:SIGINT SIGUSR1
after return from sigsuspend
SIGINT
program exit:
2) 用sigsuspend等待一个全局变量被设置
#include <signal.h>
#include <stdio.h>
volatile sig_atomic_t quitflag;
static void sig_int( int signo )
{
if ( signo == SIGINT )
printf("\ninterrupt\n");
else if ( signo == SIGQUIT )
quitflag = 1;
}
int main( void )
{
sigset_t newmask, oldmask, zeromask;
if ( signal( SIGINT, sig_int ) == SIG_ERR )
printf("signal (SIGINT) error");
if ( signal( SIGQUIT, sig_int ) == SIG_ERR )
printf("signal (SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
printf("SIG_BLOCK error\n");
printf("block begin:\n");
while ( quitflag == 0 )
sigsuspend(&zeromask);
printf("block end\n");
quitflag = 0;
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error\n");
exit( 0 );
}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out
block begin:
^C
interrupt
^C
interrupt
^\block end