(1) fork系统调用说明

fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程。fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返回值是子进程的进程号,而子进程中的返回值则返回 0。因此,可以通过返回值来判定该进程是父进程还是子进程。

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等,而子进程所独有的只有它的进程号、计时器等。因此可以看出,使用fork系统调用的代价是很大的,它复制了父进程中的数据段和堆栈段里的绝大部分内容,使得fork系统调用的执行速度并不很快。

fork的返回值这样设计是有原因的,fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程ID,也可以调用getppid函数得到父进程的进程ID。在父进程中使用getpid函数可以得到自己的进程ID,然而要想得到子进程的进程ID,只有将fork的返回值记录下来,别无它法。

fork的另一个特性是所有由父进程打开的文件描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。

由于代码段(加载到内存的执行码)在内存中是只读的,所以父子进程可共用代码段,而数据段和堆栈段子进程则完全从父进程复制拷贝了一份。

2)父进程进行fork系统调用时完成的操作

假设id=fork(),父进程进行fork系统调用时,fork所做工作如下:

①    为新进程分配task_struct任务结构体内存空间。

②    把父进程task_struct任务结构体复制到子进程task_struct任务结构体。

③    为新进程在其内存上建立内核堆栈。

④    对子进程task_struct任务结构体中部分变量进行初始化设置。

⑤    把父进程的有关信息复制给子进程,建立共享关系。

⑥    把子进程加入到可运行队列中。

⑦    结束fork()函数,返回子进程ID值给父进程中栈段变量id。

⑧    当子进程开始运行时,操作系统返回0给子进程中栈段变量id。

(3)fork调用时所发生的事情

下面代码是fork函数调用模板,fork函数调用后常与if-else语句结合使用使父子进程执行不同的流程。假设下面代码执行时产生的是X进程,fork后产生子进程的是XX进程,XX进程的进程ID号为1000。

int pid ;

pid = fork();

if (pid < 0) {

perror("fork failed");

exit(1);

}

if (pid == 0) {

message = "This is the child/n";

调用fork前,内存中只有X进程,如图12-9所示,此时XX进程还没“出生”。

图12-9 fork前的内存

调用fork后,内存中不仅有X进程(父进程),还有XX进程(子进程)。fork的时候,系统几乎把父进程整个堆栈段(除代码段,代码段父子进程是共享的)复制给了子进程,复制完成后,X进程和XX进程是两个独立的进程,

如下图12-10所示,只不过X进程栈中变量id值此时为1000,而XX进程栈中变量id值为0。fork调用完成后,X进程由系统态回到用户态。此后,X进程和XX进程各自都需要从自己代码段指针指向的代码点继续往下执行,父进程X往下执行时判断id大于0,所以执行大于0的程序段,而子进程XX往下执行时判断id等于0,所以执行等于0的程序段。

图12-10 fork后的内存

4)fork 函数原型

所需头文件

#include <sys/types.h>   // 提供类型 pid_t 的定义

#include <unistd.h>

函数说明

建立一个新的进程

函数原型

pid_t fork(void)

函数返回值

0:返回给子进程

子进程的ID(大于0的整数):返回给父进程

-1:出错,返回给父进程,错误原因存于errno中

错误代码

EAGAIN:内存不足

ENOMEM:内存不足,无法配置核心所需的数据结构空间

(5)fork函数使用实例

fork.c源代码如下:

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

pid_t pid;

char *message;

int n;

pid = fork();

if (pid < 0) {

perror("fork failed");

exit(-1);

}

if (pid == 0) {

message = "This is the child\n";

n = 3;

} else {

wait(0) ; /*阻塞等待子进程返回*/

message = "This is the parent\n";

n = 1;

}

for(; n > 0; n--) {

printf(message);

sleep(1);

}

return 0;

}

编译 gcc fork.c –o fork。

执行./fork,执行结果如下:

This is the child

This is the child

This is the child

This is the parent

读者可以把sleep(1)改成sleep(30),然后通过ps -ef|grep fork查看进程数。

(6)fork后程序处理的两种情形

一种为父进程希望复制自己,使父、子进程同时执行不同的代码段。这是网络并发服务端常见的模型,父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,让子进程处理此请求,父进程则继续等待下一个服务请求。

另一种为fork后通过exec执行另一个程序,在终端上执行命令属于这种情况,Shell进程fork后立即调用exec去执行执行命令。

(7)fork之后处理文件描述符有两种常见情况

父进程等待子进程完成。在这种情况下,父进程无需对其文件描述符做任何处理,当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件位移量已做了相应更新。

父、子进程各自执行不同的程序段。在这种情况下,在fork之后,父、子进程各自关闭它们不需使用的文件描述符,并且不干扰对方使用的文件描述符。这种方式在并发网络服务器中经常使用到。

8)除了打开文件之外,很多父进程的其他性质也由子进程继承

Ÿ      实际用户ID、实际组ID、有效用户ID、有效组ID;

Ÿ      附加组ID;

Ÿ      进程组ID;

Ÿ      会话ID;

Ÿ      控制终端;

Ÿ      设置-用户-ID标志和设置-组-ID标志;

Ÿ      当前工作目录;

Ÿ      根目录;

Ÿ      文件权限屏蔽字;

Ÿ      信号屏蔽和排列;

Ÿ      打开的文件描述符;

Ÿ      环境变量;

Ÿ      连接的共享存储段;

Ÿ      数据段、代码段、堆段、.bss段;

Ÿ      资源限制。

(9)父、子进程之间有如下区别

Ÿ      fork的返回值;

Ÿ      进程ID;

Ÿ      不同的父进程ID;

Ÿ      子进程的tms_utime、tms_stime、tms_cutime以及tms_ustime设置为0;

Ÿ      父进程设置的锁,子进程不继承;

Ÿ      未处理的闹钟信号子进程将清除;

Ÿ      子进程的未决告警被清除;

Ÿ      子进程的未决信号集设置为空集。

(10)fork与vfork的区别

使用fork调用会为子进程复制父进程所拥有的资源(进程环境、栈堆等),而vfork设计时要求子进程立即调用exec,而不修改任何内存,vfork新建的子进程则是和父进程共享所有的资源,在子进程中对数据的修改也就是对父进程数据的修改,这一点一定要注意。

使用fork系统调用产生父子进程,在默认情况下无需人为干预,父子进程的执行顺序是由操作系统调度的,谁先执行并不确定。而对于vfork所生成的父子进程,父进程是在子进程调用了_exit或者exec后才会继续执行,不调用这两个函数父进程会等待(父进程由于没有收到子进程表示已执行的相关信号所以进行等待)。

vfork的出现是为了解决当初fork浪费用户空间内存的问题,因为在fork后,很有可能去执行exec函数重生,vfork设计思想就是取消fork造成堆栈的复制,使用vfork可以避免资源的浪费,但是也带了资源共享所产生的问题。

在Linux中,对fork进行了优化,调用时采用写时复制 (COW,copy on write)的方式,在系统调用fork生成子进程的时候,不马上为子进程复制父进程的资源,而是在遇到“写入”(对资源进行修改)操作时才复制资源。它使得一个普通的fork调用非常类似于vfork,但又避免了vfork的缺点,使得vfork变得没有必要。

fork系统调用(转载)的更多相关文章

  1. 一个fork()系统调用的问题

    转载:http://coolshell.cn/articles/7965.html 题目:请问下面的程序一共输出多少个“-”? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...

  2. 《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程

    一.进程控制块PCB-stack_struct 进程在操作系统中都有一个结构,用于表示这个进程.这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息 ...

  3. 以python代码解释fork系统调用

    import os print('Process (%s) start...' % os.getpid()) # Only works on Unix/Linux/Mac: pid = os.fork ...

  4. fork 系统调用

    对自己知识储备的感觉就是过于肤浅,很多东西知其名后就不了了之 此系列博客将记录进程分析的学习过程,希望能够多些深度 提到进程,最容易的想到就是fork系统调用,比较好和快速的找到的fork的相关信息就 ...

  5. fork系统调用方式成为负担,需要淘汰

    微软研究人员发表论文称用于创建进程的 fork 系统调用方式已经很落后,并且对操作系统的研究与发展产生了极大的负面影响,需要淘汰,作者同时提出了替代方案.相信每位开发者都对操作系统中的 fork () ...

  6. fork()系统调用的理解

    系统调用fork()用于创建一个新进程.我们可以通过下面的代码来理解,最好是能自己敲一遍运行验证. ​#include<stdio.h> #include<stdlib.h> ...

  7. 用 set follow-fork-mode child即可。这是一个 gdb 命令,其目的是告诉 gdb 在目标应用调用fork之后接着调试子进程而不是父进程,因为在 Linux 中fork系统调用成功会返回两次,一次在父进程,一次在子进程

    GDB的那些奇淫技巧 evilpan 收录于 Security  2020-09-13  约 5433 字   预计阅读 11 分钟  709 次阅读  gdb也用了好几年了,虽然称不上骨灰级玩家,但 ...

  8. glibc中fork系统调用传参

    因为想跟踪下在新建进程时,如何处理新建进程的vruntime,所以跟踪了下fork. 以glic-2.17中ARM为例(unicore架构的没找到),实际上通过寄存器向系统调用传递的参数为: r7: ...

  9. linux fork()函数 转载~~~~

    转自  ::  http://blog.csdn.net/jason314/article/details/5640969  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork ...

随机推荐

  1. ASP.NET关于Login控件使用 (转)

    分类: C# 2011-02-21 10:38 4599人阅读 评论(0) 收藏 举报 loginasp.netstringurlserverbutton 今天上网找了一些关于Login控件的使用资料 ...

  2. 【Android Studio快捷键】之导入相应包声明(import packages)

    可能import 单个声明的快捷键大家都很容易找到,Alt+Enter.但是如果我要一次性import文件中所有的声明,这个快捷键是什么呢,找啊找的,就是没找到,以前在Eclipse是Ctrl+1,但 ...

  3. CXF入门例子

    1. WebService实现类:@WebService注解表示这个类发布为一个WebService服务. package com.coshaho.learn.cxf; import javax.jw ...

  4. 使用Aspose.Cell控件实现Excel高难度报表的生成(一)

    时光飞逝,生活.工作.业余研究总是在不停忙碌着,转眼快到月底,该月的博客文章任务未完,停顿回忆一下,总结一些经验以及好的东西出来,大家一起分享一下.本文章主要介绍报表的生成,基于Aspose.Cell ...

  5. LeetCode Shortest Palindrome

    原题链接在这里:https://leetcode.com/problems/shortest-palindrome/ 题目: Given a string S, you are allowed to ...

  6. js的运算

    1.表达式 表达式是EMCAscript中的一个“短语”,解释器会通过计算把它转换成一个值.最简单的表达式是字面量或者变量名. 2.前置递增(++box)和后置递增(box++)的区别 看下面一段代码 ...

  7. Java Map 按Key排序和按Value排序

    Map排序的方式有很多种,这里记录下自己总结的两种比较常用的方式:按键排序(sort by key), 按值排序(sort by value). 1.按键排序 jdk内置的java.util包下的Tr ...

  8. centOS中wget的使用方法

    对于 Linux 用户来说,几乎每天都在使用它. 下面为大家介绍几个有用的 CentOS wget 小技巧,可以让你更加高效而灵活的使用CentOS wget. CentOS wget 使用技巧 $ ...

  9. Spring Boot 1 创建Demo

    Spring Boot的主要优点: 为所有Spring开发者更快的入门 开箱即用,提供各种默认配置来简化项目配置 内嵌式容器简化Web项目 没有冗余代码生成和XML配置的要求 入门操作: 1.打开ht ...

  10. eclipse中运行Selenium遇到的问题

    1.   java.lang.NoClassDefFoundError: 解决方法:eclipse的java工程中导入selenium-java-2.44.0\selenium-2.44.0\libs ...