1.竞态条件(时序竞态)

pause函数

调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃CPU)直到有信号递达将其唤醒

int pause(void)      

PS

如果信号的默认处理动作是终止进程,则进程终止,pause函数没有机会返回

如果信号的默认处理动态是忽略,进程进行处于挂起状态,pause函数不返回

如果信号的处理动作是捕捉,则pause返回-1并设置errnoEINTR,表示“被信号中断”。

pause收到的信号不能被屏蔽,如果被屏蔽,pause就不能被唤醒

 

时序竞态引发的问题

1.因为在pause调用期间,只有信号捕捉才能让程序被唤醒而继续执行下去,如果在alarm(2)调用后,系统有很多优先级高的进程抢占CPU,那么就有可能发生在2秒后都还没有执行到调用pause函数语句从而在该程序接收到内核递送的信号进行信号捕捉处理,于是执行pause调用,就会引发死锁问题。

 

     上面的类似问题可以采用sigsuspend函数来解决

int sigsuspend(const sigset_t *mask); //挂起等待信号

sigsuspend函数调用期间,进程信号屏蔽字由其参数mask指定

解决以上死锁问题的方案是:可以将某个信号从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号。

 

可重入/不可重入函数

一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为“重入”。根据函数实现的方法可分为“可重入函数”和“不可重入函数”。

如果函数内部有使用到全局变量或者有使用new malloc free delete等,这时重入的话可能会引发很多意想不到的问题,如信号捕捉函数使用全局变量,将因为时序竞态的条件而引发不可意料的后果

所以:

1.定义可重入函数,函数内不能含有全局变量及static变量,不能使用malloc,free

       2.信号捕捉函数应设计为可重入函数

       3.信号处理程序可以调用的可重入函数可以参阅man 7 signal

       4.没有包含在上述列表中的函数大多是不可重入的,其原因为:

              a.使用了静态数据结构

              b.调用了mallocfree

              c.是标准I/O函数


 

2.SIGCHLD信号

2.1 SIGCHLD的产生条件

1.子进程终止时

2.子进程接收到SIGSTOP信号停止时

3.子进程处在停止态,接收到SIGCONT后唤醒时


DEMO捕捉SIGCHLD信号回收子进程

#include <iostream>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <sys/wait.h>

#include <sys/types.h>

 

using namespace std;

 

const int N = 5;

 

void wait_child(int signo){

 

       cout << "catch a SIGCHLD signal" << endl;

       int status;

       pid_t pid;

       while((pid = waitpid(0, &status, WNOHANG)) > 0){

              if(WIFEXITED(status)){

                     cout << "wait a child process, pid: " << pid << endl;

              }

       }

}

 

int main(){

 

       struct sigaction act;

       sigemptyset(&act.sa_mask);

       act.sa_flags = 0;

       act.sa_handler = wait_child;

 

       sigaction(SIGCHLD, &act, NULL);

 

       int i = 0;

       pid_t pid;

 

       for(i = 0; i < N; ++i){

              pid = fork();           

              if(pid == 0){

                     break;

              }else{

                     cout << "create a child process, pid is: " << pid << endl;

              }

       }    

 

       if(i == N){

              while(true);

       }    

 

       return 0;

}


3.信号传参

3.1 发送信号传参

sigqueue函数对应kill函数,但可在指定进程发送信号的同时携带参数

#include <signal.h>

 int sigqueue(pid_t pid, int sig, const union sigval value);

union sigval(

       int        sival_int;

       void      *sival_prt;

}

向指定进程发送指定信号的同时,携带数据。但,如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当期进程地址传递给另一进程没有实际意义

3.2 捕捉函数传参

struct sigaction {

               void     (*sa_handler)(int);

               void     (*sa_sigaction)(int, siginfo_t *, void *);

               sigset_t   sa_mask;

               int        sa_flags;

               void     (*sa_restorer)(void);

           };

当注册信号捕捉函数,希望获取更多信号相关信息,不应使用sa_handler而应该使用sa_sigaction。但此时的sa_flags必须指定为SA_SIGINFOsiginfo_t是一个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。


4.中断系统调用

系统调用可分为两类:慢速系统调用其他系统调用

1.慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期):也可以设定系统调用是否重启。如read, write, pause, wait…

2.其他系统调用getpid, getppid, fork…

结合pause,回顾慢速系统调用:

      慢速系统调用被中断的相关行为,实际上就是pause的行为:如read

1.想中断pause,信号不能被屏蔽

       2.信号的处理方式必须是捕捉(默认、忽略都不可以)

       3.中断后返回-1,设置errnoEINTR(“被信号中断”)

可以修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRURT不重启, SA_RESTART重启