sigaction函数sa_flags各标志影响的实例讲解

作者:ifndef 和c/c++相关  

#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 信号,然后读标准输入

输出如下:
   feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out
   ^C
   int sig_int
   read :?
   
   我们在输入任何字符之前就 按下中断键(ctrl+c),进程捕捉到SIGINT信号,然后打印信息后就终止了。read的输出内容是c变量位置处本来
   存在的数据并不是我们输入的。
   从输出可以看出,read调用被中断后,没有再重启。这说明我的系统默认就是 对被中断的系统调用不会自动重启。
   
所以我们将  第十五行 改为  new_action.sa_flags=SA_INTERUPT;
可以预测输出情况和上面的是一样的
 feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out
 ^C
 int sig_int
 read :?
从输出我们看到 的确是一样的。

现在 我们将 第十五行改为  new_action.sa_flags=SA_RESTART; 再看看输出情况
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./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信号中断睡眠,然后
结束。
我们看下输出情况:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./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;
输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./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 }
第九行中 我们 没有使用任何标志。当子进程结束时,父进程并没有获取获取他的终止状态而是继续睡眠。 那么我们预测,子进程会成为僵尸进程
输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out &
[2] 4369
feng@ubuntu:~/learn_linux_c_second/chapter_10$ 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;
输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out &
[2] 4410
feng@ubuntu:~/learn_linux_c_second/chapter_10$ 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秒左右就会结束。

输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out
father done
运行时,会发现只阻塞了两秒左右。

现在我们将 第九行改为:   action.sa_flags= SA_NOCLDWAIT;
输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./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

运行输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out &
[2] 5010

feng@ubuntu:~/learn_linux_c_second/chapter_10$ get signal:Interrupt
signal number is 2
pid=5010
Done

如果 不指定SA_SIGINFO标志我们仍可以使用sa_sigaction字段指定函数,但是应为此时不再提供附加信息,那么我们所有对siginfo的访问都是无效的将造成段错误
如果将 14 行 改为new_action.sa_flags=0;
输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_10$ ./a.out &
[2] 5041
feng@ubuntu:~/learn_linux_c_second/chapter_10$
get signal:Interrupt
^C
[2]-  Segmentation fault      (core dumped) ./a.out

我们看到的确发生了段错误

SA_ONSTACK:
  若用sigaltstatck声明了一替换栈,则将此信号递送给替换栈上的进程。对于这个选项,我还没有接触,这里就不做介绍了

相关资料:

sigaction函数sa_flags各标志影响的实例讲解来源网络,如有侵权请告知,即处理!

编程Tags: