(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系统调用(转载)的更多相关文章
-
一个fork()系统调用的问题
转载:http://coolshell.cn/articles/7965.html 题目:请问下面的程序一共输出多少个“-”? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
-
《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程
一.进程控制块PCB-stack_struct 进程在操作系统中都有一个结构,用于表示这个进程.这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息 ...
-
以python代码解释fork系统调用
import os print('Process (%s) start...' % os.getpid()) # Only works on Unix/Linux/Mac: pid = os.fork ...
-
fork 系统调用
对自己知识储备的感觉就是过于肤浅,很多东西知其名后就不了了之 此系列博客将记录进程分析的学习过程,希望能够多些深度 提到进程,最容易的想到就是fork系统调用,比较好和快速的找到的fork的相关信息就 ...
-
fork系统调用方式成为负担,需要淘汰
微软研究人员发表论文称用于创建进程的 fork 系统调用方式已经很落后,并且对操作系统的研究与发展产生了极大的负面影响,需要淘汰,作者同时提出了替代方案.相信每位开发者都对操作系统中的 fork () ...
-
fork()系统调用的理解
系统调用fork()用于创建一个新进程.我们可以通过下面的代码来理解,最好是能自己敲一遍运行验证. #include<stdio.h> #include<stdlib.h> ...
-
用 set follow-fork-mode child即可。这是一个 gdb 命令,其目的是告诉 gdb 在目标应用调用fork之后接着调试子进程而不是父进程,因为在 Linux 中fork系统调用成功会返回两次,一次在父进程,一次在子进程
GDB的那些奇淫技巧 evilpan 收录于 Security 2020-09-13 约 5433 字 预计阅读 11 分钟 709 次阅读 gdb也用了好几年了,虽然称不上骨灰级玩家,但 ...
-
glibc中fork系统调用传参
因为想跟踪下在新建进程时,如何处理新建进程的vruntime,所以跟踪了下fork. 以glic-2.17中ARM为例(unicore架构的没找到),实际上通过寄存器向系统调用传递的参数为: r7: ...
-
linux fork()函数 转载~~~~
转自 :: http://blog.csdn.net/jason314/article/details/5640969 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork ...
随机推荐
-
jquery工具方法parseJSON
error : 自定义错误 parseJSON : 字符串转json trim : 去除字符串头尾空字符 parseJSON方法先判断参数是否为字符串,否则返回空对象,再去除字符串头尾空字符,判断是否 ...
-
django foreign key 自动加_id问题
解决:http://*.com/questions/8223519/preventing-django-from-appending-id-to-a-foreign-key-f ...
-
WPF 得到子指定元素方法和得到指定子元素集合方法MvvM得到焦点
public class UIHelper { /// <summary> /// 在Visual里找到想要的元素 /// childName可为空,不为空就按名字找 /// </s ...
-
转:Scrapy安装、爬虫入门教程、爬虫实例(豆瓣电影爬虫)
Scrapy在window上的安装教程见下面的链接:Scrapy安装教程 上述安装教程已实践,可行.(本来打算在ubuntu上安装Scrapy的,但是Ubuntu 磁盘空间太少了,还没扩展磁盘空间,所 ...
-
android休眠唤醒流程2
android系统一段时间没有操作, 屏幕(screen)将从高亮(bright)变为暗淡(dim),如果再过段时间还是没有操作,屏幕(screen)从暗淡(dim)变为关闭(off).这时,系 ...
-
网址测速JS
/*.route_nav li a:hover{background: #3c7f84 url(title.png) no-repeat;border-color:#84a3a5;}*/ .route ...
-
decimal ? 含义
例如: decimal ? je = zfje; 意思是 将 JE赋值为 ZFJE , 并且允许 JE 为 NULL 值 这时JE为引用类型
-
JavaScript中JSON字符串和JSON对象相互转化
JSON字符串转化为JSON对象的2种方式 一.使用函数eval var personsstr = '[{"Name":"zhangsan","Age ...
-
201521123044 《Java程序设计》第9周学习总结
1. 本章学习总结 2. 书面作业 本次PTA作业题集异常 1.常用异常题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免 ...
-
.Net 内存对象分析
在生产环境中,通过运行日志我们会发现一些异常问题,此时,我们不能直接拿VS远程到服务器上调试,同时日志输出的信息无法百分百反映内存中对象的状态,比如说我们想查看进程中所有的Socket连接状态.服务路 ...