进程之间的数据共享
使用fork函数之后,生成的父进程和子进程两个地址空间区完全相同,后续可以进行不同操作
各个进程的地址空间中的数据是完全独立的
对于同一个变量,我们是有一个物理地址存放的,在读的时候数据是子进程和父进程共享的,在写的时候分别在物理地址上拷贝一份变量进行单独读写
父子进程之间不能通过全局变量进行通信
通过代码验证,可知父子进程之间不能通过全局变量进行通信。
exec函数族
exec
命令用于替换当前 shell 进程的图像。也就是说,它会加载并运行指定的命令,而这个命令会完全替代当前的 shell 进程,而不是在当前 shell 中启动一个子进程。这意味着一旦 exec
命令后面的程序执行完毕,shell 就会退出。
函数的原型
int execl(const char *path, const char *arg, ...
/* path : 要执行程序的路径(最好是绝对路径)
变参arg : 要执行的程序需要的参数 第一位arg: 占位 后边的
arg: 命令的参数
参数写完之后:null 一般执行自己写的程序 */
归根结底只有三个参数:路径+占位符+指令参数+NULL(结束语)
下面代码通过excel函数使用ls -l指令并替换掉当前子进程的其他程序(父进程不变)
下面代码通过执行PATH环境变量能够搜索到的程序(系统自带),execlp函数实现ps指令
函数原型:
int execle(const char *path, const char *arg, ...
即:执行指令的名称+占位符+命令的参数+NULL
孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进 程(进程号为1)所收养,并由init进程对它们完成状态收集工作
注:资源回收是父进程回收的
为了释放子进程的占用的系统资源:
1.进程结束之后,能够释放用户区空间
2.释放不了PCB,必须由父进程释放
下面展现一个孤儿进程,我们使用一个sleep指令(允许在脚本或命令行中暂停执行一段时间),使子进程一直运行,但是父进程早已结束,从而形成了孤儿进程。
僵尸进程
一个比较特殊的状态,当进程退出父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。(一般是父进程还在运行,子进程已经结束)
僵尸进程是一个已经死掉了的进程。
这里我们可以写一个僵尸进程,使父进程一直运行,子进程结束
我们通过ps指令找到这个进程,发现一进城出现 Z+ 的状态(僵尸的缩写ZOMBIE),在这时我们通过kill指令是不能使该进程结束的,因为僵尸进程是一个已经死掉的进程,我们想要使这个进程停止,必须给a.out(进程所在的可执行程序)发送kill指令
成功停止
进程的回收
wait阻塞函数
阻塞:当一个函数在执行过程中,如果必须等待某个事件的发生或条件的满足才能继续执行,那么在这个等待期间,函数所在的线程或进程会被挂起(即停止执行),直到所等待的事件发生或条件满足后,函数才会继续执行并返回结果。这种现象就被称为函数的阻塞作用。
函数作用:
1.阻塞并等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
函数原型
pid_t wait(int *wstatus);
返回值:
‐1 : 回收失败,已经没有子进程了
>0 : 回收子进程对应的pid
参数 :
status判断子进程如何退出状态
1.WIFEXITED(status):为非0 ,进程正常结束
WEXITSTATUS(status)
如上宏为真,使用此宏,获取进程退出状态的参数
2.WIFSIGNALED(status):为非0,进程异常退出
WTERMSIG(status):
如上宏为真,使用此宏,取得使进程终止的那个信号的编号
调用一次只能回收一个子进程
再来一个例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
// 如果fork失败,则打印错误信息并退出
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程执行的代码
printf("This is the child process with PID %d\n", getpid());
sleep(5); // 让子进程睡眠5秒以模拟工作
exit(0); // 子进程正常退出
} else {
// 父进程执行的代码
int status;
printf("This is the parent process waiting for the child process\n");
wait(&status); // 父进程等待子进程结束
if (WIFEXITED(status)) {
// 检查子进程是否正常退出
printf("Child process exited normally with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// 检查子进程是否被信号杀死
printf("Child process killed by signal %d\n", WTERMSIG(status));
}
}
return 0;
}
waitpid 函数
waitpid函数 函数作用:同wait函数
pid_t waitpid(pid_t pid, int *status, int options);
参数
1.pid: 指定回收某个子进程
pid == ‐1 回收所有子进程 while( (wpid=waitpid(‐1,status,0)) != ‐1)
pid > 0 回收某个pid相等的子进程
pid == 0 回收当前进程组的任一子进程
pid < 0 子进程的PID取反(加减号)
2.status: 子进程的退出状态,用法同wait函数
3.options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞
返回值:
>0 :返回清理掉的子进程ID
‐1 :回收失败,无子进程
如果为非阻塞
=0 :参数3为WNOHANG,且子进程正在运行
在这个示例中,我们将使用waitpid
函数来演示非阻塞等待和等待特定子进程的功能。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程执行的代码
printf("This is the child process with PID %d\n", getpid());
sleep(10); // 让子进程睡眠10秒以模拟长时间工作
exit(0); // 子进程正常退出
} else {
// 父进程执行的代码
int status;
pid_t ret;
// 非阻塞等待子进程结束
ret = waitpid(pid, &status, WNOHANG);
if (ret == 0) {
printf("Child process is still running\n");
} else if (ret == pid) {
// 如果ret等于pid,说明子进程已经结束
printf("Child process ended\n");
if (WIFEXITED(status)) {
printf("Child process exited normally with status %d\n", WEXITSTATUS(status));
}
} else {
// 如果ret不是pid,说明发生了错误
perror("waitpid failed");
}
// 如果需要,可以在这里执行其他操作
// ...
// 阻塞等待子进程结束(如果需要的话)
// ret = waitpid(pid, &status, 0);
// if (ret == pid) {
// printf("Child process ended (blocking wait)\n");
// if (WIFEXITED(status)) {
// printf("Child process exited normally with status %d\n", WEXITSTATUS(status));
// }
// } else {
// perror("waitpid failed (blocking)");
// }
}
return 0;
}
非阻塞运行结果
阻塞运行结果
vfork创建进程
区别一:vfork可以直接使用父进程存储空间(共享物理地址),不拷贝
区别二:vfork可以保证子进程先运行,当子进程调用exit退出后,父进程才执行
我们可以写一个程序去验证
可以得出使用vfork函数可以让子进程先运行,通过exit可以退出子进程(不能使用break,因为使用break可以理解为把子进程完全关闭了,里面i的值也被清空了,在父进程调用的时候i的值已经被清空了会被系统随机赋予一个随机的值),再进行父进程。当 exit(0)
被调用时,程序会执行一系列清理操作,比如刷新输出缓冲区、关闭打开的文件描述符等在 exit
或 _exit
后面加上 (0)
表示将进程的退出状态码设置为 0
,这通常表示程序成功执行完毕。
进程的退出
1. 正常退出
- 从main函数返回:当程序的
main
函数执行完毕并返回一个值时,进程会正常退出。这个返回值通常用于表示程序的执行状态,0表示成功,非0值表示出现了某种错误或异常情况。 - 调用exit函数:
exit
函数是C标准库提供的一个用于正常终止进程的函数。它会执行一些清理工作(如刷新输出缓冲区、关闭文件描述符等),然后向操作系统发送退出信号,并传递退出状态码。 - 调用_exit或_Exit函数:这两个函数是系统调用,用于立即终止进程,而不执行
exit
函数中的清理工作。它们通常用于在子进程中快速退出,以避免不必要的资源消耗。 - 进程最后一个线程返回或调用pthread_exit:在多线程程序中,当最后一个线程返回或调用
pthread_exit
时,整个进程会退出。
2. 异常退出
- 调用abort函数:
abort
函数用于异常终止进程。它会生成一个SIGABRT
信号,该信号的默认行为是终止进程并产生核心转储(如果启用了该选项)。虽然abort
会调用exit
,但通常不会到达exit
的调用点,因为SIGABRT
信号会先被处理。 - 进程收到某些信号:当进程收到某些无法忽略或处理的信号时(如
SIGKILL
、SIGTERM
等),它会异常终止。这些信号通常由操作系统或其他进程发送,用于强制终止进程。