Ⅰ. 信号的基本概念
- 用户输入命令,在Shell下启动一个前台进程。
- 用户按下
Ctrl-C
,这个键盘输入产生一个硬件中断。 - 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。
- 终端驱动程序将
Ctrl-C
解释成一个SIGINT
信号,记在该进程的PCB
中(也可以说发送了一个SIGINT
信号给该进程)。 - 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理
PCB
中记录的信号,发现有一个SIGINT
信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。
kill -l
命令可以察看系统定义的信号列表。
这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)
中都有详细说明。Term
表示终止当前进程, Core
表示终止当前进程并且Core Dump
,lgn
表示忽略该信号, Stop
表示停止当前进程, Cont
表示继续执行先前停止的进程
产生信号的条件主要有:
- 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如
Ctrl-C
产生SIGINT
信号,Ctrl-Z
产生SIGTSTP
信号。 - 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为
SIGSEGV
信号发送给进程。 - 一个进程调用
kill(2)
函数可以发送信号给另一个进程。 - 可以用
kill(1)
命令发送信号给某个进程,kill(1)
命令也是调用kill(2)
函数实现的,如果不明确指定信号则发送SIGTERM
信号,该信号的默认处理动作是终止进程。当内核检测到某种软件条件发生时也可以通过信号通知进程,比如闹钟超时产生SIGALRM
信号,或向读端已关闭的管道写数据时产生SIGPIPE
信号
Ⅱ. 如何产生信号
1. Core Dump
当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。可以通过ulimit -c
命令对core文件的大小进行设置。
ulimit -c 1024
/**
* @brief floating point exception (core dumped)
* SIGFPE
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
int a = 3 / 0;
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief free(): double free detected in tcache 2
* abort (core dumped)
* SIGABRT
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
void *p = malloc(16);
free(p);
free(p);
return 0;
}
2.调用系统函数向进程发信号
#include <stdio.h>
#include <unistd.h>
/**
* @brief 死循环,可以使用系统函数向该进程发信号,如
* kill -3 pid SIGQUIT
* kill -9 pid SIGKILL
* kill -11 pid SIGSEGV
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
while (1) {
sleep(1);
}
return 0;
}
- 使用命令
kill -3 pid
(SIGQUIT
)杀死上面的进程会生成core文件:quit (core dumped)
。 - 使用命令
kill -11 pid
(SIGSEGV
)杀死上面的进程会生成core文件:segmentation fault (core dumped)
。
使用gdb a.out core
+ bt
命令可以查看进程退出之前的一些信息
kill
命令是调用kill
函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise
函数可以给当前进程发送指定的信号(自己给自己发信号)abort
函数使当前进程接收到SIGABRT
信号而异常终止。
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int kill(pid_t pid, int sig);
int raise(int sig);
void abort(void);
kill
:发信号raise
:向自己发信号abort
:向自己发SIGABRT
信号
(1)kill
int kill(pid_t pid, int sig);
pid
:如果为正数
,将信号sig
发送给对应pid
的进程;
如果为0
,则将信号sig
发送给进程组中的所有进程;
如果为-1
,则将信号sig
广播发送给系统内所有的进程(需要有权限)
如果小于-1
,则将信号sig
发送到ID
为-pid
的进程组中每个进程。sig
:将要发送的信号。如果sig
为0
,则不发送信号,但仍会执行存在和权限检查,因此可用于检查允许调用者发出信号的进程ID或进程组ID是否存在。返回值
:成功(至少有一个信号被发送出去)则返回0,失败则返回-1并设置errno
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 父进程在3s之后向子进程发送SIGSEGV信号
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
pid_t pid = fork();
if (pid < 0) {
perror("fork\n");
exit(-1);
}
if (pid) {
sleep(3);
if (kill(pid, SIGSEGV) < 0) {
perror("kill\n");
exit(-2);
}
int stat;
wait(&stat);
if (WIFSIGNALED(stat)) {
printf("child process was terminated by signal %d\n", WTERMSIG(stat));
} else {
printf("child exit with other reason\n");
}
} else {
while (1) {
printf("child process sleep 1s.\n");
sleep(1);
}
}
return 0;
}
(2)raise
int raise(int sig);
发送信号给当前调用的进程或线程,它是对kill
函数的进一步封装,在单线程程序下相当于kill(getpid(), sig)
,在多线程程序下相当于pthread_kill(pthread_self(), sig)
返回值
:成功返回0,失败返回非0
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 等待2s,之后向自己发送SIGSEGV信号
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
sleep(2);
raise(SIGSEGV);
return 0;
}
(3)abort
void abort(void);
abort
首先会将SIGABRT
信号解除阻塞(unblock),然后对调用它的进程发送该信号。这将导致进程异常终止,除非捕获到SIGABRT
信号并且信号处理程序不返回。如果abort
函数导致进程终止,那么所有打开的流浆被close
和flush
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 等待2s,之后向自己发送SIGABRT信号
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
sleep(2);
abort();
return 0;
}
3.由软件条件产生信号
(1)SIGPIPE
关闭管道的所有读端之后,如果进程继续往该管道写数据,它将会收到终止信号
/**
* @brief 关闭管道的所有读端之后,如果进程继续往该管道写数据,它将会收到终止信号
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
int pipefd[2];
if (pipe(pipefd) < 0) {
perror("pipe\n");
exit(-1);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork\n");
exit(-2);
}
if (pid) {
//父进程,关闭管道的读端和写端
close(pipefd[0]);
close(pipefd[1]);
int stat;
wait(&stat);
if (WIFSIGNALED(stat)) {
printf("child process was terminated by signal %d\n", WTERMSIG(stat));
} else {
printf("child process exit with other reason\n");
}
} else {
//子进程
sleep(3);
close(pipefd[0]);
//当父进程的读端被关闭后,子进程继续往管道写数据,就会收到终止的信号
write(pipefd[1], "hello world\n", 12);
}
return 0;
}
(2)alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm
函数可以设定一个闹钟,也就是告诉内核在seconds
秒之后给当前 进程发SIGALRM
信号。
如果seconds
为0
,那么所有代办的闹钟都会被取消。
在任何情况下,任何先前设置的闹钟都会被取消。
- 返回值:闹钟被取消后,将返回剩下的时间。如果之前没有设置过闹钟,那么返回0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief 先设置一个5s的闹钟,然后经过2s后再设一个5秒的闹钟
* 闹钟生效后将向调用它的进程发SIGALRM信号
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
alarm(5);
sleep(3);
unsigned int left = alarm(5); // left==2
printf("left=%d\n", left);
while (1) {
printf("sleep 1s\n");
sleep(1);
}
return 0;
}
Ⅲ. 阻塞信号
实际执行信号的处理动作称为信号递达(Delivery) ,信号从产生到递达之间的状态,称为信号未决(Pending) 。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
每个信号都有两个标志位分别表示阻塞(block
)和未决(pending
),还有一个函数指针(handler
)表示处理信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才该标志。
如果再进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
Linux是这样实现的:常规信号在递达之前产生多从只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)
1.信号集(set)操作函数
sigset_t
类型对于每种信号用一个bit
表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit
则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t
变量
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
这些函数允许操作 POSIX 信号集。
sigemptyset
函数将set
信号集初始化并清空sigfillset
函数将set
信号集初始化并用所有信号填满它sigaddset
函数将signum
代表的信号添加到set
信号集中sigdelset
函数将signum
代表的信号从set
信号集中删除sigismember
函数用来测试signum
代表的信号是否在set
信号集中- 返回值:
sigemptyset
、sigfillset
、sigaddset
、sigdelset
成功则返回0,失败则返回-1并设置errnosigismember
如果返回1则说明ignum
代表的信号在set
信号集中,如果返回0则代表不在,失败则返回-1并设置errno
2.信号屏蔽字(signal mask)读写函数
调用函数sigprocmask
可以读取或更改进程的信号屏蔽字
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask
用于获取和/或更改调用线程的信号屏蔽字(signal mask
),信号屏蔽字是一组当前被阻塞着的未决的信号集。sigprocmask
的行为取决于参数how
how
、set
:参数how
决定了sigprocmask
的行为,有以下几种取值:SIG_BLOCK
:新的阻塞信号集是目前信号集与参数set
的并集合,即在原先的基础上,增加参数set
信号集到信号屏蔽字中(mask = mask | set)
SIG_UNBLOCK
:将参数set
信号集从原先的信号屏蔽字中移除(mask = mask & (~set))
SIG_SETMASK
:信号屏蔽字由参数set
来设置(mask = set)
oldset
:如果参数oldset
不为NULL
,则可以用来存储原先的信号屏蔽字(备份,可以用来做还原操作);如果参数set
为NULL
,虽然经过sigprocmask
之后不会改变原先的信号屏蔽字,但是仍会将信号屏蔽字返回给oldset
参数- 返回值:成功返回0,失败返回-1并设置errno
如果调用sigprocmask
解除了对当前若干个未决信号的阻塞,则在sigprocmask
返回前,至少将其一个信号递达。(先递达信号,再返回)
不能阻塞SIGKILL
、SIGSTOP
信号。
一个进程中的各个线程都有它们自己的signal mask
。
除非SIGBUS
、SIGFPE
、SIGILL
、SIGSEGV
信号是由kill(2)
、sigqueue(3)
、raise(3)
发出的,否则如果当它们被阻塞后又通过其它途径产生了,那么结果是不可预料的。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[]) {
sigset_t set, oldset;
// 初始化并清空set
if (sigemptyset(&set) < 0) {
perror("sigemptyset\n");
exit(-1);
}
if (sigaddset(&set, SIGINT) < 0) { //SIGINT:Ctrl+C
perror("sigaddset\n");
exit(-2);
}
//修改signal mask
if (sigprocmask(SIG_BLOCK, &set, &oldset) < 0) {
perror("change signal mask by function sigprocmask\n");
exit(-3);
}
int n = 10;
while (n > 0) {
sleep(1);
printf("you can not terminate this process by Ctrl+C now\n");
n--;
}
//改回去,如果在之前有发送SIGINT信号,那么未决的SIGINT信号将会被立即处理,也就是进程会立即被终止
if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) {
perror("change signal mask back by function sigprocmask\n");
exit(-4);
}
return 0;
}
3.未决(pending)信号集
#include <signal.h>
int sigpending(sigset_t *set);
sigpending读取当前进程的未决(pending
)信号集,通过set参数传出。
返回值
:成功返回0,出错返回-1并设置errno
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief 打印信号集set
*
* @param set
*/
void print_sig(const sigset_t *set) {
for (int i = 1; i < 32; i++) {
sigismember(set, i) ? putchar('1') : putchar('0');
}
putchar(10);
}
/**
* @brief 先将SIGINT和SIGQUIT信号加入到signal mask中,之后打印pending set,
* 如果在10s内发送SIGINT或者SIGQUIT信号可以看到输出结果
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
sigset_t set, oldset, pset;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigprocmask(SIG_BLOCK, &set, &oldset);
int n = 10;
while (n > 0) {
//10s内发送SIGINT或者SIGQUIT信号可以看到输出结果
sigpending(&pset);
print_sig(&pset);
sleep(1);
n--;
}
sigprocmask(SIG_SETMASK, &oldset, NULL);
return 0;
}
Ⅳ. 捕捉信号
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间(但是独立的)的,处理过程比较复杂
1. sigaction
sigaction
函数可以读取和修改与指定信号相关联的处理动作。
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum
:指定信号,除了信号SIGKILL
和SIGSTOP
- 如果
act
不为NULL
,那么在指定的signum
信号递达时将去执行act
,如果oldact
不为NULL
,那么原先的信号抵达执行动作将被备份在oldact
中(可用于恢复现场)
其中act
和oldact
指向sigaction
结构体:
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
:用于指向指定信号处理动作地址,可以设置为SIG_DEL
,代表直线默认的处理动作,可以为SIG_IGN
,代表忽略这个信号的处理动作,也可以指向自定义的信号处理函数,这个函数仅有一个唯一参数,那就是信号编号signum
sa_flags
:一般设置0sa_mask
:在指向信号处理函数时,可以使用该参数阻塞其它信号的递达
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief 修改SIGINT信号的处理动作handler为自定义的函数
*
* @param signum
*/
void undead(int signum) {
printf("get signum=%d, I'm alive.\n", signum);
}
int main(int argc, char const *argv[]) {
struct sigaction act, oldact;
act.sa_handler = undead;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT, &act, &oldact); //将SIGINT信号的处理动作改为自定义的undead函数
int n = 10;
while (n > 0) {
sleep(1);
n--;
}
sigaction(SIGINT, &oldact, NULL); //还原SIGINT信号的处理动作
return 0;
}
2.pause
pause
函数使调用进程挂起直到有信号递达。
#include <unistd.h>
int pause(void);
- 如果信号的处理动作是终止进程,则进程终止,
pause
函数没有机会返回; - 如果信号的处理动作是忽略,则进程继续处于挂起状态,
pause
不返回; - 如果信号的处理动作是捕捉,则调用了信号处理函数之后
pause
返回-1
,errno
设置为EINTR
,表示“被信号中断” ,所以pause
只有出错的返回值。
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 自定义的信号处理函数sighandler
*
* @param signum
*/
void sig_alarm(int signum) {
return;
}
/**
* @brief 实现sleep函数
*
* @param sec
* @return unsigned int
*/
unsigned int mysleep(unsigned int sec) {
struct sigaction act, oldact;
act.sa_handler = sig_alarm;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, &oldact);
alarm(sec);
pause();
int unslept = alarm(0); // 再用一次alarm函数,获取上一个alarm被终止时剩余时间
sigaction(SIGALRM, &oldact, NULL);
return unslept;
}
int main(int argc, char const *argv[]) {
int n = 5;
while (n > 0) {
printf("mysleep 1s\n");
mysleep(1);
n--;
}
return 0;
}
3.可重入函数
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
4.竞态条件与sigsuspend函数
当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。引入了信号处理函数使得一个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等) ,就有可能出现冲突:
sigsuspend
包含了pause
的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend
而不是pause
。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
sigsuspend
会临时性地使用mask
参数替换掉调用它的进程的信号屏蔽字(signal mask
),之后会挂起这个进程,直到有动作是调用信号处理(signal handler
)程序或者终止进程的信号递达为止。
如果该信号终止了这个进程,sigsuspend
将不会返回;如果信号被捕获,那么sigsuspend将在信号处理(signal handler
)程序执行完之后返回,最后信号屏蔽字(signal mask
)将会被复位为调用sigsuspend
之前的样子。
无法阻塞信号SIGKILL
和SIGSTOP
。
和pause
一样,sigsuspend
没有成功情况的返回值,只有执行了一个信号处理函数之后 sigsuspend
才返回,返回值为-1
,errno
置为EINTR
。
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 自定义的信号处理函数sighandler
*
* @param signum
*/
void sig_alarm(int signum) {
return;
}
/**
* @brief 实现sleep函数
*
* @param sec
* @return unsigned int
*/
unsigned int mysleep(unsigned int sec) {
struct sigaction act, oldact;
sigset_t new_mask, old_mask, su_mask;
//阻塞闹钟信号
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGALRM);
//将SIGALRM加入signal mask
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
//替换信号SIGALRM的信号处理函数
act.sa_handler = sig_alarm;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, &oldact);
//开启闹钟
alarm(sec);
//读取原先的的signal mask情况
su_mask = old_mask;
//将SIGALRM信号从su_mask集合中移除
sigdelset(&su_mask, SIGALRM);
//临时设置信号屏蔽字为su_mask, 然后挂起程序等待信号(含SIGALRM)的到来
sigsuspend(&su_mask);
//再用一次alarm函数,获取上一次alarm被终止时的剩余时间(可能是设置得时间到了或者是被其他信号终止了)
int unslept = alarm(0);
//先还原signal handler再还原signal mask
//防止先还原signal mask的话有信号递达,处理了我们自定义的动作
sigaction(SIGALRM, &oldact, NULL);
sigprocmask(SIG_SETMASK, &old_mask, NULL);
return unslept;
}
int main(int argc, char const *argv[]) {
int n = 5;
while (n > 0) {
printf("mysleep 1s\n");
mysleep(1);
n--;
}
return 0;
}
5.关于SIGCHLD信号
用wait
和 waitpid
函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLD
信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD
信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait
清理子进程即可。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void sig_child(int signo) {
int stat;
pid_t pid = wait(&stat);
if (WIFEXITED(stat)) {
printf("chid process(pid=%d) exit with code %d\n", pid, WEXITSTATUS(stat));
} else {
printf("666 code %d\n", WEXITSTATUS(stat));
}
return;
}
/**
* @brief 父进程fork出子进程,子进程调用exit终止,
* 父进程自定义SIGCHLD信号处理函数,在其中调用wait
* 获得子进程的退出状态并打印
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
pid_t pid = fork();
if (pid < 0) {
perror("fork\n");
exit(-1);
}
if (pid) {
//父进程
struct sigaction act, old_act;
act.sa_handler = sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, &old_act);
int n = 10;
while (n--) {
printf("work~\n");
sleep(1);
}
} else {
//子进程
sleep(3);
exit(2);
}
return 0;
}
事实上,由于UNIX
的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction
将SIGCHLD
的处理动作置为SIG_IGN
,这样fork
出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。此方法对于 Linux
可用,但不保证在其它UNIX
系统上都可用。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**
* @brief 父进程fork出子进程,子进程调用exit终止,
* 父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,
* 这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,
* 也不会通知父进程
*
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
pid_t pid = fork();
if (pid < 0) {
perror("fork\n");
exit(-1);
}
if (pid) {
//父进程
struct sigaction act, old_act;
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, &old_act);
int n = 20;
while (n--) {
printf("work~\n");
sleep(1);
}
} else {
//子进程
sleep(3);
exit(2);
}
return 0;
}
评论 (0)