源地址:http://blog.chinaunix.net/uid-23037385-id-2565472.html

fork()子进程创建

在 UNIX 系统中,用户创建一个新进程的唯一方法就是调用系统调用 fork。调 用 fork 的进程称为父进程,而新创建的进程叫做子进程。系统 调用的语法格式:

	pid = fork();

在从系统调用 fork 中返回时,两个进程除了返回值 pid 不同外,具有 完全一样的用户级上下文。在子进程中,pid 的值为零。在系统启动时由核心内 部地创建的进程0是唯一不通过系统调用 fork 而创建的进程。

核心为系统调用 fork 完成下列操作:

  1. 为新进程在进程表中分配一个空项。
  2. 为子进程赋一个唯一的进程标识号 (PID)。
  3. 做一个父进程上下文的逻辑副本。由于进程的某些部分,如正文区,可能被几个 进程所共享,所以核心有时只要增加某个区的引用数即可,而不是真的将该区拷贝到一个 新的内存物理区。
  4. 增加与该进程相关联的文件表和索引节点表的引用数。
  5. 对父进程返回子进程的进程号,对子进程返回零。

理解系统调用 fork 的实现是十分重要的,因为子进程就象从天而降一样地开始 它的执行序列。

下面是系统调用 fork 的算法。核心首先确信有足够的资源来成功完成 fork。 如果资源不满足要求,则系统调用 fork 失败。如果资源满足要求,核心在进程 表中找一个空项,并开始构造子进程的上下文。

算法:fork
输入:无
输出:对父进程是子进程的 PID
对子进程是0
{
检查可用的核心资源
取一个空闲的进程表项和唯一的 PID 号
检查用户没有过多的运行进程
将子进程的状态设置为“创建”状态
将父进程的进程表中的数据拷贝到子进程表中
当前目录的索引节点和改变的根目录(如果可以)的引用数加1
文件表中的打开文件的引用数加1
在内存中作父进程上下文的拷贝
在子进程的系统级上下文中压入虚设系统级上下文层
/* 虚设上下文层中含有使子进程能
* 识别自己的数据,并使子进程被调度时
* 从这里开始运行
*/
if (正在执行的进程是父进程) {
将子进程的状态设置为“就绪”状态
return (子进程的 PID) // 从系统到用户
}
else {
初始化计时区
return 0;
}
}

我们来看看下面的例子。该程序说明的是经过系统调用 fork 之后,对文件的 共享存取。用户调用该程序时应有两个参数,一个是已经有的文件名,另外一个是要 创建的新文件名。该进程打开已有的文件,创建一个新文件,然后,假定没有遇见过 错误,它调用 fork 来创建一个子进程。子进程可以通过使用相同的文件描述 符而继承地存取父进程的文件(即父进程已经打开和创建的文件)。

当然,父进程和子进程要分别独立地调用 rdwrt 函数,并执行一个循环,即从 源文件中读一个字节,然后写一个字节到目标文件中区。当系统调用 read 遇见 文件尾时,函数 rdwrt 立即返回。

#include <fcntl.h>

int	fdrd, fdwt;
char c; main(int argc, char *argv[])
{
if (argc != 3) {
exit(1);
}
if ((fdrd = open(argv[1], O_RDONLY)) == -1) {
exit(1);
}
if ((fdwt = creat(argv[2], 0666)) == -1) {
exit(1);
} fork();
// 两个进程执行同样的代码
rdwrt();
exit(0);
} rdwrt()
{
for (;;) {
if (read(fdrd, &c, 1) != 1) {
return ;
}
write(fdwt, &c, 1);
}
}

在这个例子中,两个进程的文件描述符都指向相同的文件表项。这两个进程永远 不会读或写到相同的文件偏移量,因为核心在每次 readwrite 调用 之后,都要增加文件的偏移量。尽管两个进程似乎是将源文件拷贝了两次,但因为 他们分担了工作任务,因此,目标文件的内容依赖于核心调度两个进程的次序。如果 核心这样调度两个进程:使他们交替地执行他们的系统调用,或甚至使他们交替地 执行每对 read 和 write 调用,则目标文件的内容和源文件的内容完全一致。但考虑 这样的情况:两个进程正要读源文件中的两个连续的字符 "ab"。假定父进程读了字 符 "a",这时,核心在父进程写之前,做了上下文切换来执行子进程。如果子进程 读到字符 "b",并在父进程被调度前,将它写到目标文件,那么目标文件将不再含有 字符串 "ab",而是含有 "ba"了。核心并不保证进程执行的相对速率。

再来看看另外一个例子:

#include <string.h>

char	string[] = "Hello, world";

main()
{
int count, i;
int to_par[2], to_chil[2]; // 到父、子进程的管道
char buf[256]; pipe(to_par);
pipe(to_chil); if (fork() == 0) {
// 子进程在此执行
close(0); // 关闭老的标准输入
dup(to_child[0]); // 将管道的读复制到标准输入
close(1); // 关闭老的标准输出
dup(to_par[1]); // 将管道的写复制到标准输出
close(to_par[1]); // 关闭不必要的管道描述符
close(to_chil[0]);
close(to_par[0]);
close(to_chil[1]);
for (;;) {
if ((count = read(0, buf, sizeof(buf)) == 0)
exit();
write(1, buf, count);
} } // 父进程在此执行
close(1); // 重新设置标准输入、输出
dup(to_chil[1]);
close(0);
dup(to_par[0]);
close(to_chil[1]);
close(to_par[0]);
close(to_chil[0]);
close(to_par[1]);
for (i = 0; i < 15; i++) {
write(1, string, strlen(string));
read(0, buf, sizeof(buf));
}
}

子进程从父进程继承了文件描述符0和1(标准输入和标准输出)。两次执行系统调用 pipe 分别在数组 to_par 和 to_chil 中分配了两个文件描述符。然后该进程 执行系统调用 fork,并复制进程上下文:象前一个例子一样,每个进程存取 自己的私有数据。父进程关闭他的标准输出文件(文件描述符1),并复制(dup)从管道 线 to_chil 返回的写文件描述符。因为在父进程文件描述符表中的第一个空槽是刚刚 由关闭腾出来的,所以核心将管道线写文件描述符复制到了文件描述符表中的第一 项中,这样,标准输出文件描述符变成了管道线 to_chil 的写文件描述符。 父进程以类似的操作将标准输入文件描述符替换为管道线 to_par 的读文件 描述符。与此类似,子进程关闭他的标准输入文件(文件描述符0),然后复制 (dup) 管道 线 to_chil 的读文件描述符。由于文件描述符表的第一个空项是原先的标准 输入项,所以子进程的标准输入变成了管道线 to_chil 的读文件描述符。 子进程做一组类似的操作使他的标准输出变成管道线 to_par 的写文件描述 符。然后两个进程关闭从 pipe 返回的文件描述符。上述操作的结果是:当 父进程向标准输出写东西的时候,他实际上是写向 to_chil--向子进程发送 数据,而子进程则从他的标准输入读管道线。当子进程向他的标准输出写的时候, 他实际上是写入 to_par--向父进程发送数据,而父进程则从他的标准输入 接收来自管道线的数据。两个进程通过两条管道线交换消息。

无论两个进程执行的顺序如何,这个程序执行的结果是不变的。他们可能去执行睡眠 和唤醒来等待对方。父进程在15次循环后退出。然后子进程因管道线没有写进程而读 到“文件尾”标志,并退出。

转:fork()子进程创建的更多相关文章

  1. Linux API fork 子进程 创建 & 无名管道通信

    #include<unistd.h> #include<stdio.h> int main() { ]; ]; pipe(filedes); printf("my p ...

  2. linux进程编程:子进程创建及执行函数简介

    linux进程编程:子进程创建及执行函数简介 子进程创建及执行函数有三个: (1)fork();(2)exec();(3)system();    下面分别做详细介绍.(1)fork()    函数定 ...

  3. fork子进程

    title: fork子进程 data: 2019/3/21 20:24:39 toc: true --- 这里实在学习socket编程前的小知识点,用来创建多个服务端 学习文档 函数可以有两个返回值 ...

  4. linux下c程序 daemon、fork与创建pthread的顺序问题

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/shuyun123456789/article/details/34418875 近期发如今写linu ...

  5. 通过fork函数创建进程的跟踪,分析linux内核进程的创建

    作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验过程 1.打开gdb, ...

  6. localtime死锁——多线程下fork子进程

    近期測试我们自己改进的redis,发如今做rdb时,子进程会一直hang住.gdb attach上.堆栈例如以下: (gdb) bt #0 0x0000003f6d4f805e in __lll_lo ...

  7. 缺陷的背后(四)---多进程之for循环下fork子进程引发bug

    导语 业务模块为实现高并发时的更快的处理速度,经常会采用多进程的方式去处理业务.多进程模式下常见的三种bug:for循环下fork子进程导致产生无数孙子进程,僵尸进程,接口窜包.本章主要介绍第一种常见 ...

  8. fork 子进程,父进程对于变量的共享

    经过代码的练习发现: fork创建的子进程会完全复制父进程的代码包括变量,既复制fork之前创建的变量. 但是在创建子进程后,子进程与父进程对同一个变量的改变将相互不受影响,即使获取同一变量的地址是一 ...

  9. fork函数创建新进程过程分析

    gdb调试执行流程,首先设置断点b sys_clone,当在shell下输入fork命令后,系统执行至断点,接下来按步执行: 判断是否被跟踪 判断是否被创建为轻量级进程(vfork) 判断父进程是否被 ...

随机推荐

  1. 如何打开rdb文件

    后缀名是RDB用什么软件打开不能用记事本打开后是乱码不知用什么软件写入的... RDB文件是QQ2009SP以后的替代DB文件的一种新的文件格式,是一种数据库文件请下载 百度搜索下载:rdb打包解包工 ...

  2. https 生成秘钥

    #生成一个RSA秘钥 openssl genrsa -des3 -out a_com.key 1024 #生成一个证书请求openssl req -new -key a_com.key -out a_ ...

  3. 使用gulp搭建less编译环境

    什么是less? 一种 动态 样式 语言. LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Fi ...

  4. VIM 代码自动补全, YouCompleteMe安装及配置

    效果 下载 使用Vundle安装 YCM 1. 安装Vundle window用户安装vundle参考这里:Windows下 vundle的安装和使用 2.

  5. LightOJ-1214-Large Division-大数取余

    Given two integers, a and b, you should check whether a is divisible by b or not. We know that an in ...

  6. 自己写一个依赖注入容器Container

    前言:在平时的写代码中为了解耦.方便扩展,经常使用一些DI容器(如:Autofac.Unity),那是特别的好用. 关于它的底层实现代码 大概是这样. 一.使用依赖注入的好处 关于使用依赖注入创建对象 ...

  7. 2_2.springboot2.x配置之自动配置原理

    前言 SpringBoot 自动配置原理: 本文主要分为三大部分: SpringBoot 源码常用注解 SpringBoot 启动过程 SpringBoot 自动配置原理 1. SpringBoot ...

  8. Mybatis Resultmap 简化之超级父类

    我们在写 mybatis多表关联查询的时候 ,要配置  resultmap ,实在太麻烦.而这个超级父类 可以省去我们查询多表时的map public class SuperPojo extends ...

  9. java_迭代器

    java的迭代器(Iterator): 一个可迭代的对象调用iterator可以得到一个迭代器对象 HasNext:判断是否还有下一个元素 next:返回迭代的元素 步骤: public static ...

  10. 设置Hadoop+Hbase集群pid文件存储位置

    有时候,我们对运行几天或者几个月的hadoop或者hbase集群做停止操作,会发现,停止命令不管用了,为什么呢? 因为基于java开发的程序,想要停止程序,必须通过进程pid来确定,而hadoop和h ...