异常控制流学习笔记——fork函数和wait函数_Lunapius的博客-程序员资料_fork wait

初步理解异常控制流中调用fork函数和wait函数的错误处理

在计算机系统中,异常有中断、陷阱、故障和终止四种类别,具体的内容我们不在此处展开,但是无论如何,当异常出现时,计算机系统都会对其进行处理, 其大致流程如下图所示:

计算机异常处理流程
作为初学者,我们平常练习时所编写的代码其实是极为朴素简单的,在代码中加入进行错误处理也不会导致程序难以阅读。但实际上,我们日常生活、工作生产中使用的大部分程序其实都是非常复杂的。
当Unix系统级函数遇到错误时,它们通常会返回-1,并设置全局整数变量errno来表示什么出错了。程序员应该总是检查错误,但是不幸的是,许多人都忽略了错误检查,因为它使代码变得臃肿,而且难以读懂。比如,下面是我们调用Unix fork函数时会如何检查错误:

if ((pid = fork()) <0){
    
	fprintf(stderr, "fork error: %s\n"strerror(errno)) ;
	exit(0) ;
}

strerror函数返回一个文本串,描述了和某个errno值相关联的错误。通过定义下面的错误报告函数,我们能够在某种程度上简化这个代码:

void unix_error(char *msg)/ * Unix-style error */{
    
	fprintf(stderr"%s: %s\n", msg,strerror(errno));
	exit(0);
}

给定这个函数,我们对fork的调用从4行缩减到2行:

if ((pid - fork()) < 0)
	unix_error ( "fork error");

通过使用错误处理包装函数,我们可以更进一步地简化代码。对于一个给定的基本函数foo,我们定义一个具有相同参数的包装函数Foo,但是第一个字母大写了。包装函数调用基本函数,检查错误,如果有任何问题就终止。比如,下面是fork函数的错误处理包装函数:

pid_t Fork(void){
    
	pid_t pid;
	if ((pid = fork()) < 0)
		unix_error("Fork error");
	return pid;
}

给定这个包装函数,我们对fork的调用就缩减为1行:

pid = Fork();

想要理解fork函数,我们还需要具备非常关键的进程相关的知识:

1.获取进程ID

每个进程都有一个唯一的正数(非零)进程ID(PID)。getpid函数返回调用进程的PID。getppid函数返回它的父进程的PID(创建调用进程的进程)。

#include <sys/types.h>
#include <unistd.h> pid_t getpid(void); pid_t getppid(void);
//返回:调用者或其父进程的PID。

getpid和getppid函数返回一个类型为pid_t的整数值,在Linux系统上它在types.h中被定义为int。

2.创建和终止进程

从程序员的角度,我们可以认为进程总是处于下面三种状态之一:

  • 运行。进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
  • 停止。进程的执行被挂起(suspended),且不会被调度。
  • 终止。进程永远地停止了。进程会因为三种原因终止: 1)收到一个信号,该信号的默认行为是终止进程,2)从主程序返回,3)调用exit函数。

exit函数以status退出状态来终止进程(另一种设置退出状态的方法是从主程序中返回一个整数值)。
父进程通过调用fork函数创建一个新的运行的子进程。

#include <sys/types.h>#include <unistd.h>pid_t fork(void);
*返回:子进程返回0,父进程返回子进程的PID,如果出错,则为-1

新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间木同的(但是独立的)一份副本,这就意味着当父进程调用fork时,子进程可以读写进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。

fork函数它只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork返回子进程的PID。在子进程中,fork返回0。因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。

接下来让我们试着去理解一个简单的fork函数

下面展示了一个使用fork创建子进程的父进程的示例。当fork调用在第6行返回时,在父进程和子进程中x的值都为1。子进程在第8行加一并输出它的x的副本。相似地,父进程在第13行减一并输出它的x的副本。

int main(){
    
	pid_t pid;
	int x = 1;
	pid = Fork();
	if (pid == 0) {
    /*Child */
		printf ( "child : x=%d\n",++x);
		exit(0);
	}
	/*Parent*/
	printf ( "parent: x=%d\n",--x);
	exit(O);
}

程序运行得到如下结果:

linux> ./fork
parent : x=0
child : x=2

通过这个例子,我们可以直观地体会到 fork() 的几个特点:

  1. 形如fork单词本身,fork函数的功能概括地说就是“分叉”,它在被调用时,会生成一个几乎与父进程一样的子进程。
  2. 子进程与父进程并存,子进程诞生之时,便会在自己的进程中继续向下执行代码。在这个过程中,它的父进程其实也在并发地执行之后的代码,所以如果没有特殊的控制,父子进程谁先结束是不一定的,就我们给出的例子而言,它也有可能给出这样的结果

linux> ./fork
child : x=2
parent : x=0

  1. fork虽然只被调用一次,但是会返回两个值,一个代表父进程,一个代表子进程:代表父进程的值是一串数字,这串数字是子进程的 PID;一个代表子进程,值为0。

大概有点感觉了?那让我们试试这个:

int main(){
    
	Fork();
	Fork();
	printf ("hello\n");
	exit(0);
}

这次我们在fork后的子进程中又再次进行fork(),这显然是可行的。就像树枝生长会长出新的枝干一样,子进程也可以创建它的子进程。于是我们可以得到这样的结果:

linux> ./fork
hello
hello
hello
hello

也许这样谈论fork非常的抽象,我们不妨画张进程图进行理解:
在这里插入图片描述
进程图是直观而形象的,多画进程图显然能够帮助更好地理解fork函数。

僵死进程是为何物?

当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。一个终止了但还未被回收的进程称为僵死进程(zombie)。

如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。init进程的PID为1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排init进程去回收它们。不过,长时间运行的程序,比如shell或者服务器,总是应该回收它们的僵死子进程。即使僵死子进程没有运行,它们仍然消耗系统的内存资源。

一个进程可以通过调用wait或者waitpid函数来等待它的子进程终止或者停止。

#include <sys/types.h>#include <sys/wait.h>
	pid_t wait(int *statusp);
	//返回:如果成功,则为子进程的PID,如果出错,则为-1.

父进程执行到wait(),就如我们之前提到过的进程的三种状态之一,会被挂起,等待其子进程结束后,自己才结束。

我们不妨通过一个例子来具体地了解wait:

void fork()
{
    
    int child_status;

    if (fork() == 0) {
    
	printf("HC: hello from child\n");
        exit(0);
    } else {
    
	printf("HP: hello from parent\n");
	wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}

运行结果如下:

linux> ./fork
HP: hello from parent
HP: hello from parent
CT: child has terminated
Bye

从输出结果来看,父进程执行到wait时,不再继续进行后面的printf("CT: child has terminated\n");,而是直接被挂起,等待自己的子进程结束。
流程图大致如下:

		   · ————>printf———————— ·
		   |        HC           |
		   |                     v
main————>fork————>printf————>wait·————>printf————>printf
		      	    HP                   CT        Bye

实际上,wait是waitpid的延伸与简化,调用wait(&status)等价于调用waitpid(- 1,&status,0)。但是篇幅有限,我们在此不进行展开。

总结

至此,我们已经对fork函数和wait函数有了基础的认识。能够记清楚fork中父进程与子进程的相同点和不同点、进程的特性和状态、wait函数的返回值参数等,对之后的学习会很有帮助。希望大家变得更强,也欢迎大家留言讨论,一起进步。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Lunapius/article/details/111742678

智能推荐

Ubuntu 20.04搭建FTP服务器_凌小皮_177的博客-程序员资料_ubuntu 20.04.3 lts 搭建ftp

Ubuntu20.04下搭建FTP服务器这是一个新手小白的搭建流程,使用的是Ubuntu20.04桌面版,小伙伴们可以参考一下哦~&nbsp;&nbsp;下面就是作者小白经过一番折腾后总结出来的搭建流程啦~一、安装1.下载VSFTPD终端。(ps:如何你在这里没有使用root用户下面的sudo不能少哦,否则会提示权限不够哒~)sudo apt-get install vsftpd二、配置VSFTPD服务器1.修改配置文件可以用你自己喜欢的编辑器进行编辑修改(我用的是gedit编辑器):

Android BLE 总结-源码篇(BluetoothLeAdvertiser)_GeneralAndroid的博客-程序员资料

在做Android BLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiser的startAdvertising方法,如下所示:mBluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, myAdvertiseCallback);那么我打算写的BLE总结之_1671465600

亚马逊FCC要求解决方案_WY177_2284_O165的博客-程序员资料_亚马逊产品fcc认证不通过

近段时间,亚马逊官方网站要求所有的销售电子电器的商家提供FCC认证。众所周知,如果商家在亚马逊要求的时间内不能提供FCC认证,那么宝贝就要下架了。怎么在短时间内拿到FCC认证呢?有如下几个解决方案:1,普通的电子产品第一步:提供产品说明书给检测机构第二步:根据产品说明书报价第三步:填写FCC认证申请表(电子档即可)第四步:双方签订合作协议第五步:邮寄样品第六步:收到样品支付款项,安排测试第七步:测试完成出具检测报告第八步:电邮报告2,无线产品FCC-ID解决办法第一步:申请App

《暗黑世界》安卓APK 编译流程详细说明教程!(图文)_uxqclm的博客-程序员资料_apk编辑器修改魔窟暗黑世界教程

欢迎来到9秒:www.9miao.com  关于开发环境的搭建,之前的相关文档已经很详细的说明,对环境的搭建请参考以前的相关文档,如有问题,及时在论坛里提问,会有管理人员快速解答,此文档主要针对eclipse下安卓编译.    首先肯定是把暗黑世界的源码导入eclipse,源码导入进来后,就开始进行编译的准备工作,这里首先需要将Classes里面的.cpp文件和和相关的文件夹路径写

Spark DataSource V1 & V2 API 一文理解_java编程艺术的博客-程序员资料_spark datasource

1. Spark Connector 介绍Spark Connector 是一个 Spark 的数据连接器,可以通过该连接器进行外部数据系统的读写操作。Spark Connector 包含两部分,分别是 Reader 和 Writer。DataSource的API的代码位于spark_sql【如spark-sql_2.11-2.4.4.jar】 模块中的org.apache.spark.sql.sources包下,定义了如何从存储系统进行读写的相关 APIAPI分为v1、v2两个版本,v2的代码

基于Javaweb实现进销存管理系统_编程指南针的博客-程序员资料

项目编号:BS-XX-054运行环境:开发工具:IDEA / ECLIPSE数据库:MYSQL5.7应用服务器:TOMCAT8.5.31开发技术:JSP/SERVLET/Jquery/easyUI本系统基于Javaweb技术开发完成一套商品的进销存管理系统,前端采用jquery+easyUI开发,界面简洁大方,用户体验较好。主要实现完成了货品、供应商、分公司的基本信息管理,以及商品出入库和库存管理,并在系统管理模块中实现了权限和角色的管理及分配操作,可以实现不同的人员分配不同角.

随便推点

清洗_tmjdone的博客-程序员资料

<br />提子上的白粉: 弄点牙膏混在洗提子的水里。(未测试)<br />瓷杯上的茶垢:(有盖的瓷杯)放个柠檬片在水杯里,倒满热水盖上盖浸泡一会儿,在水没变温前,用力摇晃瓷杯(使水在瓷杯里晃来晃去,此时应该有部分茶垢脱落),然后可以那拿个柠檬片把剩下的茶垢擦掉。遗留问题:接近杯子上沿的茶垢不易清楚,不知是水位太低还是什么原因。

判断cron表达式输入是否有效的正则表达式_firefox77的博客-程序员资料_cron表达式正则表达式 js

String regEx = "(((^([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|^([0-9]|[0-5][0-9]) |^(\\* ))((([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|([0-9]|[0-5][0-9]) |(\\* ))((([0-9]|[01

自定义的ListView 点击事件无响应解决办法_chenrenxiang的博客-程序员资料_qlistview 不响应

注,本文转载自:http://www.cnblogs.com/eyu8874521/archive/2012/10/17/2727882.html   开发中很常见的一个问题,项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,在adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有

关于使用Depends查看DLL依赖的库的使用_努力减肥的小胖子5的博客-程序员资料

1.打开depends.exe2.将所要查看的库拖入depends中3.图中红色的为需要依赖的库

Oracle函数之LISTAGG_践行见远的博客-程序员资料_listagg oracle

原文链接:https://www.cnblogs.com/ivictor/p/4654267.html前面刚说了wm_concat函数,没过几天用的时候,就报这个函数找不到了。于是有找到了新的替代函数,listagg,而且用起来更好用了。下面我们来看看其具体用法。用法:对其作用,官方文档的解释如下:For a specified measure, LISTAGG ord...

drozer安装和使用——android***测试_weixin_33962621的博客-程序员资料

一、drozer安装包把Drozer工具包drozer-installer-2.3.4.zip解压后看到的目录如下图,其中setup.exe文件是安装在PC机上面的,agent.apk是安装在手机模拟器或者移动手机里面的。二、PC机上面安装在pc机器上面直接点击setup.exe进行傻瓜式安装就行了,程序默认安装在 “C:\drozer”下,安装好后如下图所示:三、安装ag...

推荐文章

热门文章

相关标签