fork系统调用(转载)

(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变得没有必要。

时间: 2024-09-30 06:17:47

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

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

转载:http://coolshell.cn/articles/7965.html 题目:请问下面的程序一共输出多少个“-”? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) {    int i;    for(i=0; i<2; i++){       fork();   

fork 系统调用的执行过程与调试

我们可以通过fork系统调用来处理进程创建的任务.对于进程的创建, 可以sys_clone, sys_vfork,以及sys_fork. 这些系统调用的内部都使用了do_fork.函数. 对于do_fork函数, 会copy tast_struct, 设置内核堆栈, 并且对一些特定的数据结构进行修改.其中里面还有copy_thread 函数, 会设置这个进程的cs和ip.这个是在进程的thread_info中保持的.这里的ip设置成了ret_from_fork函数(在ret_from_frok里

linux中fork()系统调用总结

由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id.将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id.对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid:也可以调用getppid()来获取父进程的id.(进程id 0总是由交换进程使用,所以一个子进程的进

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

一.进程控制块PCB-stack_struct 进程在操作系统中都有一个结构,用于表示这个进程.这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息: 状态信息,例如可执行状态.就绪状态.阻塞状态等. 性质,由于unix有很多变种,进行有自己独特的性质. 资源,资源的链接比如内存,还有资源的限制和权限等. 组织,例如按照家族关系建立起来的树(父进程.子进程等). task_struct结构体内容非常庞大,暂时没有去分析源代码,以后有时间再去研究

linux: fork系统调用实现剖析

首先了解一下程序和进程的概念: 程序:程序是完成特定任务的一系列指令集合. 进程:从用户角度来看,进程是程序的一次执行过程.从系统的角度看,进程是操作系统分配内存和cpu等资源的基本单位,进程是资源分配的最小单位.每一个进程都有自己独立的地址空间与执行状态,像unix这样的多任务操作系统能够让许多程序同时运行,每一个运行着的程序就构成了一个进程. 进程数据结构: 进程的静态描述:由pcb.有关程序段和该程序段所操作的数据结构集. 进程控制块:描述进程情况及控制运行所需要的全部信息 代码段:是进程

以python代码解释fork系统调用

import os print('Process (%s) start...' % os.getpid()) # Only works on Unix/Linux/Mac: pid = os.fork() print ("haha") if pid == 0: print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid())) else: print('I (%s) just creat

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

转自  ::  http://blog.csdn.net/jason314/article/details/5640969  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都 复制到新的新进程中,只有少数值与

vfork与fork(转载)

最近学了一些关于Unix/Linux下C编程的知识,大致了解了关于基于文件描述符的IO.基于流的IO.进程.进程间通信.信号.网络编程.gtk+编程,等等,以及其对应的相关API,看完后觉得也就这样,甚至有些傲慢,这些平日听起来高深的技术也就这些东西,但同时也感到疑惑:怎么可能,绝对不是我第一印象感觉的那样,绝对还有很多我不知道的. 幸好当天晚上没事逛了陈皓的coolshell,看到了一篇文章<vfork 挂掉的一个问题>,才感觉到自己的第一感觉是多么的无知,并不是它简单,而是因为难的.复杂的

fork系统调用

fork()学习, 理解 例1: (独立的上下文) 点击(此处)折叠或打开 #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #define ERROR(flag)                 \ if(flag)