3.子进程
在Linux中,子进程是由父进程创建的进程。当一个进程被创建时,它会自动成为一个新进程的父进程,而新进程则成为子进程。子进程可以通过fork()
系统调用来创建。
- 创建子进程的意义:子进程常用于实现多任务并行处理,提高程序的执行效率。它们可以分担父进程的工作负载,或者执行不同的任务。
- 父子进程区别:虽然子进程是父进程的一个拷贝,但它们有不同的内存地址空间。子进程获得与父进程相同的数据和属性的副本,但是有自己的数据段和堆栈段。
创建子进程
fork 函数
原型:
#include <unistd.h> pid_t fork(void);
功能:创建一个新进程,新进程是当前进程的一个副本。新进程从父进程处继承了代码、数据、堆栈等资源,但是它们在不同的内存空间中运行。
参数:无
返回值:
- 成功,创建新进程,则在父进程中返回新进程的进程ID(大于0),在子进程中返回0。
- 失败,返回-1。
示例1:在父进程中打印"pid = 进程号",在子进程中打印"pid = 0"
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); printf("pid = %d\n",pid); return 0; }
示例2:
fork
的一般用法#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); printf("pid = %d\n",pid); if(pid > 0)//父进程 { printf("Father\n"); } else if(pid == 0)//子进程 { printf("Child\n"); } else//出错 { perror("fork\n"); return 0; } return 0; }
示例3:使用for循环生成多个子进程
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; int i; for(i=0; i<3; i++) { pid = fork(); if(pid < 0) { perror("fork"); return 0; } else if(pid == 0) { printf("%d Child %d\n",i,pid); sleep(5); } else if(pid > 0) { printf("%d Father%d\n",i,pid); sleep(5); } } return 0; }
示例3分析:
生成3个子进程3个孙进程1个曾孙进程
- 子进程:2、3、5 (1 生的)
- 孙进程:4、6、7 (2、3、5 生的)
- 曾孙进程:8 (4、6、7 生的)
注意:
- 子进程只执行fork之后的代码
- 父子进程执行顺序是操作系统决定的
- 子进程继承了父进程的内容
- 父子进程有独立的地址空间,互不影响
- 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程
- 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程
结束进程
exit 函数
原型:
#include <unistd.h> void exit(int status);
功能:终止当前进程,刷新(流)缓冲区并将退出状态码
status
传递给操作系统。这个状态码可以被其他进程或父进程获取,以了解子进程的结束状态参数:
status
是一个整数,表示进程的退出状态码返回值:无
_exit 函数
原型:
#include <unistd.h> void _exit(int status);
功能:与
exit
函数类似,但_exit
函数不会刷新(流)缓冲区,它直接终止进程,并将退出状态码传递给操作系统。参数:
status
是一个整数,表示进程的退出状态码返回值:无
return 和 exit 的区别:
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
示例:
#include <stdio.h> #include <stdlib.h> int main(void) { printf(“before exit”); exit(0); printf(“after exit”); }
编译运行后只打印“before exit”
回收子进程
回收子进程的必要性:
- 避免资源浪费:当一个子进程结束时,它能够释放自己用户区的资源,但无法释放内核空间的资源,如进程控制块(PCB)。如果父进程不回收这些资源,它们将一直占用内存,导致系统资源的浪费。
- 获取退出信息:父进程通过回收子进程可以获取到子进程的退出状态,如退出码和执行时间等信息。这对于父进程监控子进程的行为和进行后续处理是非常重要的。
- 防止僵尸进程:如果父进程没有及时回收子进程,那么子进程虽然已经结束,但其PCB仍然保留在系统中,这样的进程称为僵尸进程。僵尸进程不执行任何操作,但占用系统资源,如果大量僵尸进程存在,会影响系统性能。
- 管理孤儿进程:如果子进程的父进程先于子进程结束,子进程将成为孤儿进程。为了避免孤儿进程无人管理,系统会让init进程(进程号为1的进程)收养它们。init进程会负责回收这些孤儿进程的资源。
回收子进程不仅是为了维护操作系统资源的有效性,也是为了保证程序能够正确获取子进程的执行结果,以及防止产生僵尸进程和妥善管理孤儿进程,从而保证系统的稳定性和程序的可靠性。
wait 函数
原型:
#include <unistd.h> pid_t wait(int *status);
功能:挂起父进程的执行,直到一个子进程结束。一旦有子进程结束,
wait()
函数将返回该子进程的PID,并回收其资源。如果传入了status
参数,则wait()
函数还会将子进程的退出状态码写入到status指向的变量中。参数:
status
是一个整数指针,用于存储子进程的退出状态码。返回值:
- 成功,返回已结束的子进程的PID;
- 失败,返回-1,并设置errno为相应的错误码
注:
- 若子进程没有结束,父进程一直阻塞
- 若有多个子进程,哪个先结束就先回收
示例:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char** argv) { pid_t pid; pid_t rpid; pid = fork(); int status; if(pid<0)//出错 { perror("fork"); return 0; } else if(pid == 0)//子进程 { sleep(2); printf("child 2 \n"); exit(2); } else if(pid >0)//父进程 { rpid = wait(&status); printf("Get child status=%d\n",WEXITSTATUS(status)); } }
waitpid 函数
原型:
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
功能:等待指定子进程的状态改变。当子进程结束时,该函数会返回子进程的进程ID,并将子进程的退出状态存储在
status
指向的变量中参数:
pid
:指定要等待的子进程的进程ID。可以是以下值的组合:
-1
:等待任意子进程。0
:等待与调用进程属于同一进程组的任何子进程。>0
:等待指定的子进程。status
:指向一个整数变量的指针,用于存储子进程的退出状态。如果不关心退出状态,可以设置为NULL
。options
:指定额外的选项,可以是以下值的组合:
WNOHANG
:如果没有子进程状态改变,立即返回,不阻塞。WUNTRACED
:如果子进程进入暂停状态,也视为状态改变。返回值:
- 成功时,返回已经停止的子进程的进程ID。
- 如果没有任何子进程状态改变,根据
options
参数的设置,可能返回0或-1。- 失败时,返回-1,并设置errno。
示例:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char** argv) { pid_t pid; pid_t rpid; pid = fork(); int status; if(pid<0)//出错 { perror("fork"); return 0; } else if(pid == 0)//子进程 { sleep(10); printf("child 2 \n"); exit(2); } else if(pid >0)//父进程 { waitpid(pid,&status,0); printf("Get child status=%d\n",WEXITSTATUS(status)); } }
读取status参数
当使用wait()
或waitpid()
函数等待子进程结束时,可以通过传入的int *status
指针来获取子进程的退出状态。这个状态包含两个主要部分:退出代码和信号编号(如果适用)。
通过以下宏来获取status
的信息
WIFEXITED(status)
判断子进程是否正常结束WEXITSTATUS(status)
获取子进程返回值WIFSIGNALED(status)
判断子进程是否被信号结束WTERMSIG(status)
获取结束子进程的信号类型
示例-创建进程链
创建一个进程链,父进程->子进程->孙进程->重孙进程->重重孙进程
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; int i; for(i=0; i<5; i++) { pid=fork(); if(pid < 0) { perror("fork"); return 0; } if(pid > 0) { printf("father %d\n",i); break; } if(pid == 0) { printf("child %d\n",i); } } sleep(20); wait(0); return 0; }
通过
ps -elf | grep a.out
指令查看