当前位置: 首页 > news >正文

Linux--fork

一、fork入门知识

  fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。可以简单地说fork()的作用就是创建一个子进程。

  一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

什么是进程

1)进程是动态的,程序是静态的

当一个程序调用的时候,就创建了一个进程;进程在运行的时候是具有独立性的,不影响其他进程

2)进程=内核数据结构+进程的代码和数据

代码:只读

数据:当有一个执行流尝试修改数据的时候,OS会自动给我们当前进程触发写时拷贝
写时拷贝(copy-on-write, COW)就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。

 这里引入一个概念叫做PCB:进程控制块。也就是linux自己创建的Task_struct结构体。进程控制块是系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。

子进程完全拷贝父进程的PCB,但并不是同一个;父子进程代码共享,数据独有;同一个变量在父子进程>    的地址完全一样,OS中虚拟内存机制保证父子进程运行独立互不干扰

创建一个子进程实际上是创建了一个PCB,父进程与子进程看到的是同一份代码和数据

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

fork出错可能有两种原因:

1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。

二、fork进阶知识

以下面的代码为例,使用<unistd.h>头文件,调用fork()函数。其中getpid()函数的作用是获取进程id,getppid()函数的作用是获取父进程id

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("AAAAA\n");fork();printf("BBBBBBBBB   pid:%d   ppid:%d\n",getpid(),getppid());                              sleep(1);return 0;
}

运行后的结果是

 

可以看出AAAAA只打印了一行,因为此时并没有执行fork()函数,只有初始执行的代码。之后通过fork()函数创建了一个子进程,因此BBBBBBBB打印了两次。

第一次打印B的时候,pid:25251 ppid:25728。而第二次打印B的时候,pid:28252 ppid:25251父进程的进程id与第一次打印的进程id相同。

因此可以说明fork()函数创建了一个id为28252的子进程,它的父进程id为28251

下面来考虑循环中使用fork()的效果

#include <unistd.h>
#include <stdio.h>int main(void)
{int i = 0;printf("i son/pa ppid pid  fpid/n");//ppid指当前进程的父进程pid//pid指当前进程的pid,//fpid指fork返回给当前进程的值for(i = 0; i < 2; i++){pid_t fpid = fork();if(fpid == 0)printf("%d child  %4d %4d %4d/n", i, getppid(), getpid(), fpid);elseprintf("%d parent %4d %4d %4d/n", i, getppid(), getpid(), fpid);}return 0;
}

 运行结果是:

i son/pa ppid pid  fpid
0 parent 2043 3224 3225
0 child  3224 3225    0
1 parent 2043 3224 3226
1 parent 3224 3225 3227
1 child     1 3227    0
1 child     1 3226    0

第一步:在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。我们用一个链表来表示这个关系:

p2043->p3224->p3225

第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id)

p3225(子进程)的变量为i=0,fpid=0(fork函数在子进程中返回0)

第二步:假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226

对于此时的父进程p2043->p3224(当前进程)->p3226(被创建的子进程)

对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227

p3224->p3225(当前进程)->p3227(被创建的子进程)

从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的

第三步:第二步创建了两个进程p3226,p3227,这两个进程执行完printf函数后就结束了,因为这两个进程无法进入第三次循环,无法fork,该执行return 0;了,其他进程也是如此。

细心的读者可能注意到p3226,p3227的父进程难道不该是p3224和p3225吗,怎么会是1呢?这里得讲到进程的创建和死亡的过程,在p3224和p3225执行完第二个循环后,main函数就该退出了,也即进程该死亡了,因为它已经做完所有事情了。p3224和p3225死亡后,p3226,p3227就没有父进程了,这在操作系统是不被允许的,所以p3226,p3227的父进程就被置为p1了,p1是永远不会死亡的

 对于这种N次循环的情况,执行printf函数的次数为2*(1+2+4+……+2N-1)次,创建的子进程数为1+2+4+……+2N-1个。

printf的缓冲机制:printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上。

但是,只要看到有/n 则会立即刷新stdout,因此就马上能够打印了

http://www.lryc.cn/news/917.html

相关文章:

  • 计算机组成原理(一)
  • 【SpringBoot】实现Async异步任务
  • Node =>Express学习
  • QT基础入门【布局篇】消除控件之间的间隔
  • vue脚手架 element-ui spring boot 实现图片上传阿里云 并保存到数据库
  • 【FPGA】Verilog:组合电路 | 3—8译码器 | 编码器 | 74LS148
  • GLP-1类药物研发进展-销售数据-上市药品前景分析
  • C++远程监控系统接收端- RevPlayMDIChildWnd.cpp
  • QT之OpenGL深度测试
  • 用LCR测试仪测试无线充电系统中的线圈
  • 华为、南卡和漫步者蓝牙耳机怎么选?国产高性价比蓝牙耳机推荐
  • MySQl学习(从入门到精通12)
  • 08讲 | 基于STM32单片机NBIOT定位实战项目
  • 提取接近竖直物体(粗定位)
  • 程序环境和预处理
  • 财报解读:业务复苏迹象明显,中国中免能否重写增长神话?
  • macOS中虚拟机桥接模式分配静态ip
  • prometheus increase函数统计得到小数
  • C++学习记录——유 类和对象(3)
  • 基于Hi3861平台的OpenHarmony程序是如何启动运行的
  • 2023彻底解决Typora使用iPic微博图床失效问题
  • Revit中添加水平仰视平面图及水平剖面
  • Python 循环语句
  • 使用 ThreeJS 实现第一个三维场景(详)
  • 《小猫猫大课堂》三轮5——动态内存管理(通讯录动态内存化)
  • 【Selenium学习】Selenium 八大定位法
  • 算法训练营 day41 贪心算法 单调递增的数字 买卖股票的最佳时机含手续费
  • 【数据结构-JAVA】排序
  • 基于注解管理Bean
  • Containerd 的 Bug 导致容器被重建!如何避免?