什么是进程?
进程是操作系统进行资源分配和调度的基本单位,它代表了正在运行的程序。与程序不同,进程是动态的,它包括代码、数据以及分配给它的其他系统资源,如文件描述符、网络连接等。例如,我们打开的VMWare、浏览器等软件,都对应操作系统中的一个进程。
使用system函数生成子进程
system
函数是标准库中用于执行shell指令的函数。它会启动一个shell子进程来执行传入的命令。例如,我们可以使用system("ping -c 10 www.baidui.com")
来向www.baidu.com发送10个ICMP回声请求。在子进程执行过程中,我们可以通过ps -ef
命令查看所有进程,定位刚刚启动的进程及其父子关系。
进程处理相关系统调用
fork
fork
系统调用用于创建一个子进程,它会复制父进程的资源,包括内存空间。fork
的返回值在父进程中是子进程的PID,在子进程中是0,如果出错则返回-1。通过fork
,我们可以实现进程的复制和并发执行。
execve
execve
系列函数用于在同一个进程中跳转执行另外一个程序。它会替换当前进程的代码、数据和堆栈等资源,但保留进程ID。例如,我们可以通过execve("/home/fish/process_test/erlou", args, envs)
来在当前进程中执行另一个程序erlou。
waitpid
waitpid
系统调用用于父进程等待子进程的终止并获取子进程的退出状态。它可以帮助父进程回收子进程的资源,避免子进程变成僵尸进程。例如,waitpid(pid, &subprocess_status, 0)
会一直挂起父进程的执行,直至子进程pid终止。
进程树
Linux的进程通过父子关系组织起来,构成进程树。每个进程都是其上级节点的子进程,同时又是子结点的父进程。进程树的根节点是操作系统的第一个进程,即PID为1的进程,它负责初始化系统,启动其他所有用户空间的服务和进程。
孤儿进程
孤儿进程是指父进程已结束或终止,而它仍在运行的进程。当父进程结束之前没有等待子进程结束,且父进程先于子进程结束时,子进程就会变成孤儿进程。孤儿进程会被其祖先自动领养,继续运行直到完成任务或被系统回收。
waitpid系统调用
waitpid的定义与作用
waitpid
系统调用用于父进程等待子进程的终止,并获取子进程的退出状态。它可以帮助父进程回收子进程的资源,避免子进程变成僵尸进程。waitpid
的原型如下:
pid_t waitpid(pid_t pid, int *wstatus, int options);
- 参数:
pid
:等待的子进程ID。小于-1时,等待进程组ID等于-pid
的所有进程终止;等于-1时,等待任何子进程终止;等于0时,等待同一进程组中任何子进程终止;大于0时,仅等待指定进程ID的子进程终止。wstatus
:指向整数的指针,用于存储子进程的退出状态。options
:选项参数,可以设置为WNOHANG
(如果没有子进程终止,也立即返回)、WUNTRACED
(收到子进程处于收到信号停止的状态,也返回)等。
- 返回值:成功时返回子进程的PID;如果没有子进程终止且设置了
WNOHANG
,返回0;出错时返回-1。
waitpid的使用示例
下面是一个使用waitpid
系统调用的示例代码:
#include <stdio.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("子进程PID: %d\n", getpid());
// 执行一些操作,例如sleep
sleep(2);
// 子进程退出
exit(0);
} else {
// 父进程代码
printf("父进程PID: %d, 等待子进程PID: %d\n", getpid(), pid);
int wstatus;
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus)) {
printf("子进程正常退出,退出状态码: %d\n", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) {
printf("子进程被信号终止,信号编号: %d\n", WTERMSIG(wstatus));
}
}
return 0;
}
在这个示例中,我们使用fork()
创建一个子进程,然后父进程使用waitpid(pid, &wstatus, 0)
等待子进程终止。wstatus
变量用于存储子进程的退出状态,我们可以通过宏WIFEXITED
和WEXITSTATUS
来判断子进程是否正常退出以及获取退出状态码。
waitpid的执行过程分析
当调用waitpid
系统调用时,它会执行以下步骤:
- 等待子进程终止:父进程会挂起执行,等待指定的子进程终止。如果设置了
WNOHANG
选项,父进程不会挂起,而是立即返回。 - 获取子进程退出状态:当子进程终止时,
waitpid
会获取子进程的退出状态,并将其存储在wstatus
指针指向的整数中。 - 回收子进程资源:父进程通过
waitpid
回收子进程的资源,避免子进程变成僵尸进程。
waitpid的注意事项
- 避免僵尸进程:父进程应及时调用
waitpid
回收子进程的资源,避免子进程变成僵尸进程,占用系统资源。 - 错误处理:如果
waitpid
执行失败,会返回-1,并设置errno
变量以指示错误原因。例如,如果指定的子进程ID不存在,errno
会被设置为ECHILD
。 - 多子进程处理:如果父进程有多个子进程,可以通过循环调用
waitpid
来等待所有子进程的终止,并分别获取它们的退出状态。
进程树
进程树的概念与结构
进程树是Linux系统中进程的组织结构,它通过父子关系将所有进程连接起来。进程树中的每个节点代表一个进程,每个进程都是其上级节点的子进程,同时又是子结点的父进程。进程树的根节点是操作系统的第一个进程,即PID为1的进程,它负责初始化系统,启动其他所有用户空间的服务和进程。
进程树的查看方法
- ps命令:使用
ps -ef
命令可以查看系统中所有进程的信息,包括进程的PID、父进程PID、用户ID、优先级、虚拟内存使用情况、物理内存使用情况、CPU时间等。通过ps -ef
命令的输出结果,我们可以追溯进程的父子关系。 - pstree命令:使用
pstree
命令可以以树状图的形式展示所有用户进程的依赖关系。pstree
命令会从根节点开始,逐级展示进程树的结构,使得进程之间的父子关系一目了然。例如,pstree -p
命令会显示进程号,方便我们查看进程的具体信息。
进程树的应用场景
- 系统监控:通过查看进程树,系统管理员可以了解系统中进程的运行情况,发现潜在的问题。例如,如果某个进程占用了大量的系统资源,可以通过进程树找到其父进程,分析问题的原因。
- 故障排查:当系统出现故障时,查看进程树可以帮助我们快速定位故障源头。例如,如果某个服务进程崩溃了,可以通过进程树找到与其相关的其他进程,分析它们的状态和日志,找出故障的原因。
- 性能优化:通过分析进程树,可以发现系统中不必要的进程或资源占用过多的进程,进行相应的优化。例如,如果发现某个进程的子进程过多,可以考虑减少子进程的数量,或者优化子进程的资源使用,提高系统的整体性能。
孤儿进程
孤儿进程的定义与产生原因
孤儿进程是指父进程已结束或终止,而它仍在运行的进程。孤儿进程的产生原因主要有以下几点:
- 父进程先于子进程结束:如果父进程在子进程之前结束,且没有等待子进程的终止,子进程就会变成孤儿进程。
- 父进程异常退出:如果父进程由于异常原因(如程序错误、系统崩溃等)退出,而子进程仍在运行,子进程也会变成孤儿进程。
孤儿进程的处理机制
在Linux系统中,孤儿进程会被其祖先自动领养。具体来说,当父进程结束时,孤儿进程会被PID为1的进程(通常是systemd
或init
进程)领养。领养后的孤儿进程会继续运行,直到完成任务或被系统回收。例如,如果一个父进程创建了一个子进程,然后父进程退出,子进程会被systemd
进程领养,继续运行。
孤儿进程的影响与注意事项
- 资源占用:孤儿进程仍然会占用系统资源,如内存、CPU等。如果孤儿进程过多,可能会导致系统资源紧张,影响系统的性能。
- 日志记录:孤儿进程的日志记录可能会受到影响,因为它们与原始父进程的联系已经切断。在某些情况下,可能难以追踪孤儿进程的日志信息,给问题排查带来困难。
- 避免孤儿进程的建议:在编写程序时,要注意避免产生孤儿进程。可以通过以下方法来避免:
- 等待子进程终止:父进程在结束之前,应该使用
wait
或waitpid
等系统调用来等待子进程的终止,并回收子进程的资源。 - 设置信号处理:父进程可以设置信号处理函数,在接收到特定信号时,执行子进程的清理工作,确保子进程能够正常退出。
- 合理设计程序逻辑:在程序设计时,要合理安排父子进程的执行顺序和逻辑,避免父进程提前结束或异常退出,导致子进程变成孤儿进程。
- 等待子进程终止:父进程在结束之前,应该使用