SIGCHLD 信号
1. SIGCHLD 的产生条件
- 子进程终止时
- 子进程接收到 SIGSTOP 信号停止时
- 子进程处在停止态,接受到 SIGCONT 后唤醒
2. 借助 SIGCHLD 信号回收子进程
子进程结束运行,其父进程会收到 SIGCHLD 信号。该信号的默认动作时忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <assert.h>
void sigchld_handler()
{
int status;
pid_t pid;
//这里的while不能改成if,若改成if,则很大可能会回收不完全,因外当有多个子进程同时结束,都向父进程发送 SIGCHLD 信号,这时候 SIGCHLD 信号只会被记录一次,那么也只会回收一个子进程
while((pid = waitpid(0, &status, WNOHANG)) > 0)
{
if(WIFEXITED(status))
printf("----------child %d exit %d\n", pid, WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
}
}
int main()
{
pid_t pid;
struct sigaction act, oldact;
int i;
for(i = 0; i < 10; i++)
{
pid = fork();
assert(pid >= 0);
if(pid == 0)
break;
}
if(pid == 0)
{
int n = 1;
while(n--)
{
printf("I am child %d\n", i+1);
sleep(1);
}
return i+1;
}
else if(pid > 0)
{
//设置 SIGCHLD 阻塞,万一父进程还没注册完 SIGCHLD 的捕捉函数,已经有子进程退出了,那么父进程就不会接收到该子进程发送的信号,那么就不能正常回收
sigset_t mask, oldmask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &oldmask);
//注册 SIGCHLD 信号
act.sa_handler = sigchld_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, &oldact);
//解除对 SIGCHLD 的阻塞
sigprocmask(SIG_SETMASK, &oldmask, NULL);
while(1)
{
printf("I am parent\n");
sleep(1);
}
}
return 0;
}
【运行结果】10个子进程都被正常回收
信号传参
1. 发送信号传参
sigqueue 函数对应 kill 函数,但可在向指定进程发送信号的同时携带参数
int sigqueue(pid_t pid, int sig, const union sigval value);
【返回值】
成功:0
失败:-1,设置errno
union sigval{
int sival_int;
void *sival_ptr;
};
向指定进程发送指定信号的同时,携带数据。但,如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。
2. 捕捉函数传参
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
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 是个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
中断系统调用
系统调用可分为两类:慢速系统调用和其他系统调用
- 慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期);也可以设定系统调用是否重启,如:read、write、pause、wait……
- 其它系统调用:getpid、getppid、fork……
结合 pause,回顾慢速系统调用:
慢速系统调用被中断的相关行为,实际上就是 pause 的行为:如:read
- 想中断 pause,信号不能被屏蔽
- 信号的处理方式必须是捕捉(默认、忽略都不可以)
- 中断后返回 -1,设置 errno 为EINTR(表示被信号中断)
可修改 sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRUPT 不重启。SA_RESTART 重启。
【扩展了解】
sa_flags 还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行信号捕捉函数期间,不希望自动阻塞该信号,可将 sa_flags 设置为 SA_NODEFER(可重入),除非 sa_mask 中包含该信号。