Linux 系统调用 wait()、signal()、kill()

wait()waitpid()

系统调用wait()用来等待任意子进程结束,参数为int*,用来存放子进程的退出
状态,可以为NULL。还有几个配套的宏来对子进程的退出状态进行判断,如
WEXITSTATUS等。用法如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>

int main(int argc, char *argv[])
{
int status;
int pid;

pid = fork();

if (pid == -1) {
perror("clone error");
exit(1);
}

if (pid) { /* parent process */
printf("In parent process, waiting for child ...\n");
wait(&status);
printf("child exit code: %d\n", WEXITSTATUS(status));
} else { /* child process */
printf("In child process, pid is %d\n", getpid());
exit(42);
}
return 0;
}

如果父进程在创建完子进程后,没有对子进程的退出状态进行回收,而去处理其他工
作,就会使子进程变成僵尸进程。即,子进程退出,而父进程没有退出且没有回收子
进程的退出状态,子进程会成为僵尸进程。比如下面的例子:

wait3.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>

int main(int argc, char *argv[])
{
if (fork()) {
while (1) {
sleep(3); /* 父进程不退出 */
}
} else {
exit(0); /* 子进程中直接退出 */
}
return 0;
}

运行如下:

1
2
3
4
5
6
7
8
9
10
11
12
$ clang wait3.c -o wait3
$ ./wait3 &
$ ps aux
...
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
qi 15049 0.0 0.0 50972 6508 pts/12 Ss 17:08 0:00 -zsh
root 15323 0.0 0.0 0 0 ? S 17:09 0:00 [kworker/5:1]
qi 15370 0.0 0.0 4212 712 pts/12 T 17:10 0:00 ./wait3
qi 15371 0.0 0.0 0 0 pts/12 Z 17:10 0:00 [wait3] <defunct>
root 15442 0.0 0.0 0 0 ? S 17:12 0:00 [kworker/u16:0]
...

可以看到 PID 为15371的进程状态为Z,即为僵尸进程。要避免这种情况发生,可
以使父进程将SIECHLD信号的处理函数设置为忽略(SIG_IGN)即可。

signal()kill()

这两个系统调用分别用来设置信号的处理函数和发送信号。signal()的原型如下:

1
2
3
4
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signal()会接收要一个处理函数和对应的要处理的信号,返回值为调用signal()
前的该信号的处理函数指针。注册过处理函数后,进程再收到这个信号,系统就会回
调这个处理函数,并将收到的信号传递给这个处理函数。这样就可以将多个信号处理
函数注册为同一个函数,而在处理函数内部加以区分。

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig)
{
switch (sig) {
case SIGINT:
printf("SIGINT receved.\n");
exit(0);
break;
case SIGQUIT:
printf("SIGQUIT receved.\n");
break;
default:
printf("Unknown signal\n");
break;
}
}

int main(int argc, char *argv[])
{
int status;
int pid;

signal(SIGINT, handler);
signal(SIGQUIT, handler);

printf("Ctr-C to send SIGINT, Ctrl-\\ to send SIGQUIT.\n");
while (1) {
printf("%s is running\n", argv[0]);
pause();
}
return 0;
}

运行如下:

1
2
3
4
5
6
7
8
9
$ clang signal.c -o signal
$ ./signal
Ctr-C to send SIGINT, Ctrl-\ to send SIGQUIT.
./signal is running
^\SIGQUIT receved.
./signal is running
^\SIGQUIT receved.
./signal is running
^CSIGINT receved.

另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handler(int dummy)
{
printf("SIGHUP receved.\n");
exit(0);
}

int main(int argc, char *argv[])
{
int status;
int pid;

if (pid = fork()) {
printf("Sending signal SIGHUP...\n");
sleep(1);
kill(pid, SIGHUP); // 向子进程(pid)发送 SIGHUP 信号
} else {
signal(SIGHUP, handler); // 设置 SIGHUP 信号的处理函数为 handler
while (1) {
sleep(3);
printf("Child process is still alive\n");
}
}
return 0;
}

尝试用这种方式处理会产生僵尸进程的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>

void handler(int dummy)
{
int status;
pid_t cpid;
cpid = wait(&status); /* 在 handler 里回收子进程 */
if (!WEXITSTATUS(status)) {
printf("Child process %d exited, status: %d\n", cpid, WEXITSTATUS(status));
}
}

int main(int argc, char *argv[])
{
signal(SIGCHLD, handler); /* 设置信号处理函数 */
if (fork()) {
while (1) {
sleep(3);
}
} else {
while (1) {
sleep(3);
}
}
return 0;
}

在 shell 里尝试以下操作:

1
2
$ clang wait4.c -o wait4
$ ./wait4

此时再打开另一个 shell,输入以下内容:

1
2
3
4
5
6
7
8
9
10
$ ps xo pid,ppid,state,session,command
PID PPID S SESS COMMAND
/* ... */
21840 3198 S 21840 /usr/bin/zsh
22467 18896 S 18896 ./wait4
22468 22467 S 18896 ./wait4
22484 21719 R 21719 ps xo pid,ppid,state,session,command
26823 1 S 26822 /usr/bin/emacs --resume-layouts
/* ... */
$ kill -9 22468

此时原来的 shell 会产生如下输出:

1
2
3
$ clang wait4.c -o wait4
$ ./wait4
Child process 22468 exited, status: 0

可以看到设置了信号处理函数被调用了,使用ps命令也不会发现僵尸子进程。父进
程用Ctrl-c退出即可。

0%