1.竞态条件(时序竞态)
pause函数
调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃CPU)直到有信号递达将其唤醒
int pause(void)
PS
如果信号的默认处理动作是终止进程,则进程终止,pause函数没有机会返回
如果信号的默认处理动态是忽略,进程进行处于挂起状态,pause函数不返回
如果信号的处理动作是捕捉,则pause返回-1并设置errno为EINTR,表示“被信号中断”。
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.调用了malloc或free
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_SIGINFO。siginfo_t是一个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。
4.中断系统调用
系统调用可分为两类:慢速系统调用和其他系统调用。
1.慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期):也可以设定系统调用是否重启。如read, write, pause, wait…
2.其他系统调用:getpid, getppid, fork…
结合pause,回顾慢速系统调用:
慢速系统调用被中断的相关行为,实际上就是pause的行为:如read
1.想中断pause,信号不能被屏蔽
2.信号的处理方式必须是捕捉(默认、忽略都不可以)
3.中断后返回-1,设置errno为EINTR(“被信号中断”)
可以修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRURT不重启, SA_RESTART重启
Comments