一、进程创建(PCB、虚拟地址空间、页表、数据(映射关系加载好))
1、fork
fork可以从已有的进程中创建出一个新进程也叫作子进程,原来的那个进程就叫做父进程。一般,fork创建出子进程后,子进程一般是和父进程代码实现共享,数据实现写时拷贝的。
进程调用fork,当控制转移到内核中的fork代码后,内核会(1)将分配新的内存块和内核数据结构给子进程。(2)将父进程部分数据结构拷贝至子进程。(3)添加子进程到系统进程列表当中。(4)fork返回,开始调度器调度
fork的返回值:(这个我有在前面的博客中具体写过原因,可以戳此链接:https://blog.csdn.net/apt1203jn/article/details/79779888)
(1)子进程返回0;(2)父进程返回子进程的pidfork的常规用法:(1)一个父进程希望复制自己,使得父子进程同时执行不同的代码段(eg:父进程等待客户端请求,创建子进程来处理请求)。(2)一个进程要执行一个不同的程序(eg:子进程从fork返回之后,调用exec函数 )
调用fork失败的原因:(1)内存不够(2)实际用户的进程数超过了限制
2、vfork
vfork()创建子进程的特点:
* 子进程和父进程共享地址空间(fork的子进程具有独立的地址空间)
* vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行
测试用例:
(1)验证一下vfork()创建出的子进程会先于它的父进程调度运行(让子进程sleep一下使它的生命周期变长)
先写Makefile
.OPHNY:test
test:test.c
gcc -o test test.c
.OPHNY:clean
clean:
rm -f test
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
//pid_t id = fork();
pid_t id = vfork();
if (id == 0){
printf("child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), get ppid(), g_val, &g_val);
sleep(3);
exit(1);
}
else if (id > 0){
printf("father, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), ge tppid(), g_val, &g_val);
exit(2);
}
return 0;
}
先来看一下调用fork()创建子进程的运行结果
注释掉fork(),看一下vfork()创建子进程之后的调度顺序
运行结果:
由图可见,vfork() 创建的子进程会先运行,在子进程调用exit函数之后父进程才可能被调度运行。
大家可以自行验证一下,我没有开另外的终端检测,所以运行效果不是非常非常明显,大家自己动手验证一下哦!
(2)验证父进程和子进程共用一块地址空间
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
pid_t id = fork();
//pid_t id = vfork();
if (id == 0){
printf("befor:child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
g_val = 200;
printf("after:child, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
exit(1);
}
else if (id > 0){
printf("father, pid:%d, ppid:%d, g_val:%d,&g_val:%p\n", getpid(), ge tppid(), g_val, &g_val);
}
return 0;
}
注:Makefile文件同上一个
下面是用fork()创建子进程的运行结果
于此可以看出,fork()创建出的子进程并没有直接改变父进程的变量值,那么vfork()创建出的子进程会不会改变父进程的变量值嘞?
下面放开vfork() 的注释,看一下运行结果
解析:
上图结果可见子进程直接改变了父进程的变量值,这主要是因为子进程在父进程的地址空间中运行。