目录

一、写实拷贝

二、父子进程间数据共享

三、僵死进程


我们都知道fork之后,会产生子进程,那么今天就来看一下,产生的子进程与父进程之间的数据共享问题。要说这个我们必须明确写实拷贝的概念

一、写实拷贝

fork之后,子进程会拷贝父进程的PCB结构(这个拷贝是浅拷贝),然后对PCB里的数据做修改(pid,ppid等属性信息,也有保留的一些不做修改的)。我们现在假设一种情况,若fork之后,刚开始时就把父进程的所有页表直接拷贝给子进程,但子进程中并没有访问或修改这些页表,就要进程替换,岂不是白白做了一次复制(复制没有意义),所以,出现了写实拷贝技术,这样在刚开始时子进程并不是真正的复制父进程的空间,而是与父子进程共享所有的数据空间(全局、局部、堆区数据)。当父、子进程中任意一个进程试图去修改数据时,操作系统会将要修改的数据所在的页直接复制出来。可见写实拷贝技术是一种较高效的拷贝技术,省去了很多不必要的拷贝过程。

在这里,再提一下深拷贝和浅拷贝,二者仅在拷贝指针时存在区别

浅拷贝:只拷贝指针的值,而不拷贝指针所指向空间的内容

深拷贝:拷贝指针的值,同时也会将指针指向的内容拷贝一份

如图所示:

二、父子进程间数据共享

父子进程间对于全局数据、局部数据、堆区数据、文件描述符的共享问题?

这里先放上结论:父子进程间共享全局、局部、堆区数据,以及fork之前打开的文件描述符

下面一一进行举例说明:

1.对于全局和局部数据

代码:

运行结果:

结论:父子进程共享全局、局部数据(注意:程序打印出来的地址是虚拟地址)

2.堆区数据

用malloc申请堆区空间

代码:

运行结果:

结论:父子进程共享堆区数据

注意:malloc函数仅仅是开辟虚拟地址空间,真正开辟物理空间时是在程序中使用开辟的空间时

原因:已经运行测试,写一段父子进程的代码,用malloc开辟1G空间,在子进程中对开辟空间memset初始化for循环,运行代码测试,查看内核资源,发现程序运行起来后所占内存并不是直接就到1G,而是,慢慢逐渐升到1G,所以可验证这句话。

3.文件描述符

结论:父子进程共享fork()之前打开的文件描述符,其实共享的是读写偏移量。

运行测试:父子进程前打开一个文件open(“a.txt”),fork()产生子进程,,让子进程先读read(),然后打印,父进程再读read(),打印。假设a.txt中是“hello world”,则运行结果,子进程打印hello,父进程打”world”,说明二者共享fork之前打开的文件描述符。

理解:fork之后子进程拷贝父进程的PCB结构是浅拷贝,又由于PCB在内核里,所谓的每个进程的4G虚拟地址空间中的1G内核空间, 所有进程拥有一样的1G内核空间,即所有进程共享1G的内核空间,所以上图中浅拷贝之后,子进程PCB中的fd和父进程指的是同一个struct file,所以共享f_pose,读写偏移量。

内核空间没有虚拟地址,直接用物理地址,虚拟地址仅仅是操作系统为了提供安全控制而提供的一种虚拟的页面访问方式

三、僵死进程

1.什么是僵死进程?

当子进程结束,父进程未获取子进程的退出码时,这时子进程不得不保存退出码,所以子进程整个PCB无法释放。而这种PCB存在,进程主体释放了的进程就叫做僵死进程(zombie),有时也称僵尸进程。

孤儿进程:父进程关闭后子进程如果仍存在,此时子进程是孤儿进程,孤儿进程会被Init接管。

2.如何处理僵死进程?

2.1 关闭父进程(exit)

关闭父进程看似处理了僵死进程,实际上是父进程结束后僵死进程变为了孤儿进程,孤儿进程被Init接管,间接上是处理了僵死进程。

2.2 用信号处理僵死进程

(1)父进程要获取子进程退出码,通过下述两个函数:

a.     pid_t waitpid(pid_t pid,int statloc,int options);

返回值:

成功:处理的子进程的pid

出错:-1

参数statloc是一个整型指针。如果statloc不是 一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。

参数pid:

pid=-1——等待任意子进程。就这一方面而言,waitpid与wait等效

pid>0——等待其进程id与pid相等的子进程

pid==0——等待其组ID等于调用进程组ID的任一子进程

pid<-1——等待其组id等于pid绝对值的任一子进程

options参数使我们能进一步控制wa i tpid的操作。此参数可以是0,或者是表8-2中常量按位“或”运算的结果。

b.     pid_t wait(int *statloc);

返回值:

成功:处理的子进程的pid

出错:-1

参数:获取到的子进程的退出码,我们用的时候往往不关注它的退出码,所以直接传空。

wait()函数会阻塞运行,何为阻塞运行?阻塞运行就是当一个进程调用一个函数的时候,如果这个函数发现他所需条件未准备好,函数不会返回,直到条件满足才返回,从而造成进程的阻塞。与其对应的是非阻塞运行,非阻塞运行:函数调用无论条件是否满足,函数都会返回,条件不满足返回错误信息,满足了就返回正确信息

父进程调wait(),获取子进程的退出码时,若发现子进程未完成,wait()阻塞,此时父进程就会阻塞住,这时父子进程是一种串行的关系。一个wait(),只能处理一个子进程。

(2)wait()和waitpid()的比较

a. 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。

b. waitpid并不等待其凋用之后的第一个终止子迸程,witpid调用比wait调用灵活一些,可以用来等待指定的进程,可以使进程不挂起而立刻返回。

如果一个进程有几个子进程,那么只要有一个子进程终止,wait就返回。如果要等待一个指定的进程终止(如果知道要等待进程的ID),那么该如何做呢?在早期的UNIX版本中,必须调用wait,然后将其返回的进程ID和所期望的进程ID相比较。如果终止进程不是所期望的,则将该进程ID和终止状态保存起来,然后再次调用wait。反复这样做直到所期望的进程终止。下一次又想等待一个特定进程时,先查看已终止的进程列表,若其中已有要等待的进程,则取有关信息,否则调用wait。其实,我们需要的是等待一个特定进程的函数。POSIX.1定义了wai tpid函数以提供这种功能(以及其他一些功能)。

wai tpid函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由statloc指向的存储单元中。对于wait, 其唯一的出错是调用进程没有子进程( 函数调用被一个信号中断时,也可能返回另一种出错)。但是对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程则都将出错。(来自《UNIX环境高级编程》8.6)

(3)信号处理僵死进程

子进程结束后,父进程调用wait(),要wait()不阻塞,就得让父进程知道子进程结束了,那么父进程怎么知道子进程结束了,这需要子进程结束后告诉父进程,这就要用到信号了(因为子进程退出时会发送一个叫做SIGCHLD的信号)。

信号:操作系统预先定义好的某些特定事件,信号可以被产生,也可以被接收,产生和接收的主体都是进程

进程接收到信号后,怎么处理就要看——信号的响应方式:

默认(SIG_DFL)           忽略(SIG_IGN)           自定义

如何修改信号的响应方式:

typedef void(*Fun)(int);

Fun signal(int signum,Fun fun);//修改信号响应方式函数,这是一个注册函数

参数signum:信号类型(用信号值来代替的),信号值有

在/usr/include/bits/signum.h里定义了信号对应的值,常用的有两个:

SIGINT   2   键盘输入ctl+c会触发的一个信号

SIGCHLD    17   子进程状态被改变时触发的一个信号

这样父子进程就可以同时运行了

运行结果:

这个代码执行时,子进程结束后,父进程没有sleep(20),因为父进程接收到信号要处理就得醒来,一旦接受到信号,除了调用Fun函数,睡眠也会被唤醒。若硬要进行sleep(),则将sleep(),改为for()循环形式的,父进程就会sleep()了。

刚开始时子进程是运行状态,结束时状态会变为僵死状态,这时会触发SIGCHLD信号,父进程接受这个信号,父进程接收到这个信号的时候才去调用wait(),这时调用wait就不会阻塞了。这样就可以处理僵死进程了。

【Linux】写实拷贝、父子进程间数据共享以及僵死进程相关推荐

  1. Linux学习之系统编程篇:ps 和 kill 命令以及父子进程间数据共享模式

    一.ps 和 kill 命令 1.ps 命令 常用方式: ps aux :查看正在运行进程信息(主要查 pid). ps ajx :更加详细(PID. PPID:父进程 id. PGID:进程组 id ...

  2. 进程间数据传递:Queue,Pipe 进程间数据共享:Manager

    进程间数据传递:Queue,Pipe 进程间数据共享:Manager 1.使用multiprocessing模块的Queue实现数据传递 ''' 进程间通讯:Queue,用法跟线程里的Queue一样, ...

  3. Python之路(第三十九篇)管道、进程间数据共享Manager

    一.管道 概念 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信. 先画一幅图帮助大家理解下管道的基本原理 现有2个 ...

  4. python线程间数据共享_python 进程间数据共享multiProcess.Manger实现解析

    一.进程之间的数据共享 展望未来,基于消息传递的并发编程是大势所趋 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据. 这样极大地减少了对使用锁定和其他同步手段的需求, ...

  5. python manager 共享数据访问_python 进程间数据共享multiProcess.Manger实现解析

    一.进程之间的数据共享 展望未来,基于消息传递的并发编程是大势所趋 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据. 这样极大地减少了对使用锁定和其他同步手段的需求, ...

  6. Linux进程间关系之守护进程

    概念 守护进程也称精灵进程,是运行在后台的一种特殊进程.守护进程独立于控制终端并且周期性的执行某种任务或者等待处理某些打算的事件.可认为守护进程目的就是防止终端产生的一些信号让进程退出 特点 所有的守 ...

  7. VC 利用DLL共享区间在进程间共享数据及进程间广播消息

    在进程间共享数据有很多种方法,剪贴板,映射文件等都可以实现,这里介绍用 DLL 的共享区间在进程间共享数据,及共享数据有变化时及时的反馈给各相关进程. 一.在DLL中设置共享区间 在DLL中是用数据段 ...

  8. 进程间关系和守护进程

    一. 进程组/作业/会话 1.进程组     每一个进程除了有一个进程ID之外, 还属于一个进程组. 进程是一个或多个进程的集合. 通常, 它们与同一个作业向关联, 可以接收来自同一个终端下的各种命令 ...

  9. 父子进程终止顺序与僵死进程

    在Linux_父子进程与fork一文中,我们知道子进程是在父进程调用fork之后生成的.那么关于父子进程终止先后顺序又会有什么影响呢? 1.父进程在子进程之前终止 对于父进程已经终止的所有进程,它们的 ...

最新文章

  1. 图解 Git 工作原理
  2. php和mysql入门_PHP和MySQL入门(10)
  3. 大数据架构详解_【数据如何驱动增长】(3)大数据背景下的数仓建设 amp; 数据分层架构设计...
  4. python怎么反转单链表_单链表反转python实现代码示例
  5. Python入门--基本输入输出
  6. 认知行为技术是计算机技术吗,基于认知行为模型的多Agent建模技术研究与应用_问答库...
  7. Go程序:演示map用法
  8. eslint 设置目录_Nuxt项目添加自定义ESLint规则
  9. Linux32位ext4最大文件容量,linux – ext4文件系统最大inode限制 – 任何人都可以解释一下吗?...
  10. Python Tricks(十四)—— list 逆序的实现
  11. C++教程:C++开发语言可以做些什么?
  12. android 编译,gradle
  13. 最新 PMP 考试真题概要及答案分析(中文版)(1)
  14. ODB for mysql
  15. 【解决方案】Excel条形图顺序与源数据相反怎么办
  16. python合并文件夹_python实现将两个文件夹合并至另一个文件夹(制作数据集)
  17. CharField:Django文档——Model字段选项(Field Options)
  18. Android模仿微信浮窗功能的效果实现
  19. 【个人笔记】SIPp学习--正则表达式 三
  20. 毕业相关-自动问答综述

热门文章

  1. 如何做成gif动画图片?教你简单三步制作gif动图
  2. 如何批量将图片转换成jpg格式?
  3. 修复 Windows 映像
  4. 数据挖掘项目:金融风控-贷款违约预测
  5. mysql navicate查询_Mysql Navicate 基础操作与SQL语句 版本5.7.29
  6. 自动化车间3D可视化设计思路
  7. DPVS - 小米高性能负载均衡器
  8. 如何在iPhone手机上安装ipa(应用安装包)
  9. Python 股票分析入门
  10. matlab simulink 单相可调交流电源设计