【进程控制】
1. 写时拷贝
父进程创建了子进程,子进程的PCB是拷贝的父进程的PCB,内容一致。并且数据段,代码段,子进程是与父进程共享的(此时权限会被设为只读),当子进程要对其进行修改,此时会发生写时拷贝,将子进程要修改的数据,拷贝一份,单独交给子进程(这一部分权限可写,可读
)。
2. 进程终止
2.1 从main
返回
2.2 调用exit
2.3 _exit
3. 进程等待
wait
和 waitpid
是 Unix/Linux 系统中用于处理子进程状态变化的系统调用,通常在 C 或 C++ 编程里使用。下面为你详细介绍:
3.1 wait
函数
- 函数原型:
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
- 功能:
wait
函数会让调用进程阻塞,直到它的任意一个子进程终止。若子进程已经终止,wait
会立即返回。返回值是终止子进程的进程 ID,若出错则返回 -1。 - 参数:
status
是一个整型指针,用于存储子进程的终止状态。若不关心终止状态,可将其设为NULL
。 - 示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child process is running.\n");sleep(2);printf("Child process is exiting.\n");exit(0);} else {// 父进程int status;pid_t child_pid = wait(&status);if (child_pid > 0) {if (WIFEXITED(status)) {printf("Child process %d exited normally with status %d.\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d was terminated by signal %d.\n", child_pid, WTERMSIG(status));}}}return 0;
}
3.2 waitpid
函数
- 函数原型:
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);
- 功能:
waitpid
函数的功能与wait
类似,不过它可以指定等待某个特定的子进程,还能通过options
参数来控制等待行为。返回值是终止子进程的进程 ID,若指定WNOHANG
且没有子进程终止则返回 0,若出错则返回 -1。 - 参数:
pid
:用于指定要等待的子进程的进程 ID。若pid > 0
,则等待指定进程 ID 的子进程;若pid == -1
,则等待任意子进程,等同于wait
;若pid == 0
,则等待与调用进程在同一进程组的任意子进程;若pid < -1
,则等待进程组 ID 等于pid
绝对值的任意子进程。status
:和wait
中的status
作用相同,用于存储子进程的终止状态。options
:是一个位掩码,可使用WNOHANG
让waitpid
非阻塞地返回,即若没有子进程终止则立即返回 0;还可使用WUNTRACED
来关注因收到信号而停止的子进程。
- 示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child process is running.\n");sleep(2);printf("Child process is exiting.\n");exit(0);} else {// 父进程int status;pid_t child_pid = waitpid(pid, &status, WNOHANG);if (child_pid == 0) {printf("No child process has exited yet.\n");} else if (child_pid > 0) {if (WIFEXITED(status)) {printf("Child process %d exited normally with status %d.\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d was terminated by signal %d.\n", child_pid, WTERMSIG(status));}}}return 0;
}
3.3 区别总结
- 灵活性:
wait
只能等待任意子进程终止,而waitpid
能指定等待某个特定的子进程,并且可以控制等待行为,如非阻塞等待。 - 阻塞特性:
wait
是阻塞调用,会一直等待直到有子进程终止;waitpid
可通过WNOHANG
选项实现非阻塞调用。
4. 进程替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
替换函数
4.1 execl
4.2 execlp
4.3 execle
4.4 execv
4.5 execvp
4.6 execvpe
execvpe
是 Unix/Linux 系统里 exec
函数族中的一员,该函数族的作用是在当前进程的上下文中执行新程序,替换当前进程的映像。下面详细介绍 execvpe
函数。
4.6.1 函数原型
#include <unistd.h>
#include <stdlib.h>int execvpe(const char *file, char *const argv[], char *const envp[]);
4.6.2 参数说明
file
:指向要执行的程序文件名的指针。此文件名无需是完整路径,execvpe
会依据PATH
环境变量来查找该程序。argv
:指向参数数组的指针,该数组里的每个元素都是一个指向字符串的指针,代表传递给新程序的参数。数组的第一个元素通常是新程序的名称,最后一个元素必须为NULL
,以此标记参数列表的结束。envp
:指向环境变量数组的指针。数组中的每个元素都是形如name=value
的字符串,用于为新程序设置环境变量。
4.6.3 功能说明
execvpe
函数会用指定的程序替换当前进程的映像。若调用成功,它不会返回;只有在调用失败时,才会返回 -1,并设置相应的错误码。
4.6.4 示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {// 定义要执行的程序文件名const char *file = "ls";// 定义传递给程序的参数数组char *argv[] = {"ls","-l",NULL};// 定义环境变量数组char *envp[] = {"PATH=/bin:/usr/bin","HOME=/home/user",NULL};// 调用 execvpe 函数执行新程序int result = execvpe(file, argv, envp);// 如果 execvpe 调用失败,会执行到这里if (result == -1) {perror("execvpe");return 1;}return 0;
}
4.6.5 代码解释
- 在这个示例中,我们运用
execvpe
函数执行了ls
程序,并且传递了-l
参数。 - 为新程序设置了两个环境变量
PATH
和HOME
。 - 若
execvpe
调用成功,当前进程会被ls
程序替代,输出文件列表。若调用失败,则会输出错误信息。
4.6.6 注意事项
- 由于
execvpe
调用成功后不会返回,所以调用之前的代码会被执行,而调用之后的代码只有在调用失败时才会执行。 - 若
file
参数不是完整路径,execvpe
会依据envp
数组里的PATH
环境变量查找程序。若envp
中未包含PATH
,则使用当前进程的PATH
环境变量。 - 传递给新程序的参数列表和环境变量数组都必须以
NULL
结尾。