1. 进程环境
#include <stdlib.h>
void exit(int status);
exit(0) 与 return 0 是等价的。
2. 进程标识和创建子进程
每个进程都有一个非负整型表示的唯一进程ID,但进程ID是可以被复用的。当一个进程终止后进程ID会进入候选池,并且一般新的进程不会使用最近终止进程的进程ID。
#include <unistd.h>
pid_t getpid(void); // 返回值: 调用进程的进程ID
pid_t getppid(void); // 返回值: 调用进程的父进程ID
fork() 函数可以创建一个新进程,子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。并且在修改时使用了写时复制(Copy-On-Write,COW)技术。
#include <unistd.h>
pid_t fork(void); // 返回值: 子进程返回0, 父进程返回子进程ID,若出错返回-1
以下是一个示例代码,用于创建一个子进程并展示父进程和子进程的ID与父进程ID,以及如何通过信号避免僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
void sigchld_handler(int sig) {
int status;
pid_t id = waitpid(-1, NULL, WNOHANG);
if (WIFEXITED(status))
{
printf("remove proc id: %d \n", id);
printf("Child send: %d \n", WEXITSTATUS(status));
}
}
int main(int argc, char *argv[])
{
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
// 初始化信号屏蔽集,确保不阻塞任何额外信号
sigemptyset(&sa.sa_mask);
// 注册 SIGCHLD 信号处理
sigaction(SIGCHLD, &sa, NULL);
pid_t pid;
int val = 24;
if ((pid = fork()) < 0) {
fprintf(stderr, "fork err: %d\n", pid);
exit(1);
} else if (pid == 0)
{
val++;
printf("child: pid = %d, ppid = %d\n", getpid(), getppid());
printf("child: val = %d\n", val);
_exit(0);
} else
{
sleep(5);
printf("root: pid = %d, ppid = %d, child_pid = %d\n", getpid(), getppid(), pid);
printf("root: val = %d\n", val);
sleep(5);
}
exit(0);
}
以下是执行程序后的结果,一个有意思点是程序的父进程ID是bash,子进程修改的数据并不会影响到父进程的数据。
zhumouren@ThinkBook16:~/study_program/apue.3e-ubuntu24/chapter_8$ ./processes_test
child: val = 25
remove proc id: 174545
Child send: 0
root: pid = 174543, ppid = 128279, child_pid = 174545
root: val = 24
zhumouren@ThinkBook16:~/study_program/apue.3e-ubuntu24/chapter_8$ ps
PID TTY TIME CMD
56148 pts/10 00:00:00 bash
63442 pts/10 00:00:00 ps
3. 进程终止
进程有5种正常终止以及3种异常终止方式。不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
线程终止方式第一遍看起来很枯燥,但很有了解的必要。
3.1 正常终止
- 在main函数内执行return。等效于调用exit
- 调用exit()函数,此函数由ISO C定义在<stdlib.h>,其操作会调用各终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有标准I/O流等。
适合标准C程序中正常退出使用
- _exit()函数,是一个系统调用在<unistd.h>中,它是低层次的退出函数,不会执行任何清理工作,直接通过内核终止进程,不经过 C 库的清理流程。
更适合在多线程或 fork 的子进程中使用
- 进程的最后一个线程在其启动例程中执行return语句。但是,该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。
- 进程的最后一个线程调用 pthread_exit 函数。如同前面一样,在这种情况中,进程终止状态总是0,这与传送给pthread_exit的参数无关。
3.2 异常终止
- 调用abort。它产生SIGABRT信号,这是下一种异常终止的一种特例。
- 当进程接收到某些信号时。信号可由进程自身(如调用abort函数)、其他进程或内核产生。如引用错误地址、除以0等。
- 最后一个线程对“取消”(cancellation)请求作出响应。
4. wait()和waitpid()
#include <sys/wait.h>
pid_t wait(int *statloc);
/**
pid: -1 等待任一子进程,>0 等待进程ID与pid相等的子进程,=0 等待组ID等于调用进程组ID的任一子进程,< -1等待组ID等于pid绝对值的任一子进程
*/
pid_t waitpid(pid_t pid, int *statloc, int options);
两个函数返回值:若成功,返回进程ID,若出错,返回0(见后面的说明)或−1。这两个函数都可以显示的获取子进程返回值,防止僵尸进程。
两个函数的区别
- 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞。
- waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
以下是wait处理各种返回
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void
pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
void
err_sys(const char *msg)
{
fprintf(stderr, "%s", msg);
exit(1);
}
int main(int argc, char *argv[])
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
exit(7);
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
abort(); /* generates SIGABRT */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 generates SIGFPE */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
return 0;
}
以下是程序执行的结果。
normal termination, exit status = 7
abnormal termination, signal number = 6 (core file generated)
abnormal termination, signal number = 8 (core file generated)
参考书目
本文章很多内容都是参考《UNIX环境高级编程(第三版)》,基本上就是一个笔记。
- UNIX环境高级编程(第三版)