分类: C/C++
2013-05-31 16:45:15
#include
int sigaction(int sigon,const struct sigaction &restrict act,
struct sigaction &restrict oact);
成功返回0,出错返回-1
此函数使用下列结构:
struct sigaction{
void ( *sa_handler)(int); //信号处理函数地址 sigset_t sa_mask; //一个调用信号捕捉函数之前要加到进程信号屏蔽字中的信号集
int sa_flags; //信号处理选项
void (*sa_sigaction)(int,siginfo_t *,void *);
}
对于sigaction 函数本身我们不做多介绍。我们重点是在 sa_flags 的不同值的情况下,sigaction函数的处理方式
SA_INTERRUPT:由此信号中断的系统调用不会自动重启动(针对sigaction的XSI默认处理方式不会自动重启)
SA_RESTART: 由此信号中断的系统调用会自动重启。
我们看下面这段程序:
4 void sig_int(int signo){
5 printf("\nint sig_int\n");
6 }
7
8 int main(void){
9 struct sigaction new_action;
10 new_action.sa_handler=sig_int;
11 if(sigemptyset(&new_action.sa_mask)==-1){
12 printf("set new action mask error\n");
13 exit(1);
14 }
15 new_action.sa_flags=0;
16
17 if(sigaction(SIGINT,&new_action,NULL)==-1){
18 printf("set SIGINT process error\n");
19 exit(1);
20 }
21 char c;
22 read(0,&c,1);
23 printf("read :%c\n",c);
24 exit(0);
25 }
第十五行中我们没有使用任何标志。设置捕捉 SIGINT 信号,然后读标准输入
输出如下:
./a.out
^C
int sig_int
read :?
我们在输入任何字符之前就 按下中断键(ctrl+c),进程捕捉到SIGINT信号,然后打印信息后就终止了。read的输出内容是c变量位置处本来
存在的数据并不是我们输入的。
从输出可以看出,read调用被中断后,没有再重启。这说明我的系统默认就是 对被中断的系统调用不会自动重启。
所以我们将 第十五行 改为 new_action.sa_flags=SA_INTERUPT;
可以预测输出情况和上面的是一样的
./a.out
^C
int sig_int
read :?
从输出我们看到 的确是一样的。
现在 我们将 第十五行改为 new_action.sa_flags=SA_RESTART; 再看看输出情况
./a.out
^C
int sig_int
^C
int sig_int
^C
int sig_int
^\Quit (core dumped)
从输出中我们看到 当多次按下 中断键后 进程捕捉到信号并打印消息,但是进程并未结束,而是继续等待输入。按下退出键时进程才退出
也就是说 使用了 SA_RESTART标志后 read调用被中断后 会自动重启 继续等待中断输入。
SA_NOCLDSTOP:
一般当进程终止或停止时都会产生SIGCHLD信号,但若对SIGCHLD信号设置了该标志,当子进程停止时不产生此信号。当子进程终止时才产生此信号。
我们看下面这段代码:
4 void sig_chld(int signo){
5 printf("get signal:%s\n",strsignal(signo));
6 }
7
8 int main(void){
9 pid_t pid;
10
11 struct sigaction new_action,old_action;
12 new_action.sa_handler=sig_chld;
13 if(sigemptyset(&new_action.sa_mask)==-1){
14 printf("empty new_action.sa_mask error\n");
15 exit(1);
16 }
17 new_action.sa_flags=0;
18 sigaction(SIGCHLD,&new_action,&old_action);
19
20 if((pid=fork())==-1){
21 perror("fork error");
22 exit(1);
23 }else if(pid==0){
24 if(kill(getpid(),SIGSTOP)==-1){
25 printf("send SIGSTOP to child error\n");
26 exit(1);
27
28 }
29 printf("child done\n");
30 }else{
31 sleep(1);
32 kill(pid,SIGCONT);
33 sleep(2);
34 printf("parent done\n");
35
36 }
37 exit(0);
38 }
我们捕捉 SIGCHLD 信号 但 第十七行中 我们没有使用任何标志。我们期望让子进程先暂停(所以父进程先睡眠一秒),这时父进程应该收到一个SIGCHLD然后父进程发送SIGCONT
信号给子进程让他继续运行,然后父进程休眠(为了在子进程之后终止),子进程运行并终止,此时父进程应该受到一个SIGCHLD信号中断睡眠,然后
结束。
我们看下输出情况:
./a.out
get signal:Child exited
child done
get signal:Child exited
parent done
正如 我们预测的。父进程先收到一次子进程暂停是而产生的SIGCHLD,然后当子进程结束时,父进程又接收到一次。
(注意这段程序是有问题的,它存在一个竞争条件。如果fork是实现是父进程先执行而系统非常繁忙,可能父进程睡眠一秒后子进程还未运行。那么SIGCONT信号将
丢失,然后当子进程停止时,父进程收到信号中断睡眠(第二次的睡眠)然后结束进程。那么 子进程在结束前一直就是出于停止状态。我们也不会看到子进程打印的"child done"。
,如果你的系统是父进程先运行,你可以去掉父进程中的 sleep(1)实验一下。输出中是不会出现子进程打印的"child done")
现在我们修改下 第十七行 中的标志选项 new_action.sa_flags=SA_NOCLDSTOP;
输出如下:
./a.out
child done
get signal:Child exited
parent Done
正如我们期望的,当 子进程暂停时。父进程并未受到 SIGCHLD信号。只在子进程结束时才收到一次。
SA_NOCLDWAIT:若信号是 SIGCHLD时,当使用此标志时,1 当调用进程的子进程终止时不创建僵尸进程。2若调用进程在后面调用wait。则调用进程阻塞,直到其所有子进程都终止
对于第一个影响(当调用进程的子进程终止时不创建僵尸进程) 我们先给一个测试例子:
4 int main(void){
5 pid_t pid;
6
7 struct sigaction action;
8 sigemptyset(&action.sa_mask);
9 action.sa_flags= 0;
10
11 if(sigaction(SIGCHLD,&action,NULL)==-1){
12 printf("sigaction error\n");
13 exit(1);
14 }
15
16 if((pid=fork())==-1){
17 printf("fork error\n");
18 exit(1);
19 }else if(pid==0){
20 exit(0);
21 }
22 else {
23 sleep(10);
24 }
25 exit(0);
26 }
第九行中 我们 没有使用任何标志。当子进程结束时,父进程并没有获取获取他的终止状态而是继续睡眠。 那么我们预测,子进程会成为僵尸进程
输出如下:
./a.out &
[2] 4369
ps 4369 4370
PID TTY STAT TIME COMMAND
4369 pts/2 S 0:00 ./a.out
4370 pts/2 Z 0:00 [a.out]
我们在后台运行程序后,查看 父子进程的状态。正如预测的一样,父进程处于睡眠状态而子进程成为僵尸进程
如果我们将第九行 改为: action.sa_flags= SA_NOCLDWAIT;
输出如下:
./a.out &
[2] 4410
ps 4410 4411
PID TTY STAT TIME COMMAND
4410 pts/2 S 0:00 ./a.out
从输出中我们看到,没有子进程的 状态消息。说明子进程已经正常终止了,并未产生僵尸进程
现在正对第二个影响(若调用进程在后面调用wait。则调用进程阻塞,直到其所有子进程都终止),我们在给出测试例子
4 int main(void){
5 pid_t pid;
6
7 struct sigaction action;
8 sigemptyset(&action.sa_mask);
9 action.sa_flags= 0;
10 if(sigaction(SIGCHLD,&action,NULL)==-1){
11 printf("sigaction error\n");
12 exit(1);
13 }
14
15 if((pid=fork())==-1){
16 printf("fork error\n");
17 exit(1);
18 }else if(pid==0){
19 sleep(2);
20 }
21 else {
22 pid=fork();
23 if(pid==0){
24 sleep(5);
25 }
26 else{
27 wait(NULL);
28 printf("father done\n");
29 }
30 }
31 exit(0);
32 }
同样第九行,我们没有使用任何标志。我们先创建第个子进程他只睡眠2秒。然后父进程创建第二个子进程。他休息5秒。之后父进程调用wait阻塞等待任意子进程结束
。应为 wait等待任意子进程结束时就会返回,那么我们预测。父进程阻塞2秒左右就会结束。
输出如下:
./a.out
father done
运行时,会发现只阻塞了两秒左右。
现在我们将 第九行改为: action.sa_flags= SA_NOCLDWAIT;
输出如下:
./a.out
father done
运行程序 我们明显能感觉到阻塞了 五秒左右才有输出。也就是说 父进程一直等待到第二个子进程结束时wait才返回。
SA_NODEFER:
SA_RESETHAND:
这两个标志对应早期的不可靠信号机制,除非明确要求使用早期的不可靠信号,否则不应该使用。这里也不多做介绍
SA_SIGINFO:
在开头我们看到 struct sigacton结构有又一个 void (*sa_sigaction)(int,siginfo_t *,void *); 字段
该字段是一个 替代的信号处理函数。
当我们没有使用 SA_SIGINFO 标志时,调用的是 sa_handler指定的信号处理函数。
当指定了该标志后,该标志对信号处理函数提供了附加的信息,一个指向siginfo结构的消息和一个指向进程上下文标识符
的指针这时我们就能调用sa_sigaction指定的信号处理函数
对于siginfo结构体 XSI规定至少包含下列字段;
struct siginfo{
int si_signo;//信号编号
int si_errno;//错误值
int si_code;//信号对应的代码值
pid_t si_pid;//发送信号的进程id
/*
后面还有一些这里就不列出了
*/
}
看下面的例子:
4 void sig_int(int signo,siginfo_t *info,void *context){
5 printf("\nget signal:%s\n",strsignal(signo));
6 printf("signal number is %d\n",info->si_signo);
7 printf("pid=%d\n",info->si_pid);
8 }
9 int main(void){
10
11 struct sigaction new_action;
12 sigemptyset(&new_action.sa_mask);
13 new_action.sa_sigaction=sig_int;
14 new_action.sa_flags=SA_SIGINFO;
15 if(sigaction(SIGINT,&new_action,NULL)==-1){
16 printf("set signal process mode\n");
17 exit(1);
18 }
19
20 kill(getpid(),SIGINT);
21 printf("Done\n");
22 exit(0);
23 }
我们使用了 SA_SIGINFO标志 然后使用了 sa_sigaction字段表示的处理函数。
设置信号处理函数后,进程向自己发送一个中断信号,然后结束。 在信号处理函数可以使用siginfo结构中提供的信息打印信号编号和发送信号进程id
运行输出如下:
./a.out &
[2] 5010
get signal:Interrupt
signal number is 2
pid=5010
Done
如果 不指定SA_SIGINFO标志我们仍可以使用sa_sigaction字段指定函数,但是应为此时不再提供附加信息,那么我们所有对siginfo的访问都是无效的将造成段错误
如果将 14 行 改为new_action.sa_flags=0;
输出如下:
./a.out &
[2] 5041
get signal:Interrupt
^C
[2]- Segmentation fault (core dumped) ./a.out
我们看到的确发生了段错误
SA_ONSTACK:
若用sigaltstatck声明了一替换栈,则将此信号递送给替换栈上的进程。对于这个选项,我还没有接触,这里就不做介绍了