Linux系统编程 - 进程异常自动重启

Linux系统编程 - 进程异常自动重启

文章目录

  • Linux系统编程 - 进程异常自动重启
    • 开篇
    • 基础概念
      • 守护进程
      • 僵死进程(zombie)
    • 设计思路
    • 源码实现
    • 总结

开篇

  在Linux平台,自研服务进程通常以守护进程的形式在后台常驻运行。但偶尔也会遇到服务进程异常crash,导致产品基本功能异常,影响恶劣。
  解决这种问题,通常两种应对措施:
  ① 定位crash原因,上传补救措施。
  ② 后台重新拉起异常进程,避免影响基本功能。
对于措施①,系统部署coredump文件,通过gdb解析coredump文件就能很快定位到原因,本篇主要记录下措施②实现流程。

基础概念

守护进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
守护进程的特点是不占用终端,后台运行。在终端只需要在启动进程时加&,即可启动一个守护进程:

$ ./testBin &

僵死进程(zombie)

  long long ago,子进程终止后,会在系统中完全消失,父进程无从查询子进程的信息。因此,UNIX设计者们作出一个这样的约定:如果一个子进程在父进程之前结束,内核应该把子进程设置为一个特殊的状态。处于这种状态的进程叫做僵死(zombie)进程。僵死进程只保留一些最小的概要信息,直至父进程获取这些信息,才会完全消失,否则一直保持僵死状态。父进程可通过wait()/waitpid()获取这些状态。
  如果父进程先退出,子进程被init接管,子进程退出后init会回收其占用的相关资源。通过终端查看僵死进程(后缀带有<defunct>):

$ ps -ef | grep defunct
dx         10144   10135  0 18:08 pts/2    00:00:00 [test] <defunct>

设计思路

  通过对僵死进程概念的理解:子进程先于父进程结束时,会在系统产生一个僵死进程,直至父进程对其回收。则可以通过这点,实现进程异常crash的重启。

方案一
  在《Linux系统编程》中,有讲道:当子进程终止时,会发送SIGCHLD至父进程。因此可按如下流程:

  1. 父进程先创建一个子进程,在子进程中通过execl拉起需要的bin。此时父进程缓存bin文件对应路径和对应的pid。
  2. 父进程注册信号SIGCHLD监听,在处理函数中,通过wait()/waitpid()获取异常子进程的pid。
  3. 通过pid匹配异常进程对应的bin文件路径,再重新拉起此进程。

方案二
  进程在启动时,都会在/proc下创建一个对应的目录/proc/[pid]/。可通过监测此路径实现,流程如下:

  1. 同方案一。
  2. 获取到子进程的pid后,父进程一直检测/proc/[pid]是否存在,不存在时,重新拉起进程。

方案三
  由于waitpid()可以获取所有僵死子进程的pid,因此通过轮询此接口可实现,流程如下:

  1. 同方案一
  2. 通过waitpid()可获取所有僵死子进程的,匹配对应bin路径,重新拉起。

  其中,方案一是触发式监测,属于其中最优雅的方法。但是在实测过程中发现,子进程异常终止时,父进程存在小概率收到不到信号SIGCHLD,网上的说法是SIGCHLD不可靠。从而导致监测子进程状态失败,因此将终端触发改为轮询,衍生了方案三
方案二也是可行的,网上很多也是这么做的。既然有waitpid接口,个人更倾向于方案三

源码实现

  代码同时实现了方案一方案三,用CONFIG_SUPPORT_SIGCHLD控制。为1时,为方案一实现;为0时,为方案三实现(实测方案一,SIGCHLD偶尔接收不到)。

#define CONFIG_SUPPORT_SIGCHLD 0 // SIGCHLD不可靠。 1: 信号中断 0: 轮询#define LOG(fmt, args...)  printf(fmt, ##args)
#define LOGD(fmt, args...)  printf("[%d] %-20s %-4d D: " fmt, getpid(), __FUNCTION__, __LINE__, ##args)
#define LOGE(fmt, args...)  printf("[%d] %-20s %-4d E: " fmt, getpid(), __FUNCTION__,__LINE__, ##args)typedef struct exeInfo {char path[20];int  times;
} SExeInfo;typedef std::map<int, shared_ptr<SExeInfo>> TMapPid2Path;const char PROC_PATH[] = "/proc";
TMapPid2Path pidMap;static std::mutex sigchildMtx;
static std::condition_variable sigchildCond;static bool existExeByProc(int pid)
{struct stat fileStat;char pidPath[20] = {0};snprintf(pidPath, sizeof(pidPath), "%s/%d", PROC_PATH, pid);int ret = lstat(pidPath, &fileStat);if (ret) {//LOGD("%s lstat failed. errno = %d (%s)\n", pidPath, errno, strerror(errno));return false;}// /proc/pid/ 为目录则当前进程正常if (S_ISDIR(fileStat.st_mode)) {return true;}return false;
}static void dumpPidMapInfo(const TMapPid2Path &aMap)
{LOGD("PID     PATH                 TIME+\n");LOGD("----------------------------------\n");for (auto it = aMap.begin(); it != aMap.end(); ++it) {LOGD("%-6d  %-20s %-2d \n", it->first, it->second->path, it->second->times);}LOGD("----------------------------------\n");
}static void startExe(const char *pExePath)
{static int pid = -1;pid = fork();if (pid == -1) {LOGE("fork failed. errno = %d (%s).\n", errno, strerror(errno));} else if (pid == 0) {          // 子进程static int startCount = 0;LOGD("Pull up %s (%d).\n", pExePath, ++startCount);execl(pExePath, pExePath);} else {                        // 父进程LOGD("Child fork pid: %d.\n", pid);auto it = pidMap.begin();for (; it != pidMap.end(); ++it) {if (!strncmp(it->second->path, pExePath, strlen(pExePath))) {LOG("find %s %s.\n", it->second->path, pExePath);break;}}if (it == pidMap.end()) {shared_ptr<SExeInfo> spInfo = make_shared<SExeInfo>();strncpy(spInfo->path, pExePath, sizeof(spInfo->path));spInfo->times = 1;pidMap.insert(pair<int, shared_ptr<SExeInfo>>(pid, spInfo));} else {it->second->times++;pidMap.insert(pair<int, shared_ptr<SExeInfo>>(pid, it->second));pidMap.erase(it);}}
}#if CONFIG_SUPPORT_SIGCHLD
static bool sigchildRcv = false;
static void handler(int sig, siginfo_t *si, void *unused)
{LOGD("Receive sig: %d.\n", sig);sigchildRcv = true;sigchildCond.notify_one();
}
#endif// Eg. ./exe /tmp/test_bin
int main(int argc, char *argv[])
{if (argc < 2) {LOGE("usage: %s [path].\n", argv[0]);return 0;}#if CONFIG_SUPPORT_SIGCHLDstruct sigaction sa;sigemptyset(&sa.sa_mask);sa.sa_sigaction = handler;if (sigaction(SIGCHLD, &sa, NULL) == -1){LOGE("sigaction failed! errno = %d (%s) \n", errno, strerror(errno));}
#endiffor (int i = 1; i < argc; i++) {startExe(argv[i]);}thread th1([&]() {while(1) {#if !(CONFIG_SUPPORT_SIGCHLD)int pid = 0, status = 0;while( (pid = waitpid(-1, &status, WNOHANG)) > 0) {if (!existExeByProc(pid) && pidMap.count(pid)) {startExe(pidMap[pid]->path);}}
#elsestd::unique_lock<std::mutex> lk(sigchildMtx);sigchildRcv = false;sigchildCond.wait(lk, []{int pid = 0, status = 0;while( (pid = waitpid(-1, &status, WNOHANG)) > 0) {if (!existExeByProc(pid) && pidMap.count(pid)) {startExe(pidMap[pid]->path);}}LOGD("Receive SIGCHLD.\n");return sigchildRcv;});lk.unlock();
#endifdumpPidMapInfo(pidMap);sleep(1);}});th1.join();return 0;
}

测试
实验bin: /tmp/test为2s crash的bin文件,tmp/lambda正常运行bin。
预期: test进程2s挂掉,会被自动拉起;lambda进程正常运行,不受影响。

$ ./exe /tmp/test /tmp/lambda 
[12737] startExe             99   D: Child fork pid: 12738.
[12737] startExe             99   D: Child fork pid: 12739.
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12738   /tmp/test            1  
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12738] startExe             96   D: Pull up /tmp/test (1).
[12739] startExe             96   D: Pull up /tmp/lambda (1).
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12738   /tmp/test            1  
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12738   /tmp/test            1  
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12737] startExe             99   D: Child fork pid: 12742.
find /tmp/test /tmp/test.
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       81   D: 12742   /tmp/test            2  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12742] startExe             96   D: Pull up /tmp/test (1).
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       81   D: 12742   /tmp/test            2  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       81   D: 12742   /tmp/test            2  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12737] startExe             99   D: Child fork pid: 12744.
find /tmp/test /tmp/test.
[12737] dumpPidMapInfo       78   D: PID     PATH                 TIME+
[12737] dumpPidMapInfo       79   D: ----------------------------------
[12737] dumpPidMapInfo       81   D: 12739   /tmp/lambda          1  
[12737] dumpPidMapInfo       81   D: 12744   /tmp/test            3  
[12737] dumpPidMapInfo       83   D: ----------------------------------
[12744] startExe             96   D: Pull up /tmp/test (1).

总结

  • 在开发阶段,应优先查后台进程异常终止的原因。通常由系统配置生成coredump文件,配合gdb可以快速定位到crash代码行号。
  • 至于方案一偶尔收不到SIGCHLD,缩短处理函数的响应时间,排除信号处理函数不可重入因素,还是存在问题。网上查到的原因此信号不可靠,具体原因尚不清晰。
  • 经过此方案,在Linux系统部署用户进程时,加入此方案,能够避免进程异常导致的系统宕机等其他严重问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux:rpm部署Jenkins(1)

1.获取Jenkins安装包 我这里使用的是centos7系统&#xff0c;ip为&#xff1a;192.168.6.6 2G运存 连接外网 Jenkins需要java环境&#xff0c;java的jdk包你可以去网上下载离线包&#xff0c;或者直接去yum安装&#xff0c;我这里使用的是yum安装 再去获取Jenkins的rpm包…

每日一题——LeetCode1720.解码异或后的数组

方法一 异或运算的性质 encoded[i−1]arr[i−1]⊕arr[i] 在等式两边同时异或arr[i−1] 由于&#xff1a; 一个数异或它自己&#xff0c;结果总是0。 0异或任何数&#xff0c;结果都是那个数本身。 所以等式可以转化为&#xff1a; arr[i]arr[i−1]⊕encoded[i−1] 由于 a…

量子时代隐患显露!苹果CPU漏洞被爆,Mac用户敏感数据易遭窃取

研究表明&#xff0c;一项使Apple M系列处理器速度更快的功能也使它们容易受到无法修补的新侧信道攻击。理论上&#xff0c;黑客可以提取秘密加密密钥&#xff0c;然后访问敏感数据。 这个新发现的概念验证攻击被称为“GoFetch”。 GoFetch基于一种称为数据内存依赖预取器&…

qrcode插件-生成二维码

安装 yarn add qrcodejs2 --save npm install qrcodejs2 --save 使用 <template><div><div id"qrcodeImg"></div><!-- 创建一个div&#xff0c;并设置id --></div> </template> <script> import QRCode from q…

Sequelize一个易用且基于 promise 的 Node.js ORM 工具

Sequelize中文文档|Sequelize中文网 Sequelize 是一个易用且基于 promise 的 Node.js ORM 工具 适用于 Postgres, MySQL, MariaDB, SQLite, DB2, Microsoft SQL Server, Snowflake, Oracle DB 和 Db2 for IBM i. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功…

洛谷刷题 | P1706 全排列问题

全排列问题 题目描述 按照字典序输出自然数 1 1 1 到 n n n 所有不重复的排列&#xff0c;即 n n n 的全排列&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 n n n。 输出格式 由 1 ∼ n 1 \sim n 1∼n 组成的所有不重复的数字序…

Web3创作整理 - 2024-02-23 ~ 2024-03-25

Web3 创作整理 - 2024-02-23 ~ 2024-03-25 整理下3月份的文章&#xff0c;方便大家阅读 分类文章地址&#x1fad1;ETH什么是Dapp&#x1f3bc;ETH什么是以太坊&#x1f3b5;ETH如何才能拥有ETH&#x1f3b6;ETHBTC网络 vs ETH网络&#x1f399;️ETHETH网络中的区块链&#x1…

利用Python和IP技术实现智能旅游情报系统

文章目录 引言一、系统架构设计1. 数据采集模块2. 数据处理模块3. 用户界面模块 二、数据获取技术应用三、系统功能展示四、亮数据采集工具介绍五、总结六、号外 引言 随着旅游行业的不断发展&#xff0c;人们对旅游信息的需求也越来越大。为了帮助旅行者更好地规划行程&#…

Nodejs沙盒逃逸

Buffer 在较早一点的node.js版本中 (8.0 之前)&#xff0c;当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer&#xff0c;并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。 注&#xff1a;关…

P1219八皇后 (典DFS)

注意&#xff1a; 数组大小要开够&#xff0c;数据范围是6到13&#xff0c;要开到20&#xff0c;不然开到15数据点没法全部过。 代码&#xff1a; #include<algorithm> #include<iostream> #include<cstring> #include<queue>using namespace std;i…

Rocket mq

搭建服务【有2个】 一个是NameServer还有一个是Broker&#xff0c;要两个服务协同 后台启动服务 允许服务端自己创建topic&#xff0c;如果客户端传来的topic没有的话 然后搞个rocket mq的仪表盘&#xff0c;application.yml改成自己的地址【只需要指定NameServer的地址&#…

更灵活的定位内存地址的方法

文章目录 更灵活的定位内存地址的方法and和or指令关于ASCII码以字符形式给出的数据大小写转换的问题[bxidata]用[bxidata]的方式进行数组的处理SI和DI[bxsi]和[bxdi][bxsiidata]和[bxdiidata]不同的寻址方式的灵活应用题目示例 更灵活的定位内存地址的方法 and和or指令 1&…

Axure RP 8中文---快速原型设计工具,一站式解决方案

Axure RP 8是一款专业的快速原型设计工具&#xff0c;以其直观易用的界面和丰富的功能受到广大用户的青睐。它支持用户通过拖放操作快速创建交互式原型&#xff0c;包括线框图、流程图等&#xff0c;并具备高保真度的设计能力。Axure RP 8还提供了团队协作和共享功能&#xff0…

【Java程序设计】【C00368】基于(JavaWeb)Springboot的箱包存储系统(有论文)

TOC 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击卡片…

基于nodejs+vue铁路订票管理系统python-flask-django-php

该铁路订票管理系统采用前后端分离进行设计&#xff0c;并采用nodejs语言以及express框架进行开发。本系统主要设计并完成了用户登录管理过程、个人信息修改、用户管理、火车类型管理、火车信息管理、车票预订管理、车票退票管理、系统管理等功能。该系统操作简单&#xff0c;界…

【React】使用 JSX 为 JavaScript 添加标签

使用 JSX 为 JavaScript 添加标签实际上是将 JSX 语法与 JavaScript 代码结合使用&#xff0c;以描述用户界面。JSX 允许你在 JavaScript 中编写类似 HTML 的结构&#xff0c;并最终由 React 库将其转换为真正的 DOM 元素。以下是将标签引入 JavaScript 以及将 HTML 转化为 JSX…

13_Linux内核配置选项

内核配置选项 内核配置通常是对内核支持的各个功能进行取舍配置&#xff0c;将配置的方案保存到 configure文件中。在编译内核的时候&#xff0c;就会根据此配置对内核进行取舍编译。 编译内核之前要先配置。为了正确、合理地设置内核编译配置选项&#xff0c;从而只编译系统需…

BM85 验证IP地址(字符串)

import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** 验证IP地址* param IP string字符串 一个IP地址字符串* return string字符串*/public boolean isIPv4(String IP){//没有.i…

Redis命令集redis实战案例

作为资深研发专家&#xff0c;对Redis命令集以及实战案例有深入的了解。下面&#xff0c;我将详细介绍Redis的命令集&#xff0c;并结合实际案例来展示Redis的应用。 Redis命令集非常丰富&#xff0c;涵盖了数据操作、持久化、事务处理等多个方面。以下是一些常用的Redis命令&…

单片机入门到精通:一站式在线学习平台!

介绍&#xff1a;单片机&#xff0c;也称为微控制器&#xff08;MCU&#xff09;&#xff0c;是一种集成了中央处理器&#xff08;CPU&#xff09;、随机存储器&#xff08;RAM&#xff09;、只读存储器&#xff08;ROM&#xff09;以及输入/输出接口于单一芯片上的微型计算机。…