【Linux】——进程创建fork()详解

【Linux】——进程创建fork()详解
⽂章⽬录
⼀、fork()作⽤
我们都知道fork可以⽤于进程的创建,那⾸先我们来了解⼀下fork函数原型和基本的两种⽤法才真正的了解我们何时才会⽤到fork来进⾏进程的创建
1、fork函数原型
fork()函数 需要引⼊头⽂件#include<unistd.h>,fork函数原型为:
pid_t fork(void)
参数含义:⽆参传⼊,返回pid_t类型的数值。pid_t 的实质是int类型的。
2、⽤法⼀
⼀个⽗进程希望复制⾃⼰,使⽗、⼦进程同时执⾏不同的代码段。
这在⽹络服务进程中是最常见的⼀种处理,⽗进程等待客户端的服务请求,当达到请求到达时,⽗进程调⽤fork,使⼦进程处理此请求。⽗进程则等待下⼀个服务请求到达。
3、⽤法⼆
旋转翼⼀个进程要执⾏⼀个不同的程序
这个⽤法对shell是常见的情况。在这种情况下,⼦进程从fork返回后⽴即调⽤exec。此概念我会在后续的博⽂中持续更新。
【注意】
在某些操作系统中会把fork和exec这两个操作组合成⼀个,并称其为spawn。
但是在UNIX系统中是将两个操作分开的,因为在很多场合需要单独使⽤fork,后⾯不跟随exec操作,使得⼦进程在这两个操作之间可以更改⾃⼰的属性,例如I/O重定向,⽤户ID、信号安排等等。
⼆、fork()特性
1、⽗⼦进程之间的关系载人行李箱
当我们执⾏fork函数时⽤fork()函数⽣成⼀个进程,调⽤fork函数的进程为⽗进程,新⽣成的进程为⼦进程,可以理解为复制⼀份⼀样的代码;
创建后⽗⼦进程是两个独⽴的进程,各⾃拥有⼀份代码。fork()⽅法调⽤之后,⽗,⼦进程都从fork()调⽤之后的代码开始运⾏。
他们的关系如下图所⽰:
【举个栗⼦】⽗⼦进程之间的关系验证
漏水探测器
具体代码实现如下:
int main()
{
pid_t n =fork();
assert(-1!= n);
if(0== n)
{
printf("Hello: mypid = %d,  myppid = %d\n",getpid(),getppid());
}
else
{
sleep(1);// 保证新进程先执⾏完
printf("World: n = %d, mypid = %d\n", n,getpid());
}
exit(0);
}
运⾏结果如下:
2、⽗⼦进程返回情况
fork函数被调⽤⼀次,但是返回两次
⼦进程返回的是0,⽽⽗进程返回值则是新⼦进程的进程ID。
【注意】⽗进程返回值是新进程的进程ID
因为⼀个进程的⼦进程可以有多个,并且没有⼀个函数是⼀个进程可以获得其所有⼦进程的进程ID。⽗进程中没有记录⼦进程的PID。所以为了⽅便⽗进程知道和处理⼦进程,fork()返回最新⼦进程的pid。
【举个栗⼦】下⾯这个程序,请问输出结果是什么?
#include<string.h>
int main()
{
pid_t n =fork();
assert(-1!= n);
if(0== n)
{
printf("Hello\n");
}黄鳝精
else
{
printf("World\n");
}
exit(0);
}
运⾏结果如下:
原因分析:这个结果就和fork的返回值有关,调⽤⼀次有两个返回结果,⾸先打印world,因为⼦进程返回的值是0,所以打印hello.
3、⽗⼦进程执⾏情况
fork之后是两个独⽴的进程,所以两个进程会被交给操作系统,然后操作系统进⾏调度,调度到谁就运⾏谁,故⽗⼦进程并发执⾏,不是按顺序的,⽗进程和⼦进程输出的结果是交替的,随机的。
如下图所⽰的程序:
# include<assert.h>
int main()
{
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)//⼦进程
{
printf("child:PPID=%d,PID=%d\n",getppid(),getpid());
int i=0;
for(;i<5;i++)
{
printf("child\n");
sleep(1);
}
}
else//⽗进程
{
printf("father:PPID=%d,PID=%d\n",getppid(),getpid());
int i=0;
for(;i<5;i++)
{
printf("father\n");
sleep(1);
}
}
exit(0);
}
根据上述的执⾏情况,该程序的运⾏结果是不定的,⽗⼦进程谁先执⾏谁后执⾏都是随机的,如下图所⽰为执⾏两次该程序的不同结果:
结果分析:
⽗⼦进程均从fork()之后的代码开始执⾏,也就是执⾏ if 判断。
⽗进程打印father,⼦进程打印child,我们可以看到输出的顺序不是固定的,⽽是随机的,每⼀次运⾏的结果是不⼀样的,这就是我们说的fork()之后⽗⼦进程并发执⾏。
4、⽗⼦进程的存储空间
我们已经知道了内存空间布局,主要由数据段,堆栈空间等组成,那么我们现在⽤代码来测试⼀下⽗⼦进程的内存空间:
思路:
代码定义全局初始化变量,局部初始化变量,动态开辟指针,那么它们分别在==.data段,栈,堆==存储。
⼦进程对这三个变量修改数值,⼦进程中输出数值和地址,⽗进程也输出三个变量的值和地址
我们⽤sleep来保证⽗进程在⼦进程修改数值之后执⾏,先让⽗进程睡眠,这样我们就可以根据⽗进程输出的数值判断⼦进程的修改是否影响⽗进程。
进⽽判断⽗⼦进程的三个变量是否存储在⼀块存储空间中。
代码实现如下:
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<unistd.h>
int gdata=10;//全局初始化
int main()
{
int ldata=10;
虚拟架子鼓
int*hdata=(int*)malloc(4);
*hdata=10;
pid_t pid=fork();
assert(pid!=-1);
//⼦进程
if(pid==0)
{
printf("child:%d,%d,%d\n",gdata,ldata,*hdata);
printf("child addr:0x%x,0x%x,0x%x\n",&gdata,&ldata,hdata);
gdata=20;
ldata=20;
*hdata=20;
printf("change num after:\n");
printf("child:%d,%d,%d\n",gdata,ldata,*hdata);
printf("child addr:0x%x,0x%x,0x%x\n",&gdata,&ldata,hdata);
sleep(3);
}
else
人防堵漏{
sleep(3);//保证⼦进程先修改了变量值
printf("father:%d,%d,%d\n",gdata,ldata,*hdata);
printf("father addr:0x%x,0x%x,0x%x\n",&gdata,&ldata,hdata);
}
free(hdata);
}
运⾏结果为:
仔细观察上⾯运⾏结果,我们可以发现:

本文发布于:2024-09-21 23:23:19,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/102281.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进程   变量   输出   结果   代码   函数   数值
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议