两个函数
创建进程:fork()
暂缓进程执行:sleep()
1.fork()函数
1.使用方法:
#include <unistd.h>
pid_t fork(void);
2.函数功能:
函数执行后,系统会创建一个与原进程几乎相同的进程,之后父子进程都继续执行。子进程创建成功后,原程序会被复制,就有了两个fork函数。若子进程创建失败,原程序不会复制。
3.返回值:
成功:返回两个值:
- 父进程的fork函数会返回子进程的pid,
- 子进程的fork函数会返回0。
不成功:只有父进程的fork函数返回-1。
Eg.1.创建单个子进程
使用fork函数创建一个进程,创建成功后父子进程分别执行不同的功能。
test_fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
pid_t tempPid;
tempPid = fork();
if(tempPid == -1){
perror("fork error");
}else if(tempPid > 0){//parent
printf("parent process, pid = %d, chilpid = %d\n", getpid(), tempPid);
}else{//child
printf("child process, pid = %d, ppid = %d\n", getpid(), getppid());
}//of if
printf("......finish......");
return 0;
}//of main
补充说明:
getpid()返回值为调用getpid()进程的PID,而getppid()返回调用这个函数进程的父进程的PID
执行结果:
Question1:
?两次执行,结果不同:一个是先调用父进程,一个是先调用子进程
因为两进程优先级相同,两进程同时抢资源,所以执行顺序可能不同。
Question2:
?发现子进程的ppid并不等于父进程的pid
我们可以先来看一下1412是个什么东西
/usr/lib/systemd/system目录自动存放启动文件的配置位置
而老师上课的时候提到了init,说会被init进行领养,下面问题变成孤儿进程为什么不是被init领养
在Ubuntu20.04中:
所以归根结底还是init领养了子进程
fork()创建子进程后,在两进程抢夺资源的时候,父进程先于子进程被调用,然后父进程被摧毁,子进程接着被调用,但是父进程已经被摧毁,子进程变为孤儿进程,init会“收养”这个子进程所以该子进程的父进程就变为了init。
我们可以和图一进行对比,发现虽然两次运行都是父进程先运行,但是第一次子进程运行的时候父进程并没有结束运行,所以子进程的父进程显示的还是我们的父进程而不是init。
为了防止子进程变为孤儿进程,可以使父进程后于子进程执行,即让父进程等待子进程执行结束后再执行。
此处我们用到sleep()函数:此处我们先卖个关子,在第二点进行详细案例说明。
我们创建了一个子进程,那么要想通过父进程创建多个子进程又该怎么办呢?
Eg.2.创建多个子进程
int i;
for(i = 0; i < 2; i ++){
tempPid = fork();
}//of for i
执行结果:
此时会发现创建进程数量与预计的不同,产生了4个进程并且两个父进程,两个子进程。
父进程在创建子进程的同时,之前创建的子进程也会调用fork函数进行子进程的创建,成为了之后创建的进程的父进程,如果只想父进程才进行子进程的创建的话,必须进行判断:如果是父进程才能创建子进程。
改进版:Eg.2.创建多个子进程
test_fork2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
pid_t tempPid;
int i;
for(i = 0; i < 2; i ++){
if((tempPid = fork()) == 0){
break;
}//of if
}//of for i
if(tempPid == -1){
perror("fork error");
}else if(tempPid > 0){//parent
printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());
}else{//child
printf("I am child process = %d, pid = %d, ppid = %d\n", i + 1, getpid(), getppid());
}//of if
printf("......finish......");
return 0;
}//of main
执行结果:
2.sleep()函数
函数功能:
暂缓进程执行
Eg.1.父进程回收全部子进程
test_fork3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
pid_t tempPid;
int i;
for(i = 0; i < 2; i ++){
if((tempPid = fork()) == 0){
break;
}//of if
}//of for i
if(tempPid == -1){
perror("fork error");
}else if(tempPid > 0){//parent
printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());
sleep(2);
}else{//child
sleep(i);
printf("I am child process = %d, pid = %d, ppid = %d\n", i + 1, getpid(), getppid());
}//of if
printf("......finish......");
return 0;
}//of main
运行结果:
完美解决了孤儿进程的问题。
补充:
僵尸进程:
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。僵尸进程会占用进程号。
下面摘自百度百科:
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。但UNⅨ提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。