本节目标:

  • 复制进程映像
  • fork系统调用
  • 孤儿进程、僵尸进程
  • 写时复制

一,进程复制(或产生)

     使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。

子进程与父进程的区别在于:

1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)

2、各自的进程ID和父进程ID不同

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

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

二,fork系统调用

包含头文件 <sys/types.h> 和 <unistd.h>

函数功能:创建一个子进程

函数原型

pid_t fork(void);  //一次调用两次返回值,是在各自的地址空间返回,意味着现在有两个基本一样的进程在执行

参数:无参数。

返回值:

  • 如果成功创建一个子进程,对于父进程来说返回子进程ID
  • 如果成功创建一个子进程,对于子进程来说返回值为0
  • 如果为-1表示创建失败

流程图:

父进程调用fork()系统调用,然后陷入内核,进行进程复制,如果成功:

1,则对调用进程即父进程来说返回值为刚产生的子进程pid,因为进程PCB没有子进程信息,父进程只能通过这样获得。

2,对子进程(刚产生的新进程),则返回0,

这时就有两个进程在接着向下执行

如果失败,则返回0,调用进程继续向下执行

注:fork英文意思:分支,fork系统调用复制产生的子进程与父进程(调用进程)基本一样:代码段+数据段+堆栈段+PCB,当前的运行环境基本一样,所以子进程在fork之后开始向下执行,而不会从头开始执行。

示例程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> #define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while (0)\ int main(void)
{
pid_t pid;
printf("before calling fork,calling process pid = %d\n",getpid());
pid = fork();
if(pid == -1)
ERR_EXIT("fork error");
if(pid == 0){
printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid());
}
if(pid > 0){
//sleep(1);
printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid);
} return 0;
}

运行结果:

当没给父进程没加sleep时,由于父进程先执行完,子进程成了孤儿进程,系统将其托孤给了1(init)进程,

所以ppid =1。

当加上sleep后,子进程先执行完:

这次可以正确看到想要的结果。

三,孤儿进程、僵尸进程

fork系统调用之后,父子进程将交替执行,执行顺序不定。

如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)

如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程(僵尸进程:只保留一些退出信息供父进程查询)

示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> #define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while (0)\ int main(void)
{
pid_t pid;
printf("before calling fork,calling process pid = %d\n",getpid());
pid = fork();
if(pid == -1)
ERR_EXIT("fork error");
if(pid == 0){
printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid());
}
if(pid > 0){
sleep(100);
printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid);
} return 0;
}

以上程序跟前面那个基本一致,就是让父进程睡眠100秒,好让子进程先退出

运行结果:

从上可以看到,子进程先退出,但进程列表中还可以查看到子进程,[a.out] <defunct>,死的意思,即僵尸进程,如果系统中存在过多的僵尸进程,将会使得新的进程不能产生。

 

四,写时复制

linux系统为了提高系统性能和资源利用率,在fork出一个新进程时,系统并没有真正复制一个副本。

如果多个进程要读取它们自己的那部分资源的副本,那么复制是不必要的。

每个进程只要保存一个指向这个资源的指针就可以了。

如果一个进程要修改自己的那份资源的“副本”,那么就会复制那份资源。这就是写时复制的含义

fork 和vfork:

在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。

vfork有个限制,子进程必须立刻执行_exit或者exec函数。

即使fork实现了copy on write,效率也没有vfork高,但是我们不推荐使用vfork,因为几乎每一个vfork的实现,都或多或少存在一定的问题

vfork:

Linux Description

    vfork(), just like fork(2), creates a child process of the calling pro-

    cess.  For details and return value and errors, see fork(2).

    vfork()  is  a special case of clone(2).  It is used to create new pro-

    cesses without copying the page tables of the parent process.   It  may

    be  useful  in performance-sensitive applications where a child will be

    created which then immediately issues an execve(2).

    vfork() differs from fork(2) in that the parent is suspended until  the

    child  terminates (either normally, by calling _exit(2), or abnormally,

    after delivery of a fatal signal), or it makes  a  call  to  execve(2).

    Until  that point, the child shares all memory with its parent, includ-

    ing the stack. The child must not return from the current function  or

    call exit(3), but may call _exit(2).

    Signal  handlers  are inherited, but not shared.  Signals to the parent

    arrive after the child releases the parent’s memory  (i.e.,  after  the

    child terminates or calls execve(2)).

 

示例程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> #define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while (0)\ int main(void)
{
pid_t pid;
int val = 1;
printf("before calling fork, val = %d\n",val); //pid = fork();
pid = vfork();
if(pid == -1)
ERR_EXIT("fork error");
if(pid == 0){
printf("chile process,before change val, val = %d\n",val);
val++;
//sleep(1);
printf("this is child process and val = %d\n",val);
_exit(0); }
if(pid > 0){
sleep(1);
//val++;
printf("this is parent process and val = %d\n",val);
} return 0;
}

当调用fork时:

运行结果:

可知写时复制

当使用vfork但子进程没使用exit退出时:

结果出错了,

使用vfork且exit退出:

结果正常,父子进程共享

 

fork之后父子进程共享文件:

 

fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件文件偏移指针

示例程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> #define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while (0)\ int main(void)
{
pid_t pid;
int fd;
fd = open("test.txt",O_WRONLY);
if(fd == -1)
ERR_EXIT("OPEN ERROR");
pid = fork();
if(pid == -1)
ERR_EXIT("fork error");
if(pid == 0){
write(fd,"child",5);
}
if(pid > 0){
//sleep(1);
write(fd,"parent",6);
} return 0;
}

运行结果:

可知父子进程共享文件偏移指针,父进程写完后文件偏移到parent后子进程开始接着写。

linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程的更多相关文章

  1. linux系统编程之管道(三)

    今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...

  2. Linux系统编程--文件描述符的复制dup()和dup2()【转】

    本文转载自:http://blog.csdn.net/tennysonsky/article/details/45870459 dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个 ...

  3. linux系统编程之信号(三)

    今天继续对信号进行研究,话不多说,言归正传: 更多信号发送函数: 上节中我们已经接触到了一些信号的发送函数,这里更进一步学习一下其它的发送函数: alarm:只能发送SIGALRM信号 下面通过一个例 ...

  4. linux系统编程之进程(一)

    今天起,开始学习linux系统编程中的另一个新的知识点----进程,在学习进程之前,有很多关于进程的概念需要了解,但是,概念是很枯燥的,也是让人很容易迷糊的,所以,先抛开这些抽象的概念,以实际编码来熟 ...

  5. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  6. linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid

    本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ...

  7. linux系统编程-进程

    进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...

  8. Linux系统编程——特殊进程之僵尸进程

    僵尸进程(Zombie Process) 进程已执行结束,但进程的占用的资源未被回收.这种进程称为僵尸进程. 在每一个进程退出的时候,内核释放该进程全部的资源.包含打开的文件.占用的内存等. 可是仍然 ...

  9. linux shell编程,先等10秒再判断是否有进程存在,存在就再等10秒再杀了进程才运行

    linux shell编程,先等10秒再判断是否有进程存在,存在就再等10秒再杀了进程才运行 crontab每分钟执行一次,但5秒以上才有更新数据,有时候一分钟可能跑不完上一个进程,需要先等10秒再判 ...

  10. Linux 系统编程

    简介和主要概念 Linux 系统编程最突出的特点是要求系统程序员对它们工作的的系统的硬件和操作系统有深入和全面的了解,当然它们还有库和系统调用上的区别. 系统编程分为:驱动编程.用户空间编程和网络编程 ...

随机推荐

  1. 使用spring-data-JPA调用存储过程

    第一种情况,对于只有一个或没有返回值的存储过程,使用如下方式调用: @Entity @NamedStoredProcedureQuery(name = "pro1", proced ...

  2. 关于mybatis缓存配置详解

    一级缓存: 一级缓存是默认的. 测试:在WEB页面同一个查询执行两次从日志里面看同样的sql查询执行两次. 2次sql查询,看似我们使用了同一个sqlSession,但是实际上因为我们的dao继承了S ...

  3. 过滤输入htmlentities与htmlspecialchars用法

    过滤输入 (即来自所列数据源中的任何数据)是指,转义或删除不安全的字符.在数据到达应用的存储层之前,一定要过滤输入数据.这是第一道防线.假如网站的评论表单接收html,默认情况下 访客可以毫无阻拦地在 ...

  4. HHvm建站环境搭建方法:Nginx,Mariadb,hhvm及lnmp/lamp安装部署

    HHVM起源于Facebook公司,是一个开源的PHP虚拟机,使用JIT的编译方式以及其他技术,让PHP代码的执行性能大幅提升.HHVM提升PHP性能的途径,采用的方式就是替代Zend引擎来生成和执行 ...

  5. 《Ubuntu标准教程》学习总结

    第6章 Shell Shell就是一个命令解释器,负责完成用户与内核之间的交互. 目前流行电Shell主要有:Bourne Shell( sh ).Bourne Again Shell( Bash ) ...

  6. crontab定时任务操作

    一.查看定时任务 crontab -l 二.添加定时任务 crontab -e (一)执行外部链接 //每隔10分钟执行1次 */ * * * * /usr/bin/curl "http:/ ...

  7. Netty系列(四)TCP拆包和粘包

    Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...

  8. httpclient之基本类

    HttpHost类  主机类  主要属性有域名和端口. HttpRoute类  路由类  主要属性有targetHost(目标主要).proxyChain[]代理链 RouteTracker类  和H ...

  9. ardunio 实现RS485通讯-下位机

    #include <SoftwareSerial.h> SoftwareSerial mySerial(,); byte ZERO=0x00; byte Addr=0x03; byte S ...

  10. 《Linux多线程服务端编程——使用muduo C++网络库》读书笔记

    第一章 线程安全的对象生命期管理 第二章 线程同步精要 第三章 多线程服务器的适用场合与常用编程模型 第四章 C++多线程系统编程精要 1.(P84)11个常用的最基本Pthreads函数: 2个:线 ...