博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
17-atexit函数——进程终止
阅读量:2044 次
发布时间:2019-04-28

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

1. 何为终止函数

  一般内核中每个启动的进程默认都有一个标准的默认终止函数,用于在进程终止时执行的函数,该函数主要用来释放进程所占用的资源,也可以自定义终止函数。按照ISO C规定,一个进程可以注册32个终止函数,这些函数将由exit函数自动调用

   登记的终止函数以栈的形式运行,先注册的后执行。如果自定义注册了进程终止函数,那么内核提供的默认的终止函数将会被覆盖

  有同学可能会问,登记终止函数的意义何在?

  有时候我们希望进程在结束时,进行一些清理工作,比如某些重要的数据保存到文件中。如果不登记终止函数的话,实现这个操作是有些困难的,因为进程有可能是因为某个函数调用失败,且该函数出错时又调用了exit函数导致进程终止,更糟糕的是出错的函数是不确定的。

  这个时候就可以通过登记注册终止函数,这样进程在终止时,会自动执行注册的终止函数把数据保存到文件中,方便了许多。

2. atexit函数语义

  atexit函数是用来在内核中注册一个进程终止时执行的函数,通过atexit函数所包含的头文件来看,atexit函数是一个C库函数。

函数原型:

#include 
int atexit(void(*function)(void));

  atexit函数的参数是一个函数指针,表示注册的终止函数,终止函数语法格式为:void(*function)(void) 。

  返回值:成功返回0,失败返回-1,不会设置errno变量

3. atexit实现进程不同方式退出

#include 
#include
#include
#include
#include
//以下func1,func2,func3都是线程终止函数void term_func1(void){ puts("term func1");}void term_func2(void){ puts("term func2");}void term_func3(void){ puts("term func3");}int main(int argc , char *args[]){ if(argc < 3){ fprintf(stderr,"use: %s file[exit | _exit | return]\n" ,args[0]); exit(0); } //注册终止函数 atexit(term_func1); atexit(term_func2); atexit(term_func3); //打开文件 FILE *fp = fopen(args[1] , "w"); if(NULL == fp){ perror("fopen"); exit(-1); } //向文件写入数据 fprintf(fp , "hello linux"); if(!strcmp(args[2], "exit")){ exit(0); //标准C库函数 }else if(!strcmp(args[2], "_exit")){ _exit(0); //系统调用 }else if(!strcmp(args[2], "return")){ return 0; //正常返回 }else{ fprintf(stderr,"use: %s file[exit | _exit | return]\n", args[0]); } exit(0);}

编译并运行:

gcc -o atexit_process atexit_process.c -lpthread./atexit_process test.txt return

  需要注意的是通过atexit函数注册的终止函数并非在所有情况下都会被调用,是否调用终止函数这取决于进程的终止方式,下面我们来看一下进程以不同方式退出的各种状态。

3.1 以return方式退出

执行命令./atexit_process test.txt return, 以return方式运行,结果如下:

这里写图片描述

  从上图中可以看出,当前进程以return形式退出,终止函数是以栈的形式执行的,先注册的终止函数后执行。然后通过cat命令查看test.txt文件的内容为hello linux 。

3.2 以exit方式退出

执行命令./atexit_process test.txt exit, 以exit方式运行,结果如下:

这里写图片描述

exit方式和return方式的结果是一样的,也调用了注册的终止函数。

3.3 以_exit方式退出

执行命令./atexit_process test.txt _exit, 以_exit方式运行,结果如下:

这里写图片描述

  _exit方式和前两种有所不同,之前注册的进程终止函数一个都没有执行,另外,test.txt文件倒是创建了,但是使用cat命令查看文件中并没有内容。

4. 总结

  不同的进程终止方式会产生不同的结果,如果我们选择return方式和exit方式,进程在终止之前会调用注册的进程终止函数,但是选择_exit方式,就算注册了终止函数,进程在终止之前也不会调用。

  另外,需要注意的是在程序中写入文件数据用的函数是一个标准C库函数,在使用fprintf函数写入数据时,实际上数据并不会写入文件,而是先写入buff缓冲区中,这种方式也叫全缓存,只有当缓冲区写满或者调用fclose函数才会把数据写入文件中

  当程序终止时也会把数据写入文件,但是会有一些区别,如果使用return方式和exit方式让程序退出,会刷新buf中的数据到文件中,但是以_exit方式让程序退出,是不会刷新数据到文件中。

  _exit是系统调用,进程终止前,不会调用终止函数,也不会刷新数据到文件中,而是直接进入内核。而exit和return是标准库函数,进程终止前,会调用终止函数,也会刷新数据到文件中。

因此我们可以把进程终止方式简单总结为:

这里写图片描述
图1-进程终止方式

5. 进程启动和退出——atexit函数

下面这种图也证实了exit函数内部调用了_exit函数。

这里写图片描述
图2-进程的启动和退出过程

进程的启动和退出过程大概如图2所示:

  1.进程在运行时,首先内核会先启动一个例程,这个例程的作用是加载程序运行的参数,环境变量,在内核注册终止函数等这些工作,然后启动例程会调用main函数。

  2.main函数调用了exit函数使进程终止退出,在进程终止之前,如果注册了终止函数,那么exit函数会先去依次调用进程终止函数,注册了几个就调用几个,每调用完一个终止函数并返回,调用顺序是以栈的形式来调用,然后调用flush刷新IO缓冲区的数据再返回,最后调用了系统调用_exit或_Exit,然后进程终止退出。

  3.值得注意的是main函数也可以通过调用系统调用_exit直接进入内核使进程终止并退出,通过调用系统调用的方式使进程终止的话,并不会调用注册的进程终止函数。也可以通过main函数调用用户函数,然后用户函数调用exit,然后依次调用进程终止函数,再调用标准I/O函数刷新缓冲区,最后调用系统调用_exit使进程终止退出。

  4.当然,用户函数也是可以直接通过系统调用_exit()进入内核使进程终止退出的。

你可能感兴趣的文章
iOS常用宏定义
查看>>
被废弃的dispatch_get_current_queue
查看>>
什么是ActiveRecord
查看>>
有道词典for mac在Mac OS X 10.9不能取词
查看>>
关于“团队建设”的反思
查看>>
利用jekyll在github中搭建博客
查看>>
Windows7中IIS简单安装与配置(详细图解)
查看>>
linux基本命令
查看>>
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
ExecutorService 线程池 newFixedThreadPool newSingleThreadExecutor newCachedThreadPool
查看>>
强引用 软引用 弱引用 虚引用
查看>>
数据类型 java转换
查看>>
常用的正则表达式
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>
ie8 加载不到js 报SCRIPT1028: 缺少标识符、字符串或数字 ;SCRIPT5009: “anorectaSearch”未定义
查看>>
mybatis 根据 数据库表 自动生成 实体
查看>>
win10将IE11兼容ie10
查看>>
Kettle WebService组件无法传参问题解决
查看>>
checkbox设置字体颜色
查看>>
统计:分组统计后只加合计,不加小计 group by rollup
查看>>