博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux环境下编程(一)——进程fork()的使用
阅读量:4132 次
发布时间:2019-05-25

本文共 3832 字,大约阅读时间需要 12 分钟。

先简单介绍一下进程的概念

定义: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

经典定义: 进程是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程上下文(context)中的。

注:上下文可能让人看不懂,简单地理解,将文本文档里的‘e’字母翻译成ASCII码是101,,在这个环境(上下文)中101代表字母e;当用二进制101转换成int型后,101就是数值5。 在不同的环境中,二进制101所代表的意义不同,这个不同的环境就是上下文。

作用:有了进程的概念之后,程序在运行的过程中才会产生这种假象,好像自己是系统当前运行的唯一的程序,独占地使用CPU、独占地使用存储器。

进程所拥有的:1、独立的逻辑控制流。  (即 某时刻利用cpu来执行指令)

                         2、私有的地址空间。     (即存储器的一段地址空间)

内核为每个进程维持一个上下文,通过上下文切换的方式来从一个进程跳转到另一个进程,上下文切换也是进程并发执行的基础。

每个进程都有一个进程id(大于0的整数),getpid()可以返回进程id,getppid()返回父进程的pid。

#include 
#include
pit_t getpid(void);pid_t getppid(void);

先通过具体的程序来看一看,如何获取进程和父进程id。

#include 
#include
void main(){ pid_t pid; fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid()); }

父进程通过fork来创建一个新的进程

#include
#include
pid_t fork(void);

进程的退出

进程通过exit函数退出,status用来设置进程的退出状态。exit(0)代表正常0,exit(1)代表异常退出。

#include
void exit(int status);

在父进程中,当我们调用了fork函数之后,我们就创建了一个新的进程。这个函数在两种不同的进程上下文中返回不同的值。在父进程中,fork返回子进程的pid;在子进程中fork返回0。

我们要注意的是:

1、上面这句话第一次读的时候可能会觉得有些奇怪,子进程就不是进程吗?为什么它的fork函数会返回0,而不是返回子进程的子进程的pid?其实当子进程被创建的时候,系统会给子进程开辟一个新的地址空间,然后将父进程地址空间的所有东西都复制给子进程,也就是说会将父进程中的pid = Fork()这一段指令也复制给子进程。但是如果这个被复制来的pid=Fork()也被执行了,那么子进程就会产生新的子进程,然后新的子子进程复制了这句话又产生了新的子子子进程,这样岂不是子子孙孙无穷尽也了?所以在子进程中,这条指令是不会被执行的,而是会返回一个0。

2、子进程到底复制了哪些东西?a)所有的变量  b)从创建它的fork函数开始的指令(当然也有可能是指令也被全部复制,只是从fork的地址开始执行,这个有待继续考证)。被复制过来的用于创建它的fork函数如上所讲不会被执行,但是下面的其它的不是用来创建它的fork函数却会被执行。这个下面再讨论。

先看看具体实例:

#include 
#include
#include
#include
void error_msg(char *msg){ fprintf(stderr, "%s: %d\n", msg, strerror(errno)); exit(0);}int Fork(){ pid_t pid; if ( ( pid = fork() ) < 0 ) error_msg("fork failed"); return pid;}void main(){ pid_t pid; fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid()); pid = Fork(); if ( 0 == pid ) fprintf(stdout, "I am the child procress, my pid value is %d, but my real pid is %d, my parent pid is %d\n", pid, getpid(), getppid()); else fprintf(stdout,"I am the parent process, my child pid is %d, my pid is %d, my parent pid is %d\n", pid, getpid(), getppid()); }

对于这段程序,采用了包裹函数的形式进行了错误处理,这样就可以直接使用pid = Fork()的形式进行子进程的调用,而不用额外写一大堆臃肿的错误处理代码。

简单运行如下:

结果分析:

1、第23行的fprintf函数只被父进程执行了,而在子进程中并没执行,但是24行的pid=Fork()后面的fprintf语句却被子进程执行了。证明这个指令的复制是以fork本身为分界线的。

2、不管父进程还是子进程,都有pid这个变量,它们的值在两个进程中是不一样的,在父进程中pid为子进程的pid52569,在子进程pid的值为0。 这个就让部分人头疼了,怎么fork上面有的代码被复制了,有的代码没有被复制?这个就得了解,程序被装载的时候,变量和指令存储的位置是不一样的,将变量全部复制,然后只执行部分指令,其实也很容易理解。

3、我明明执行了一次./a.out这个指令来运行这段程序,为什么会在运行的过程中会打印出来“jdh@jdh-virtual........../cssh$”这个东西,然后再继续运行程序,这个不是很奇怪么?而且子进程的父进程不就是最开始的进程吗?它的pid应该是52568,怎么变成了1352了呢?

这个我最开始也很奇怪,在查看了一些资料后知道,其实这是子进程被托管了的标志。子进程被创建后,父进程和子进程就同时作为两个独立的进程独立地运行。它们执行的先后顺序依赖于操作系统的调度。在本实例中,父进程在打印完了最后的话后就该结束了,所以它正常的退出了。退出之前,父进程将子进程托管其它的进程。大多数的时候子进程会被托管给init进程,而init的进程号是1。这时候疑问又来了,这个是1352又不是1,那么它到底被托管给了谁?其实是因为在ubuntu下面有一个init -user进程,一看这个名字就知道这个和init有关系了吧。至于为什么ubuntu要弄出个这个来,就超出了fork的范围了,有兴趣自己再深究。

我抓取下init -user 这个进程,看看他是不是1352,如图所示:

很庆幸,被我抓获到了,这下无影遁形了吧?

接下来,我们看看,假如父进程有多个fork,到底会产生多少个子进程。

#include 
#include
#include
#include
void error_msg(char *msg){ fprintf(stderr, "%s: %d\n", msg, strerror(errno)); exit(0);}int Fork(){ pid_t pid; if ( ( pid = fork() ) < 0 ) error_msg("fork failed"); return pid;}void main(){ pid_t pid; fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid()); pid = Fork(); pid = Fork(); pid = Fork(); fprintf(stdout," Hi Fork , my pid is %d, my parent pid is %d \n", getpid(), getppid()); }
运行结果:

结果分析:

我们不难看出调用了3个,产生了除了first进程外的7个其它进程,而且很不幸的是,它们都被托管了。

问题:为什么调用3个产生7个?你知道吗?

参考《深入理解计算机系统》

你可能感兴趣的文章
Ubuntu 16.04 apt-get更换为国内阿里云源
查看>>
laravel部署到宝塔步骤
查看>>
小程序获取access_token
查看>>
navicat远程连接mysql数据库
查看>>
tp5令牌数据无效 解决方法
查看>>
自己的网站与UCenter整合(大致流程)
查看>>
laravel 制作通用的curd 后台操作
查看>>
【小红书2017年笔试】求一个数组中平均数最大的子数组
查看>>
Linux基础系列-定时器与时间管理
查看>>
Linux基础系列-可执行程序的产生过程
查看>>
Linux基础系列-Kernel 初始化宏
查看>>
Linux子系统系列-I2C
查看>>
<iOS>关于自定义description的一点用法
查看>>
Unix 命令,常用到的
查看>>
DLL中建立进程共享数据段需要注意的语法问题
查看>>
服务器端技术----Http请求的处理过程
查看>>
C语言-预处理指令2-条件编译
查看>>
C语言-预处理指令3-文件包含
查看>>
C语言-变量类型
查看>>
C语言-static和extern关键字1-对函数的作用
查看>>