进程创建
参考博客
fork与vfork的区别
fork()与vfork()函数
- fork :子进程
拷贝
父进程的数据段、代码段等资源
vfork :子进程与父进程共享
数据段、代码段等资源 - fork :父子进程的
执行次序不确定
vfork :保证子进程先运行
,在调用exec或exit之前与父进程数据是共享的,在它调用exec
或exit之后父进程才可能被调度运行 - vfork :保证子进程先运行,在调用exec 或exit之后父进程才可能被调度运行。如果在
调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁
fork()函数
#include <unistd.h>/* Clone the calling process, creating an exact copy.Return -1 for errors, 0 to the new process,and the process ID of the new process to the old process. */
extern __pid_t fork (void) __THROWNL;
例1:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;pid = fork();cout << "pid:" << pid << endl;if (pid < 0)printf("error in fork!\n");else if (pid == 0)printf("I am the child process,ID is %d\n", getpid());elseprintf("I am the parent process,ID is %d\n", getpid());return 0;
}
运行输出:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test
pid:6448
I am the parent process,ID is 6447
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ pid:0
I am the child process,ID is 6448
- fork()函数用于从已存在的进程中创建一个新的进程,新的进程称为子进程,而原进程称为父进程
- fork()的返回值有两个,子进程返回0,父进程返回子进程的进程号,进程号都是非零的正整数,所以父进程返回的值一定大于零
- 在
pid=fork();
语句之前只有父进程在运行,而在pid=fork();
之后,父进程和新创建的子进程都在运行 - 如果pid==0,那么肯定是子进程,若pid≠0 (事实上肯定大于0),那么是父进程在运行
- fork()函数子进程是拷贝父进程的代码,那么下面的代码会被执行两遍
cout << "pid:" << pid << endl;
if (pid < 0)printf("error in fork!\n");
else if (pid == 0)printf("I am the child process,ID is %d\n", getpid());
elseprintf("I am the parent process,ID is %d\n", getpid());
例2:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;int count = 0;pid = fork();cout << "pid:" << pid << endl;if (pid < 0){printf("error in fork!\n");}else if (pid == 0){printf("I am the child process,ID is %d\n", getpid());++count;cout << "child process count:" << count << endl;}else{printf("I am the parent process,ID is %d\n", getpid());++count;cout << "parent process count:" << count << endl;}return 0;
}
运行输出:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test
pid:6700
I am the parent process,ID is 6699
parent process count:1
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ pid:0
I am the child process,ID is 6700
child process count:1
注意++count;
运行了两次,但两次输出都是1
因为fork()函数子进程拷贝
父进程的数据段代码段等资源,++count;
被父子进程各执行一次,但是子进程执行自己数据段里面的(这个数据段是从父进程那copy 过来的一模一样)count+1,同样父进程执行自己的数据段里面的count+1,他们互不影响
💡 fork()函数子进程**拷贝
**父进程的数据段代码段等资源,相当于有独立的内存空间
vfork()函数
#include <unistd.h>
#include <sys/types.h>/* Clone the calling process, but without copying the whole address space.The calling process is suspended until the new process exits or isreplaced by a call to `execve'. Return -1 for errors, 0 to the new process,and the process ID of the new process to the old process. */
extern __pid_t vfork (void) __THROW;
将fork()函数例2代码中的fork()改为vfork(),编译运行,输出如下:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test
pid:0
I am the child process,ID is 6839
child process count:1
pid:6839
I am the parent process,ID is 6838
parent process count:1874747691
观察到父进程中的count
完全是错误的值
因为vfork创建的子进程会优先运行,且与父进程共享地址空间
,当执行完exit
后或者调用exec
后,父进程才可运行,而代码中未添加相应语句,导致子进程结束后count内存地址被销毁,父进程对count的操作相当于操作了一块非法的内存空间
更改如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;int count = 0;pid = vfork();cout << "pid:" << pid << endl;if (pid < 0){printf("error in fork!\n");}else if (pid == 0){printf("I am the child process,ID is %d\n", getpid());++count;cout << "child process count:" << count << endl;exit(0);}else{printf("I am the parent process,ID is %d\n", getpid());++count;cout << "parent process count:" << count << endl;}return 0;
}
运行输出:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test
pid:0
I am the child process,ID is 6928
child process count:1
pid:6928
I am the parent process,ID is 6927
parent process count:2
子进程调用exec
或exit
之前与父进程数据是共享
的,所以子进程退出后把父进程的数据段count改成1 ,子进程退出后,父进程又执行,最终就将count变成了2
进程等待
参考博客
Linux wait() 和 waitpid()函数介绍
【Linux】进程控制(wait 和 waitpid)的理解和使用
当有多个进程同时运行时,进程间需要协作工作,可能用到进程等待的操作,进程间的等待包括父子进程间的等待
和进程组内成员间的等待
一般我们在父进程fork出一个子进程,希望子进程完成某些功能,也就是帮助父进程完成某些任务的,所以父进程就需要知道子进程完成的状态如何,是成功还是失败,所以就需要父进程通过wait 或者waitpid函数等在子进程退出
为什么等待
- 父进程等待子进程退出,是因为父进程需要子进程退出的信息,和完成功能的状态如何
- 可以保证时序问题:子进程先退出,父进程再退出
- 可以预防子进程成为僵尸进程,防止内存泄漏的问题;而这我们需要父进程wait等待子进程退出之后,释放它的僵尸资源,也就子进程的PCB
- 并且我们需要知道,一旦进程成为僵尸状态,即使你使用 kill -9 也杀不死这个僵尸进程,只能通过父进程等待wait回收它
wait()函数
wait函数的作用是父进程调用,等待(阻塞式
)子进程退出,回收子进程的资源
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);返回值:
**成功返回被等待进程pid**,失败返回-1
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
示例:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{if (fork() == 0){// child processprintf("i am a child pid=%d\n", getpid());exit(0); //让子进程退出}// parent process执行,这里不会执行子进程了,因为子进程被我退出了sleep(2); //休息2s,为的是观察监控消息,是否子进程成为僵尸进程printf("wait函数开始执行\n");pid_t ret = wait(NULL);if (ret == -1){perror("wait error\n");}// wait返回成功printf("wait返回的是子进程的ret=%d执行结束,注意观察监控窗口是否>僵尸进程被回收\n", ret);sleep(2); //不让父进程那么快退出,观察窗口僵尸进程是否被回收return 0;
}
运行输出:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./wait
i am a child pid=10795
wait函数开始执行
wait返回的是子进程的ret=10795执行结束,注意观察监控窗口是否>僵尸进程被回收
waitpid()函数
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数:
pid
Pid=-1,等待任一个子进程。与wait等效
Pid>0,等待其进程ID与pid相等的子进程
status
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(查看进程的退出码)
options
参数为0:也就是阻塞
版本的等待,也就是说该waitpid在子进程没有退出情况下就不会返回,就和wait的使用一模一样,因为wait的使用就是阻塞版本的等待方式
参数为WNOHANG::这是一个宏,表示调用wait为非阻塞
的版本,非阻塞也就以为执行带waitpid函数会立即返回
而设置这个参数:返回情况有以下几种:
- 若pid指定的子进程没有结束,则waitpid()函数返回0,父进程不予以等待
- 若正常结束,则返回该子进程的ID
- 若等待失败,即返回-1,这时errno会被设置成相应的值以指示错误所在
示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;int main()
{pid_t pid = fork();if (pid == 0){// childint count = 10;while (count){printf("i am a child my pid = %d\n", getpid());cout << "count value:" << count << endl << endl;count--;sleep(1);}exit(11); //退出子进程,我们在父进程调用waitpid来获得子进程的退出码信息}// parent processint status; //该变量是父进程的变量,为的是在父进程获得子进程的退出状态的信息while (1){ //这个循环就是继续轮回检测的非阻塞版本的设计,假如子进程没退出,我们一直死循环检测知道直到它退出pid_t ret = waitpid(pid, &status, WNOHANG); // WNOHANG:表示父进程非阻塞方式等待子进程退出if (ret == 0){// ret == 0 表示waitpid等待成功,但是子进程还没有退出,waitpid返回0回到父进程的代码执行//做父进程的事情;printf("我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事\n");}else if (ret > 0){// waitpid 等待成功,子进程退出,父进程就可以获取子进程的信息printf("waitpid 返回的stauts 的退出码信息:%d,终止信号的信息:%d\n", (status >> 8) & 0xFF, status & 0x7F);break;}else{printf("waitpid is failed\n");break;}sleep(1); //让父进程每隔一秒去检测}return 0;
}
运行输出:
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./waitpid
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:10我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:9i am a child my pid = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:8我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:7i am a child my pid = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:6我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:5i am a child my pid = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:4我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:3i am a child my pid = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:2我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid = 10209
count value:1waitpid 返回的stauts 的退出码信息:11,终止信号的信息:0
💡 不可以简简单单的认为 stauts就是一个整形int,我们要把它为一个位图
进程终止信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作