板凳----Linux/Unix 系统编程手册 25章 进程的终止

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/727140.html

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

相关文章

手机携号转网接口查询与对接指南:技术开发者必备手册

在当今通信技术飞速发展的背景下&#xff0c;手机携号转网已成为推动电信市场竞争、保障用户权益的重要手段。而对于技术开发者而言&#xff0c;掌握手机携号转网接口的查询与对接方法&#xff0c;无疑是提升服务兼容性和用户满意度的关键。 比如如下应用场景&#xff1a; 1.…

docker容器网络与宿主机网络冲突的原因与解决方案

一、故障现象 在用docker-compos.yaml文件或者手动创建docker网络时&#xff0c;可能会出现新建的容器网络与宿主机网络冲突&#xff0c;导致SSH远程连接中断&#xff0c;并无法再用Xshell等远程连接工具连接宿主机。现象如下&#xff1a; [rootcontroller ~]# docker networ…

Web数据提取:Python中BeautifulSoup与htmltab的结合使用

1. 引言 Web数据提取&#xff0c;通常被称为Web Scraping或Web Crawling&#xff0c;是指从网页中自动提取信息的过程。这项技术在市场研究、数据分析、信息聚合等多个领域都有广泛的应用。Python社区提供了丰富的工具和库来支持这一技术&#xff0c;其中BeautifulSoup和htmlt…

家有老人小孩,室内灰尘危害大!资深家政教你选对除尘空气净化器

哈喽&#xff0c;各位亲爱的朋友们&#xff01;今天我们来聊聊每次大扫除时最让人头疼的问题——灰尘。你有没有发现&#xff0c;两天不打扫&#xff0c;桌子上就能积上一层灰&#xff1b;阳光一照&#xff0c;地板上的灰尘都在跳舞&#xff1b;整理被子的时候&#xff0c;空气…

卷积网络热图显示

【卷积神经网络可视化】之热度图可视化_visualizing heatmaps of class activation in an ima-CSDN博客 首先安装好对应的安装包&#xff0c;做这个的目的是为了可视化网络模型&#xff0c;查看每一个卷积之后得出的效果&#xff0c;可以通过改变网络模块来改进网络结构。这段…

基于JSP的二手交易平台网站

开头语&#xff1a; 你好&#xff0c;我是计算机专业的学长猫哥。如果你对二手交易平台感兴趣或有开发相关需求&#xff0c;欢迎联系我&#xff0c;我的联系方式见文末。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;ECLI…

RH850---注意问题积累--1

硬件规格(引脚分配&#xff0c;内存映射&#xff0c;外设功能规格、电气特性、时序图)和操作说明 注意:有关使用的详细信息&#xff0c;请参阅应用说明 ---------外围函数。。。 1:存储指令完成与后续同步指令的一代 当控制寄存器被存储指令更新时&#xff0c;从存储的执行开始…

3ds MAX 2024版资源包下载分享 3ds Max三维建模软件资源包下载安装

3DSMAX凭借其强大的功能和广泛的应用领域&#xff0c;吸引了无数创作者的青睐。 在游戏制作领域&#xff0c;3DSMAX展现出了无可比拟的优势。从细腻的角色建模到宏大的场景搭建&#xff0c;再到逼真的动画效果和渲染&#xff0c;它都能轻松应对&#xff0c;为游戏世界注入了生动…

通过MindSpore API实现深度学习模型

快速入门 将相应的包逐一导入到项目中&#xff0c;这是制作项目的第一步。 import mindspore from mindspore import nn from mindspore.dataset import vision, transforms from mindspore.dataset import MnistDataset 处理数据集 先从网上下载对应的数据集文件,MindSpor…

【GO】rotatelogs库和sirupsen/logrus库实现日志功能的实践用例

“github.com/sirupsen/logrus” 是一个 Go 语言的日志库&#xff0c;它提供了一种简单、灵活的方式来记录日志。该库的主要特点包括&#xff1a; 支持多种日志输出目标&#xff0c;如控制台、文件等。 支持日志轮转&#xff0c;可以按照时间或文件大小进行轮转。 支持日志格式…

“开放”的大模型到底有多“开放”?!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

FRP内网穿透及多级代理的使用

目录 0、前言 1、场景介绍 2、环境准备 2.1 下载frp 2.2 配置一台VPS 2.3 socks5客户端 2.5 网络环境准备 3、Frp设置 3.1 一层代理 3.1 二层代理 4、Frp总结 0、前言 FRP是比较老牌的也是比较流行的反向代理、内网穿透软件。FRP用途和使用场景可以看官方文档&#xff0c;…

【Linux工具】yum软件包管理器与Vim编辑器的高效运用

目录 Linux 软件包管理器 YUM 什么是软件包 安装工具 rzsz 及注意事项 查看软件包 安装和卸载软件 安装软件 卸载软件 Linux 开发工具 编辑器 - Vim 使用 ​编辑 Vim 与 Vi 的区别 Vim 的基本概念 三种模式 Vim 的基本操作 操作尝试&#xff1a; Vim 命令集解释…

【有趣分享】火柴人VS数学

今天看到一个很有趣的视频&#xff0c;是火柴人VS数学&#xff0c;大家感兴趣的可以看一下&#xff1a; 火柴人 VS 数学(Math)https://www.bilibili.com/festival/jzj2023?bvidBV1ph4y1g75E&spm_id_from333.337.search-card.all.click

[Linux] 系统的基本架构特点

Linux系统的基本结构 Linux is also a subversion of UNIX,it follows the basic structure of UNIX 内核(kernel)&#xff1a; 操作系统的基本部分 管理与硬件相关的功能&#xff0c;分模块进行 常驻模块&#xff1a;进程控制IO操作文件\磁盘访问 用户不能直接访问内核 外壳(s…

java学习--集合(大写一.1)

看尚硅谷视频做的笔记 1.集合框架概述 1.1生活中的容器 首先知道集合是来解决什么问题的&#xff1f; 1.1.1内存层面需要针对于多个数据进行存储&#xff0c;此时&#xff0c;可以考虑的容器有&#xff1a;数组、集合类 对于内存层面的来说&#xff0c;断电后数据就不复存…

Python和OpenCV图像分块之图像边长缩小比率是2

import cv2 import numpy as npimg cv2.imread("F:\\mytupian\\xihuduanqiao.jpg") # 低反光 cv2.imshow(image, img) # # 图像分块 # dst np.zeros(img.shape, img.dtype) ratio 2 #图像边长缩小比率是2&#xff0c;也就是一张图片被分割成四份 height, wi…

模型预测控制MPC详解(附带案例实现)

模型预测控制MPC详解&#xff08;附带案例实现&#xff09; 文章目录 模型预测控制MPC详解&#xff08;附带案例实现&#xff09;1. 最优控制问题2. 什么是MPC3. 二次规划Quadratic Programming4. MPC为什么可以转换成QP问题&#xff08;推导过程&#xff09;5. MPC总结5.1 MPC…

希喂、K9、弗列加特主食冻干测评:这次,我们去了美国的实验室

主食冻干你有喂过吗&#xff1f; 作为有七年养猫经验的铲屎官&#xff0c;我强烈推荐&#xff0c;真的值得一试&#xff01; 在宠物经济蓬勃发展的今天&#xff0c;养猫已经成为了一门学问。人们开始追求更加科学、健康的养猫方式。大量喂养案例表明&#xff0c;传统的商业猫粮…

【ARMv8/v9 GIC 系列 2.1 -- GIC SPI 中断的 pending 和 clear pending 配置】

文章目录 GIC Pending 和 Clear PendingGICD_ISPENDR<n>GICD_ICPENDR<n>参数<n>编号解释使用举例设置中断ID 100为挂起状态清除中断ID 100的挂起状态 代码实现小结 GIC Pending 和 Clear Pending 在ARMv8体系结构中&#xff0c;GICD_ISPENDR<n> 和 GI…