D 25章 进程的终止

D 25章 进程的终止 440
25.1 进程的终止:_exit()和exit() 440
1. _exit(int status), status 定义了终止状态,父进程可调用 wait 获取。仅低8位可用,
调用 _exit() 总是成功的。
2.程序一般不会调用 _exit(), 而是调用库函数 exit()。
exit() 执行如下动作:
1.调用退出处理程序(通过 atexit() 和 on_exit() 注册的函数),其执行顺序与注册顺序相反。
2.刷新 stdio 流缓冲区
3.使用由 status 提供的值执行 _exit() 系统调用。
与专属的Unix的 _exit 不同, exit() 则属于标准C语言函数库。

25.2 进程终止的细节 441
无论进程是否正常终止,都会发生如下动作:
1.关闭所有打开文件描述符,目录流,信息目录描述符,以及转换描述符
2.作为文件描述符关闭的后果之一,将释放该进程所持有的任何文件锁
3.分离任何已经连接的 System V 共享内存段,且对应于各段的 shm_nattch 计数器值减一
4.进程为每个 System V 信号量所设置的 semadj 值将会被加到信号量值中。
5.如果该进程是一个管理终端的管理进程,那么系统会向该终端前台进程组中的每个进程发送 sighup 信号,
接着终端会于会话脱离。
6.将关闭该进程打开的任何 POSIX 有名信号量,类似调用 sem_close()
7.将关闭该进程打开的任何 POSIX 消息队列,类似于调用 mq_close()
8.作为进程退出的后果之一,如果某进程组称为孤儿,且该组中存在任何已停止进程,则组中所有进程都将收到 sighup
信号,随着为 sigcont 信号。
9.移除该进程通过 mlock() 和 mlockall() 锁建立的任何内存锁
10.取消该进程调用 mmap() 所创建的任何内存映射。

25.3 退出处理程序 442
退出处理程序是一个由程序设计者提供的函数,可于进程生命周期的任意时间点注册,并在该进程调用 exit() 正常终止时
自动执行。如果程序直接调用 _exit() 或者因信号而异常终止,则不会调用退出处理程序。
当程序收到信号而终止时,将不会调用退出处理程序。这一事实一定程序上限制了它们的效用。此时最佳的应对方式莫若为可能发送
给进程的信号建立信号处理程序,并于其中设置标志位,领主程序据此来调用 exit()。因为 exit() 不属于异步信号安全函数,所有通常
不能在信号处理程序中对其发起调用。
注册退出处理程序
atexit();
1.概念:一般内核中每个启动的进程默认都有一个标准的默认终止函数,用于在进程终止时执行的函数,该函数主要用来释放进程所占用的资源,也可以自定义终止函数。按照ISO C规定,一个进程可以注册32个终止函数,这些函数将由exit函数自动调用。
   登记的终止函数以栈的形式运行,先注册的后执行。如果自定义注册了进程终止函数,那么内核提供的默认的终止函数将会被覆盖。
原文链接:https://blog.csdn.net/qq_35733751/article/details/82392918
希望进程在结束时,进行一些清理工作,比如某些重要的数据保存到文件中。如果不登记终止函数的话,实现这个操作是有些困难的,因为进程有可能是因为某个函数调用失败,且该函数出错时又调用了exit函数导致进程终止,更糟糕的是出错的函数是不确定的。这个时候就可以通过登记注册终止函数,这样进程在终止时,会自动执行注册的终止函数把数据保存到文件中,方便了许多。
2. atexit函数语义
  atexit函数是用来在内核中注册一个进程终止时执行的函数,通过atexit函数所包含的头文件来看,atexit函数是一个C库函数。

函数原型:

#include <stdlib.h>
int atexit(void(*function)(void));

atexit函数的参数是一个函数指针,表示注册的终止函数,终止函数语法格式为:void(*function)(void) 。
  返回值:成功返回0,失败返回不一定是-1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

//以下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);

}
通过atexit函数注册的终止函数并非在所有情况下都会被调用,是否调用终止函数这取决于进程的终止方式

(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ gcc -o atexit_process atexit_process.c -lpthread
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ ./atexit_process test.txt return
term func3
term func2
term func1
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ cat test.txt
hello linux
当前进程以return形式退出,终止函数是以栈的形式执行的,先注册的终止函数后执行。然后通过cat命令查看test.txt文件的内容为hello linux 。
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ ./atexit_process test.txt exit
term func3
term func2
term func1
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ cat test.txt
hello linux
exit方式和return方式的结果是一样的,也调用了注册的终止函数。

(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ ./atexit_process test.txt _exit
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ cat test.txt
_exit方式和前两种有所不同,之前注册的进程终止函数一个都没有执行,另外,test.txt文件倒是创建了,但是使用cat命令查看文件中并没有内容。
总结

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

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

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

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

因此我们可以把进程终止方式简单总结为:
在这里插入图片描述

图1-进程终止方式

进程启动和退出——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()进入内核使进程终止退出的。
原文链接:https://blog.csdn.net/qq_35733751/article/details/82392918
一个操作系统的实现https://blog.csdn.net/chuanwang66/category_8332297.html
on_exit();
用来注册执行exit()函数前执行的终止处理程序。
函数声明

#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);

功能描述
  on_exit()用来注册终止处理程序,当程序通过调用exit()或从main 中返回时被调用, 终止处理程序有两个参数,第一个参数是来自最后一个exit()函数调用中的status,第二个参数是来自on_exit()函数中的arg;
  同一个函数若注册多次,那它也会被调用多次;
  当一个子进程是通过调用fork()函数产生时,它将继承父进程的所有终止处理程序。在成功调用exec系列函数后,所有的终止处理程序都会被删除。
返回值

成功返回0,失败返回非0值。
#define _BSD_SOURCE /* Get on_exit() declaration from <stdlib.h> */
#include <stdlib.h>
#include “tlpi_hdr.h”

#ifdef linux /* Few UNIX implementations have on_exit() */
#define HAVE_ON_EXIT
#endif

static void
atexitFunc1(void)
{
printf(“atexit function 1 called\n”);
}

static void
atexitFunc2(void)
{
printf(“atexit function 2 called\n”);
}

#ifdef HAVE_ON_EXIT
static void
onexitFunc(int exitStatus, void *arg)
{
printf(“on_exit function called: status=%d, arg=%ld\n”,
exitStatus, (long) arg);
}
#endif

int
main(int argc, char *argv[])
{
#ifdef HAVE_ON_EXIT
if (on_exit(onexitFunc, (void *) 10) != 0)
fatal(“on_exit 1”);
#endif
if (atexit(atexitFunc1) != 0)
fatal(“atexit 1”);
if (atexit(atexitFunc2) != 0)
fatal(“atexit 2”);
#ifdef HAVE_ON_EXIT
if (on_exit(onexitFunc, (void *) 20) != 0)
fatal(“on_exit 2”);
#endif

exit(2);

}
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ gcc exit_handlers.c -o exit_handlers error_functions.c curr_time.c get_num.c
(base) wannian07@wannian07-PC:~/Desktop/std/linux prog$ ./exit_handlers
on_exit function called: status=2, arg=20
atexit function 2 called
atexit function 1 called
on_exit function called: status=2, arg=10

25.4 fork()、stdio缓冲区以及_exit()之间的交互 445  

#include “tlpi_hdr.h”

int
main(int argc, char *argv[])
{
printf(“Hello world\n”);
write(STDOUT_FILENO, “Ciao\n”, 5);

if (fork() == -1)
    errExit("fork");

/* Both child and parent continue execution here */

exit(EXIT_SUCCESS);

}
(base) wannian07@wannian07-PC:~/Desktop/std/tlpi-dist/procexec$ make fork_stdio_buf
cc -std=c99 -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE -g -I…/lib -pedantic -Wall -W -Wmissing-prototypes -Wno-sign-compare -Wno-unused-parameter fork_stdio_buf.c …/libtlpi.a …/libtlpi.a -lm -o fork_stdio_buf
(base) wannian07@wannian07-PC:~/Desktop/std/tlpi-dist/procexec$ ./fork_stdio_buf
Hello world
Ciao
(base) wannian07@wannian07-PC:~/Desktop/std/tlpi-dist/procexec$ ./fork_stdio_buf > a
(base) wannian07@wannian07-PC:~/Desktop/std/tlpi-dist/procexec$ cat a
Ciao
Hello world
Hello world

printf() 信息输出了2次,是在进程的用户空间内存维护 stiod 缓冲区的。因此,通过 fork 创建的子进程会复制这些缓冲区。

父子进程调用 exit() 时,会各自刷新 stdio 缓冲区,从而导致重复输出。

可以采用以下2种方法避免:
1.可以在调用 fork 之前,使用 fflush() 来刷新 stdio 缓冲区,作为另外一种选择,使用 setvbuf() 和 setbuf() 来关闭 stdio 流缓冲。
2.子进程可以调用 _exit() 而非 exit(),以便不刷新 stdio 缓冲区。
这一技术例证了更为通用的原则:在创建子进程的应用中,典型情况下仅一个进程(一般为父进程)应通过调用 exit() 终止,而其他进程应调用 _exit()终止,
从而确保一个进程调用退出处理程序刷新 stdio 缓冲区。

write 并未出现2次,是因为 write 会将数据直接传递给内核缓冲区,fork 不会复制这一缓冲区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/706780.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

做了2年前端,盘点前端技术栈!大佬轻喷~

前言 自己写了快两年前端&#xff0c;但是大致总结一下哈哈哈哈我觉得这个话题蛮有意思的&#xff0c;可以看看大家的技术广度&#xff0c;可以进行分享和学习以及讨论所以这里说一下我对我的前端技术&#xff0c;做一下盘点和总结因为我的开发年限有限&#xff0c;所以我觉得…

C++面试准备

变量作用&#xff1a;给一段指定的内存空间起名&#xff0c;方便操作这段内存。 常量&#xff1a;用于记录程序中不可更改的数据。 #include <iostream> using namespace std;#define DAY 7 int main() {cout << "一周有" << DAY << "…

设置systemctl start kibana启动kibana

1、编辑kibana.service vi /etc/systemd/system/kibana.service [Unit] DescriptionKibana Server Manager [Service] Typesimple Useres ExecStart/home/es/kibana-7.10.2-linux-x86_64/bin/kibana PrivateTmptrue [Install] WantedBymulti-user.target 2、启动kibana # 刷…

Vue 简单自定义标签

Vue 简单自定义标签 思路&#xff1a; 1、计算每个项离父级左侧宽 left 2、计算当前滑块的宽&#xff0c;绝对定位 3、下一个项的宽/2-滑块的宽/2下一项离父级左侧的宽 left 4、使用定位left&#xff08;性能较差一点&#xff09; 或 translate 移动距离 <template><…

实用软件下载:硕思LOGO设计师最新安装包及详细安装教程

​硕思Logo设计师是一款操作灵活简单&#xff0c;且功能强大的logo制作软件&#xff0c;它可以通过简单的点击就可以为网站、博客、论坛和邮件创建专业的logo、条幅、按钮、标题、图标和签名等。 该软件提供了很多精心设计的模板和丰富的资源&#xff0c;为更好的创建logo艺术…

NestJS学习笔记

一、安装NestJS CLI工具 环境检查 //查看node版本 node -v//查看npm版本 npm -v 安装nest/cli 使用npm全局安装nestjs/cli npm i -g nestjs/cli 查看nest版本 nest -v 结果如图&#xff1a; 创建nest项目 //命令行创建nest项目 nest new 【项目名】 VScode扩展下载 1、…

linux 安装sftp及使用sftp上传和下载

一、centos7 安装sftp 1.安装 OpenSSH 服务&#xff1a; sudo yum install openssh-server2.启动 SSH 服务&#xff0c;并设置为开机启动&#xff1a; sudo systemctl start sshd sudo systemctl enable sshd3.创建一个新用户&#xff0c;用于SFTP连接&#xff08;替换your_…

STM32理论 —— μCOS-Ⅲ(2/2):时间管理、消息队列、信号量、任务内嵌信号量/队列、事件标志、软件定时器

文章目录 9. 时间管理9.1 OSTimeDly()9.2 OSTimeDlyHMSM()9.3 OSTimeDlyResume()9.4 延时函数实验 10. 消息队列10.1 创建消息队列函数OSQCreate()10.2 发送消息到消息队列函数(写入队列)OSQPost()10.3 获取消息队列中的消息函数(读出队列)OSQPend()10.4 消息队列操作实验 11. …

【Unity】官方文档学习-光照系统

目录 1 前言 2 光照介绍 2.1 直接光与间接光 2.2 实时光照与烘焙光照 2.3 全局光照 3 光源 3.1 Directional Light 3.1.1 Color 3.1.2 Mode 3.1.3 Intensity 3.1.4 Indirect Multiplier 3.1.5 Shadow Type 3.1.6 Baked Shadow Angle 3.1.7 Realtime Shadows 3.1…

具有不确定性感知注意机制的肺结节分割和不确定区域预测| 文献速递-深度学习结合医疗影像疾病诊断与病灶分割

Title 题目 Lung Nodule Segmentation and UncertainRegion Prediction With an Uncertainty-Aware Attention Mechanism 具有不确定性感知注意机制的肺结节分割和不确定区域预测 01 文献速递介绍 肺结节分割在肺癌计算机辅助诊断&#xff08;CAD&#xff09;系统中至关重…

终结国企虚假贸易:监管闭环下的关系排查与风控增强

2024年5月28日&#xff0c;国务院正式公布《国有企业管理人员处分条例》&#xff08;以下简称《条例》&#xff09;&#xff0c;将于2024年9月1日起施行。作为国家经济的重要支柱&#xff0c;国有企业的管理人员在企业运营中扮演着至关重要的角色&#xff0c;《条例》的颁布&am…

Sm4【国密4加密解密】

当我们开发金融、国企、政府信息系统时&#xff0c;不仅要符合网络安全的等保二级、等保三级&#xff0c;还要求符合国密的安全要求&#xff0c;等保测评已经实行很久了&#xff0c;而国密测评近两年才刚开始。那什么是密码/国密&#xff1f;什么是密评&#xff1f;本文就关于密…

Android低代码开发 - InputMenuPanelItem详解

我们知道MenuPanel是一个菜单面板容器&#xff0c;它里面可以放各式各样的菜单和菜单组。今天我们就来详细讲解输入菜单这个东西。 InputMenuPanelItem源码 package dora.widget.panel.menuimport android.content.Context import android.text.Editable import android.text…

Vue2/Vue3使用video播放视频--捕获截图

基本步骤 在JavaScript中&#xff0c;实现从<video>元素中截图的基本步骤如下&#xff1a; 1、创建Canvas元素&#xff1a;首先&#xff0c;需要创建一个<canvas>元素&#xff0c;因为截图操作会借助Canvas的绘图上下文来完成。 2、获取Video帧&#xff1a;从<v…

Elasticsearch-经纬度查询(8.x)

目录 一、开发环境 二、pom文件 三、ES配置文件 四、ES相关字段 五、ES半径查询 ES的字段类型:geo_point&#xff0c;可以实现以一个点为中心的半径查询(geo_distance query) ES地理位置查询: 半径查询(geo_distance query)查询指定矩形内的数据(geo_bounding_box query…

现代易货模式:重塑物品价值,引领交换新潮流

在日益繁荣的现代社会&#xff0c;物品交换文化正逐渐兴起&#xff0c;一种新型的交易模式——现代易货模式&#xff0c;正在成为市场的新宠。它不仅是对传统“以物易物”模式的现代化演绎&#xff0c;更是对物品价值再认识和交换方式创新的体现。 现代易货模式&#xff0c;简言…

【表单视图】-卡片视图支持统计设置

06/05 主要更新模块概览 统计设置 快捷回复 背景设置 帮助中心 01 表单管理 1.1 【表单视图】-卡片视图新增统计设置 说明&#xff1a; 在卡片视图中成功新增了统计功能和统计效果。现在&#xff0c;用户无需额外操作&#xff0c;即可在卡片视图状态下直接清晰地看到需求…

STM32开发过程中碰到的问题总结 - 1

文章目录 前言1. 怎么生成keil下可以使用的文件和gcc下编译使用的makefile2. STM32的时钟树3.怎么查看keil5下的编译工具链用的是哪个4. Arm编译工具链和GCC编译工具链有什么区别吗&#xff1f;5. 怎么查看Linux虚拟机是x86的还是aarch646. 怎么下载gcc-arm的编译工具链7.怎么修…

推挽与开漏输出

一般来说&#xff0c;微控制器的引脚都会有一个驱动电路&#xff0c;可以配置不同类型的数字和模拟电路接口。输出模式一般会有推挽与开漏输出。 推挽输出 推挽输出&#xff08;Push-Pull Output&#xff09;&#xff0c;故名思意能输出两种电平&#xff0c;一种是推&#xf…

天降流量于雀巢?元老品牌如何创新营销策略焕新生

大家最近有看到“南京阿姨手冲咖啡”的视频吗&#xff1f;三条雀巢速溶咖啡入杯&#xff0c;当面加水手冲&#xff0c;十元一份售出&#xff0c;如此朴实的售卖方式迅速在网络上走红。而面对这一波天降的热度&#xff0c;雀巢咖啡迅速做出了回应&#xff0c;品牌组特地去到了阿…