fork函数的定义

#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);

fork函数在父进程中返回子进程的pid,在子进程中返回0。注意在子进程中返回的0,并不是子进程的pid,子进程的pid在父进程的返回值中保存。而子进程的返回值是为了标识它是子进程,用来区分父子进程的。那么为什么这样设计父子进程的返回值呢?我的理解是这样的:第一,对于父进程来说,它可能同时有多个子进程,并且没有一个函数可以获得所有子进程的pid,所以它需要知道每个子进程的pid,这样便于管理各个子进程;第二,对于子进程来说,它父进程只能有一个,所以它可以不必实现和父进程类似的机制,获取父进程的pid,它可以通过getpid函数获取父进程的pid。而在内核中,进程pid为0,总是在内核交换进程时使用,所以进程的真实pid不可能为0。

资源相关问题

读时共享,写时复制

fork函数并不是在被调用时,为子进程copy一份父进程的副本,而是将父进程数据段、堆区和栈区的权限改变为只读。当父子进程中任何一个进程要修改该区域的值时,内核只会为修改区域的那块内存制作一个副本,通常是虚拟内存中的一页。这样的机制无需copy所有的父进程资源(这些资源在子进程中不一定有用),提高了程序的效率。但要注意,父子进程共享代码段。

复制的资源

  • 能够复制fork函数会复制数据段、堆区和栈区的资源
  • 能够复制参数表和环境表
  • 能够复制各个ID,但不包括PID
  • 不能够继承文件锁
1)数据段、堆区、栈区、参数表和环境表和上文说的一样,遵循读时共享,写时复制的机制;
2)环境表中只能改变其本身进程和其子进程的环境变量,无法改变其父进程的环境变量;
3)像是实际用户ID、实际组ID、有效用户ID、有效组ID、进程组ID和附属ID,这些都是进程的属性,所以存在于PCB中,每个进程都有自己的PCB节点,其中部分数据是继承父进程的。当然pid不会。
4)对于文件锁,子进程是不会被继承的。子进程虽然复制了文件描述符表,但都指向同一个文件表,当然了,文件表也就指向了同一个i节点。在i节点中保存着和文件锁相关的链表头节点(struct lockf)的地址。i节点资源不属于进程资源,而是文件资源,所以不会继承文件锁。

调用使用

常见使用方法

  • if...else结构:最常见的使用方法,很简单。
  • 和管道结合使用:主要用来父子进程间通信,使用时要注意,子进程会复制父进程的管道文件描述符,因此,都可以对对管道进行读写操作。需要注意的是,在使用管道这个临界资源时,不要忘记在软件逻辑上避免进程饥饿现象的产生。
  • 子进程的管理:别忘了了父进程能返回子进程的pid。当有多个子进程时,如果会用到各个子进程的pid的话,不要只是定义一个 pid_t pid

    的变量。这样的话,只能管理一个子进程哟!

避免僵尸进程的方法

  • 使用wait和waitpid函数:阻塞等待子进程结束,回收进程表资源
  • 安插信号:利用signal函数安插SIGCHLD信号。因为在子进程结束后,父进程会收到该信号。再自己写个回调函数,在函数中调用wait或waitpid函数,回收进程表资源;如果父进程对子进程结束不感兴趣,则可以利用“signal(SIGCHLD,SIG_IGN)”,将回收子进程资源的工作交给内核来做;但要注意,SIGCHLD信号是传统的不可靠信号,信号处理函数执行期间会暂时阻塞,因此,在这期前,如果又有了SIGCHLD信号,则会被抛弃,即无法处理多个SIGCHLD信号。所以信号处理函数的正确写法是:
void handler(int signs)
{
int tmp_errno=errno;
while(waitpid(-1,&status,WNOHANG)>0)
{
//处理返回信息
}
errno=tmp_errno;
}

但仍要注意的是,当waitpid返回值为-1时,会改变全局变量errno的值,如果这是在主程序中检测errno的值时,就很有可能发生冲突。因此,在进入信号处理函数之前要保存errno 的值,最后再回复errno的值。

  • 利用孤儿进程的机制:在创建的子进程中在调用fork函数,创建一个孙子进程,然后子进程终止,那么孙子进程就会被init进程收养。再当孙子进程结束时,回收进程资源的工作就交由了init进程去做。当然,有人会问,那么子进程结束后它的进程资源谁去回收?我说,那当然是父进程回收。这个时候能用父进程去回收子进程资源,是因为这是父子进程已经不在时异步的关系了。换句直白点的话说就是,父进程知道子进程在不久的将来一定会结束。回头想想,安插信号的方法的本质,不就是通过信号捕捉,确定了子进程会在不久的将来会结束吗?即在调用wait和外套pid时,不会阻塞等待很长时间就能返回,不会影响父进程自身的工作。
注意:安插信号和利用孤儿进程的机制来避免僵尸进程的方法,不仅仅能避免僵尸进程,还能不影响父进程本身的工作任务,这一点是非常有用的。

进程管理之fork函数的更多相关文章

  1. UNIX环境编程学习笔记(19)——进程管理之fork 函数的深入学习

    lienhua342014-10-07 在“进程控制三部曲”中,我们学习到了 fork 是三部曲的第一部,用于创建一个新进程.但是关于 fork 的更深入的一些的东西我们还没有涉及到,例如,fork ...

  2. Linux -- 进程管理之 fork() 函数

    一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. Test1 f ...

  3. 进程控制之fork函数

    一个现有进程可以调用fork函数创建一个新进程. #include <unistd.h> pid_t fork( void ); 返回值:子进程中返回0,父进程中返回子进程ID,出错返回- ...

  4. 【Linux编程】进程标识符与fork函数

    ID为0的进程一般是调度进程.常被称为交换进程(swapper),是内核中的系统进程. ID为1的进程叫做init进程,是一个普通用户进程,不属于内核,由内核调用. 一个现有进程能够调用fork函数创 ...

  5. [Chapter 3 Process]Practice 3.1 相关知识:进程创建、fork函数

    3.1 Using the program shown in the Figure3.30, explain what the output will be at LINE A 答案:LINE A 处 ...

  6. Linux0.11 创建进程的过程分析--fork函数的使用

    /* * linux/kernel/fork.c * * (C) 1991 Linus Torvalds */ /* 注意:signal.c和fork.c文件的编译选项内不能有vc变量优化选项/Og, ...

  7. UNIX环境编程学习笔记(22)——进程管理之system 函数执行命令行字符串

    lienhua342014-10-15 ISO C 定义了 system 函数,用于在程序中执行一个命令字符串.其声明如下, #include <stdlib.h> int system( ...

  8. UNIX环境高级编程——进程管理和通信(总结)

    进程管理与通信 进程的管理 进程和程序的区别: 进程: 程序的一次执行过程   动态过程,进程的状态属性会发生变化 程序:存放在磁盘上的指令.数据的有序集合  是个文件,可直观看到 程序program ...

  9. 深入浅出--UNIX多进程编程之fork()函数

    0前言 上周都在看都在学习unix环境高级编程的第八章--进程控制.也就是这一章中.让我理解了unix中一些进程的原理.以下我就主要依照进程中最重要的三个函数来进行解说.让大家通过阅读这一篇文章彻底明 ...

随机推荐

  1. 通用JSONHelp 的通用的封装

    1. 最近项目已经上线了 ,闲暇了几天 想将JSON  的序列化 以及反序列化进行重新的封装一下本人定义为JSONHelp,虽然Microsoft 已经做的很好了.但是我想封装一套为自己开发的项目使用 ...

  2. C# 百分比的获取

    这里介绍 C# 百分比转换有2种方式 例: double a=50; double b=100; a/b.ToString("0.00%"); 或 a/b.ToString(&qu ...

  3. spring boot 拦截器添加

    @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Autowired private XxxInt ...

  4. vue数据绑定原理

    一.定义 vue的数据双向绑定是基于Object.defineProperty方法,通过定义data属性的get和set函数来监听数据对象的变化,一旦变化,vue利用发布订阅模式,通知订阅者执行回调函 ...

  5. Android-Bluetooth Low Energy(BLE)

    Android Bluetooth Low Energy Android 低功耗蓝牙简介 2016-4-18 Android4.3(API 18)介绍了平台支持的低功耗蓝牙,app可用于发现设备,检索 ...

  6. Druid 详细介绍

    文章来自阿里巴巴 Druid是一个JDBC组件,它包括三部分:  DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系. DruidDataSource 高效可 ...

  7. Loadrunner常用15种的分析点

    性能测试的工具目前用的最多的就是LoadRunner和JMeter,性能测试重点在分析和解决, 下边列出了LR中常见的15种分析点,不知道如何分析性能,来看这里吧! Vusers:提供了生产负载的虚拟 ...

  8. DL4NLP——词表示模型(一)表示学习;syntagmatic与paradigmatic两类模型;基于矩阵的LSA和GloVe

    本文简述了以下内容: 什么是词表示,什么是表示学习,什么是分布式表示 one-hot representation与distributed representation(分布式表示) 基于distri ...

  9. 关于vue组件的一个小结

    用vue进行开发到目前为止也有将近一年的时间了,在项目技术选型的时候隔壁组选 react的时候我们坚持使用vue作为前端的开发框架.虽然两者思想上的差异不大,但是vue的语法在代码的可读性以及后期的维 ...

  10. C# 接口基础学习

    什么是接口  接口,在表面上是由几个没有主体代码的方法.属性.索引器.事件,或者它们的组合的集合体,有唯一的名称,可以被类或结构或者其他接口所实现(或者也可以说继承).它在形式上可能是如下的样子: i ...