1、信号概念
•
每个信号有一个名字,以
SIG
打头,在头文件
signal.h
中定义为正整数
•
能够产生信号的情形有:
•
1
、终端按键产生,如
Ctrl+C
产生的中断信号
•
2
、硬件例外产生,如除以
0
,或无效的内存访问等
•
3
、通过
kill()
函数给指定进程或进程组发送信号
•
4
、使用
kill
命令给指定进程或进程组发送信号
•
5
、软件条件产生,如
SIGPIPE
(管道)、
SIGALARM
(定时)
•
信号是异步事件的典型例子,可以告知内核,信号到达时,执行三种操作之一:
•
1
、忽略信号,但是有两个信号不能被忽略或捕获:
SIGKILL
、
SIGSTOP
•
2
、捕获信号,信号发生时,执行事先注册的一个函数
•
3
、系统默认,每个信号对应一个默认动作,大多数信号的默认动作时终止进程
•
典型信号:
SIGALRM
、
SIGCHLD
、
SIGHUP
、
SIGINT
、
SIGKILL
、
SIGQUIT
、
SIGTERM
、
SIGUSR1
、
SIGUSR2
2、signal函数
•
#include <
signal.h
>
•
void (*
signal
(
int
signo
, void (*
func
)(
int
)))(
int
);
•
func
参数的取值:
1
、
SIG_IGN 2
、
SIG_DFL 3
、用户自定义函数
•
如果定义一个类型别名,
typedef
void
Sigfunc
(
int
);
•
那么
signal
函数可以声明为:
Sigfunc
*signal(
int
,
Sigfunc
*);
•
一个例子,使用
kill
命令演示信号机制(
kill
命令的名字并不恰当)
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void sig_usr(int); /* one handler for both signals */
int main(void)
{
if( signal(SIGUSR1, sig_usr) == SIG_ERR )
{
perror("can't catch SIGUSR1");
exit(0);
}
if( signal(SIGUSR2, sig_usr) == SIG_ERR )
{
perror("can't catch SIGUSR2");
exit(0);
}
for( ; ; )
pause();
}
static void sig_usr(int signo) /* argument is signal number */
{
switch( signo )
{
case SIGUSR1:
printf("received SIGUSR1\n");
break;
case SIGUSR2:
printf("received SIGUSR2\n");
break;
default:
printf("received signal %d\n", signo);
}
}
•
在后台执行的进程,由
shell
自动忽略中断与退出信号
3、不可靠信号
•
早期
Unix
版本中,信号可能会丢失,进程并不知晓
•
另外,进程对信号的控制能力很弱,不能临时阻塞信号
•
进程每次处理信号时,系统随即自动将信号动作复置为默认值
•
不可靠的代码
•
...
•
signal(SIGINT,
sig_int
); /* establish handler */
•
...
•
sig_int
()
•
{
•
signal(SIGINT,
sig_int
);
/* reestablish handler for next time */
•
... /* process the signal ... */
•
}
4、可重入函数
•
我的理解:指使用相同的参数多次调用,其行为一致
•
记住信号随时可能发生,在信号处理函数中只能使用
“
可重入函数
”
•
进程捕捉到信号时,首先执行信号处理程序中的指令,从信号处理程序返回时,继续执行在捕捉到信号时进程正在执行的正常指令
•
不可重入函数包括:
•
1
、使用静态数据结构
•
2
、函数中调用了
malloc
或
free
•
3
、标准
I/O
库的一部分,标准
I/O
库大多使用了全局数据结构
•
注:在信号处理程序中使用
printf
其实也是不可靠的,可能产生非预期行为
•
编写信号处理函数的总体原则就是充分考虑对被中断函数的影响
/*
* 在AIX平台演示,程序异常
* 信号处理程序中使用了不可重入函数
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pwd.h>
static void my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler\n");
signal(SIGALRM, my_alarm);
if((rootptr = getpwnam("root")) == NULL )
{
printf("getpwnam(root) error\n");
exit(0);
}
alarm(1);
}
int main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for( ;; )
{
if( (ptr = getpwnam("billing")) == NULL )
{
printf("getpwnam error\n");
exit(0);
}
if( strcmp(ptr->pw_name, "billing") != 0 )
{
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
}
5、SIGCLD信号语义
•
早期版本的
Unix
系统(如
System v
),对于
SIGCLD
特殊处理:
•
1
、如果应用程序明确将
SIGCLD
信号设置为
SIG_IGN
,其子进程终止时不会变成僵尸,子进程终止时,其状态被丢弃
•
2
、如果将
SIGCLD
信号的处理动作设置为捕获,内核将立即检查是否有已经终止的子进程,如果有,将调用信号处理程序
•
看下面这个程序,在某些版本的
Unix
系统中不能正常运行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
static void sig_cld(int);
int main()
{
pid_t pid;
signal(SIGCLD, sig_cld);
if ((pid = fork()) < 0)
{
perror("fork error");
exit(0);
}
else if( pid == 0 ) /* child */
{
sleep(2);
_exit(0);
}
pause(); /* parent */
exit(0);
}
static void sig_cld(int signo) /* interrupts pause() */
{
pid_t pid;
int status;
printf("SIGCLD received\n");
signal(SIGCLD, sig_cld); /* reestablish handler */
if ((pid = wait(&status)) < 0) /* fetch child status */
{
perror("wait error");
exit(0);
}
printf("pid = %d\n", pid);
}
•
了解一下原因
6、kill、raise函数
•
#include <
signal.h
>
•
int
kill
(
pid_t
pid
,
int
signo
);
#
向指定进程
/
进程组发送信号
•
int
raise
(
int
signo
);
#
给自己发送一个信号
•
kill
函数的参数:
•
pid
> 0
信号发送给指定进程
•
pid
= 0
信号发送给同一进程组内的所有进程
•
pid
< 0
信号发送给特定进程组(
ID
等于
pid
绝对值)内的进程
•
pid
= -1
信号发送给当前进程有权限发送的所有进程
•
如果
signo
是
0
,并不实际发出信号,可以检测指定进程是否存在
7、alarm、pause函数
•
#include <
unistd.h
>
•
unsigned
int
alarm
(unsigned
int
seconds);
•
alarm
设定一个定时器,届时将触发
SIGALRM
信号
•
每个进程只有一个定时器,调用
alarm
时,如果之前的一个定时器尚未触发,那么其剩余时间将被返回
•
如果参数
seconds
为
0
,将取消定时器设置
•
int
pause(void); #
阻塞,直到捕获一个信号后返回
•
有三个问题:
•
1
、如果调用者之前已经设置了一个定时器,将被函数中的
alarm
取消
•
2
、函数中修改了
SIGALRM
信号的处理逻辑
•
3
、在第一个
alarm
和
pause
之间存在竞态条件
例子:sleep的简单实现
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_alrm(int signo)
{
/* nothing to do, just return to wake up the pause */
}
unsigned int sleep1(unsigned int nsecs)
{
if( signal(SIGALRM, sig_alrm) == SIG_ERR )
return(nsecs);
alarm(nsecs); /* start the timer */
pause(); /* next caught signal wakes us up */
return(alarm(0)); /* turn off timer, return unslept time */
}
int main(void)
{
sleep1(4);
}
8、信号集
•
顾名思义,就是信号的集合表示
•
#include <
signal.h
>
•
int
sigemptyset
(
sigset_t
*set);
•
int
sigfillset
(
sigset_t
*set);
•
int
sigaddset
(
sigset_t
*set,
int
signo
);
•
int
sigdelset
(
sigset_t
*set,
int
signo
);
•
int
sigismember
(
const
sigset_t
*set,
int
signo
);
•
初始化信号集时,必须调用
sigemptyset
、
sigfillset
之一
9、sigprocmask()函数
•
#include <
signal.h
>
•
int
sigprocmask
(
int
how,
const
sigset_t
*restrict set,
sigset_t
*restrict
oset
);
•
操作进程的信号屏蔽,被屏蔽的信号不能递交给进程,被阻塞
•
how
参数的取值:
SIG_BLOCK
、
SIG_UNBLOCK
、
SIG_SETMASK
•
注意:
•
同一个信号是不能累积的,或者说是不排队的
•
解除阻塞后,阻塞期间同一个信号的多次发生只能递交一个
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno; /* we can be called by signal handlers */
if( sigprocmask(0, NULL, &sigset) < 0 )
{
perror("sigprocmask error");
exit(0);
}
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 ");
/* remaining signals can go here */
printf("\n");
errno = errno_save;
}
int main(void)
{
sigset_t st;
pr_mask("main 1");
sigemptyset(&st);
sigaddset(&st, SIGINT);
/* 屏蔽 INT 信号 */
sigprocmask(SIG_BLOCK, &st, NULL);
pr_mask("main 2");
printf("sleep 10 ...\n");
sleep(10);
/* 解除 INT 信号屏蔽 */
sigprocmask(SIG_UNBLOCK, &st, NULL);
pr_mask("main 3");
}
10、sigpending函数
•
#include <
signal.h
>
•
int
sigpending
(
sigset_t
*set);
•
获取当前因阻塞而未递交给进程的信号集
11、sigaction函数
•
用以检查或(和)设置与特定信号相关的处理动作,作为
signal
函数的替代
•
int
sigaction
(
int
signo
,
const
struct
sigaction
*restrict act,
struct
sigaction
*restrict
oact
);
•
struct
sigaction
的成员:
•
1
、
sa_handler
,指定处理函数的地址
•
2
、
sa_mask
,在处理函数调用之前,被临时加入进程的信号屏蔽,处理返回后恢复
•
调用处理函数时,当前正在被处理的信号也会临时加入进程的信号屏蔽
•
3
、
sa_flags
,指定一些可选的处理选项
•
由
sigaction
函数设置的信号处理动作,除非显示改变,不会自动恢复为缺省动作。
•
在一些版本的
Unix
系统中,使用
sigaction
函数实现
signal
函数。
12、sigsetjmp、siglongjmp函数
•
与
setjmp
/
longjmp
函数相比,
sigsetjmp
/
siglongjmp
保存
/
恢复进程的信号屏蔽
•
在信号处理函数中应该调用这两个
sig
打头的函数
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno; /* we can be called by signal handlers */
if( sigprocmask(0, NULL, &sigset) < 0 )
{
perror("sigprocmask error");
exit(0);
}
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 ");
/* remaining signals can go here */
printf("\n");
errno = errno_save;
}
int main(void)
{
signal(SIGUSR1, sig_usr1);
signal(SIGALRM, sig_alrm);
pr_mask("starting main: "); /* Figure 10.14 */
if( sigsetjmp(jmpbuf, 1))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for( ;; )
pause();
}
static void sig_usr1(int signo)
{
time_t starttime;
if( canjump == 0 )
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
starttime = time(NULL);
for( ;; ) /* busy wait for 5 seconds */
if (time(NULL) > starttime + 5)
break;
pr_mask("finishing sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1); /* jump back to main, don't return */
}
static void sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
13、sigsuspend函数
•
可以屏蔽
/
解除屏蔽指定信号,这种技术可以用以保护关键代码段,如:
•
屏蔽信号
•
关键代码段
•
解除屏蔽信号
•
思考:如果我们想在解除屏蔽之后调用
pause
,等待之前被阻塞信号的发生,该如何做?
•
存在的问题:如果信号在屏蔽期间发生,那么
pause
将不能被中断。
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
/* critical region of code */
/* reset signal mask, which unblocks SIGINT */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
/* window is open */
pause(); /* wait for signal to occur */
/* continue processing */
•
为解决这个问题,需要在一个原子操作中恢复信号屏蔽,然后睡眠,为此引入:
•
int
sigsuspend
(
const
sigset_t
*
sigmask
);
•
1
、进程的信号屏蔽首先设置为参数
sigmask
,然后进程休眠直到捕获一个信号
•
2
、从信号处理动作返回时,
sigsuspend
返回,然后信号屏蔽恢复为调用之前的状态
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
static void sig_int(int);
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno; /* we can be called by signal handlers */
if( sigprocmask(0, NULL, &sigset) < 0 )
{
perror("sigprocmask error");
exit(0);
}
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 ");
/* remaining signals can go here */
printf("\n");
errno = errno_save;
}
int main(void)
{
sigset_t newmask, oldmask, waitmask;
pr_mask("program start: ");
signal(SIGINT, sig_int);
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/*
* Block SIGINT and save current signal mask.
*/
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
/*
* Critical region of code.
*/
pr_mask("in critical region: ");
/*
* Pause, allowing all signals except SIGUSR1.
*/
sigsuspend(&waitmask);
pr_mask("after return from sigsuspend: ");
/*
* Reset signal mask which unblocks SIGINT.
*/
sigprocmask(SIG_SETMASK, &oldmask, NULL);
/*
* And continue processing ...
*/
pr_mask("program exit: ");
exit(0);
}
static void sig_int(int signo)
{
pr_mask("\nin sig_int: ");
}
•
sigsuspend
的应用场景之一:用于进程同步
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;
static void sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
}
void TELL_WAIT(void)
{
signal(SIGUSR1, sig_usr);
signal(SIGUSR2, sig_usr);
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/*
* Block SIGUSR1 and SIGUSR2, and save current signal mask.
*/
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
}
void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}
void WAIT_PARENT(void)
{
while( sigflag == 0 )
sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0;
/*
* Reset signal mask to original value.
*/
sigprocmask(SIG_SETMASK, &oldmask, NULL);
}
void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
}
void WAIT_CHILD(void)
{
while( sigflag == 0 )
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0;
/*
* Reset signal mask to original value.
*/
sigprocmask(SIG_SETMASK, &oldmask, NULL);
}
static void charatatime(char *);
int main(void)
{
pid_t pid;
TELL_WAIT(); /* 初始化同步结构 */
setbuf(stdout, NULL); /* set unbuffered */
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 )
{
WAIT_PARENT(); /* 等待父进程完成 */
charatatime("output from child\n");
}
else
{
charatatime("output from parent\n");
TELL_CHILD(pid); /* 通知子进程已完成 */
}
exit(0);
}
static void charatatime(char *str)
{
while( *str ) putc(*(str++), stdout);
}
14、abort函数
•
#include <
stdlib.h
>
•
void
abort
(void);
•
给调用进程自己发送
SIGABRT
信号
15、system函数
•
system
函数的实现需要充分考虑信号的处理
•
这儿只描述一下
system
函数的返回值:
•
system
函数所执行的命令被信号中断时,其返回值是
128+
信号编号
16、sleep函数
•
看似简单,处处留心皆学问
•
#include <
unistd.h
>
•
unsigned
int
sleep(unsigned
int
seconds);
•
两种情况下返回:
•
1
、指定的时间流逝后,此时返回
0
•
2
、被信号中断,此时返回剩余的未睡眠时间
•
如果使用
SIGALRM
信号实现
sleep
,需要注意与函数之外的
alarm
的交互影响
17、任务控制信号
•
SIGCHLD
Child process has stopped or terminated.
•
SIGCONT
Continue process, if stopped.
•
SIGSTOP
Stop signal (can't be caught or ignored).
•
SIGTSTP
Interactive stop signal.
•
SIGTTIN
Read from controlling terminal by member of a background process group.
•
SIGTTOU
Write to controlling terminal by member of a background process group.
•
应用程序通常无需考虑,只有涉及终端管理的程序才需要考虑,如
vi