linux基于systemd自启守护进程 systemctl自定义服务傻瓜式教程

系统服务

书接上文: linux自启任务详解

演示系统:ubuntu 20.04

开发部署项目的时候常常有这样的场景: 业务功能以后台服务的形式提供,部署完成后可以随着系统的重启而自动启动;服务异常挂掉后可以再次拉起
这个功能在ubuntu系统中通常由systemd提供
如果仅仅需要达成上述的场景功能,则systemd的自定义服务就可以满足

什么是systemd

systemd:系统和服务管理器

  • 功能:
    systemd 是一个初始化系统(init system)和服务管理器,它负责在 Linux 系统启动时启动系统的核心服务和进程。它的任务是管理系统引导、服务管理、进程监控、资源管理等。
    systemd 提供了服务启动、停止、重启、日志记录等功能,并管理系统的运行状态。
  • 作用:
    启动和管理系统服务:systemd 会在系统启动时根据配置文件(服务单元文件)启动必要的系统服务(例如网络、日志记录、定时任务等)。
    管理进程和依赖关系:systemd 确保服务按照正确的顺序启动,并且根据需要重启或停止。
    资源管理:通过 cgroups(控制组)和其他技术,systemd 能够限制服务对 CPU、内存等资源的使用。
  • 配置文件:
    systemd 使用以 .service 结尾的单元文件(unit files)来定义服务。每个服务有一个单独的配置文件,这些文件描述了服务如何启动、停止、重启等。
    例如,/etc/systemd/system/ 和 /lib/systemd/system/ 目录下存放着这些单元文件。

什么是systemctl

systemctl:管理 systemd 的命令行工具

  • 功能:
    systemctl 是与 systemd 配合使用的命令行工具,用于启动、停止、重新启动、查看、启用或禁用 systemd 管理的服务。它是用户与 systemd 交互的主要方式。
  • 作用:
    启动和停止服务:通过 systemctl 命令,你可以启动、停止或重启任何由 systemd 管理的服务。
    查看服务状态:systemctl status 命令可以用来查看服务的当前状态,帮助管理员诊断服务是否正常运行。
    管理系统:systemctl 也可用于关闭、重启、挂起系统等操作。
    启用/禁用服务:systemctl enable 用于设置服务开机启动,systemctl disable 用于禁止服务开机启动。
  • 常见命令示例:
  1. 启动服务:systemctl start <service_name>
  2. 停止服务:systemctl stop <service_name>
  3. 查看服务状态:systemctl status <service_name>
  4. 重启服务:systemctl restart <service_name>
  5. 设置服务开机启动:systemctl enable <service_name>
  6. 设置服务不开机启动:systemctl disable <service_name>

关系

  • systemd 是基础,systemctl 是工具:
    systemd 是系统和服务的管理器,它负责实际的服务管理、进程监控、资源分配等。而 systemctl 是一个命令行工具,用户通过它与 systemd 进行交互,执行启动、停止、查看状态等操作。
    可以理解为,systemd 是背后的系统管理框架,而 systemctl 是用户与其交互的接口。
  • systemctl 控制 systemd:
    systemctl 是通过向 systemd 发送指令来管理服务和系统。例如,当你通过 systemctl start <service_name> 启动一个服务时,systemctl 会告诉 systemd 启动该服务,systemd 会根据服务的配置文件启动服务并管理它。

自定义自启动服务

linux自启任务详解

想要自定义一个自启服务,需要两个东西:可执行程序(我们自己的后台业务程序)和systemd的服务脚本
假设我们自己的业务程序名为:test_demo,服务脚本名为:test_demo.service
当然了这个程序仅做演示比较简单,仅有一个test_demo_main.c文件,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>const char * filePath = "/home/lijilei/1.txt";
const char * text = "hello world\n";
const char * textend = "end lalala\n";
int g_count = 0;int main(int argc,char**argv)
{FILE *fp = NULL;fp = fopen(filePath,"a+");assert(fp > 0);while(true){sleep(6);fwrite(text, strlen(text),1,fp);fflush(fp);  ++g_count;if(g_count > 10){fwrite(textend, strlen(textend),1,fp);break;}}fprintf(fp,"我要写东西: %s","东西");fflush(fp);fclose(fp);return 0;}

使用cc -o test_demo test_demo_main.c 可编译出test_demo程序
该演示程序逻辑相当简单:打开一个文件/home/lijilei/1.txt,向文件中分10次写入内容,然后退出

test_demo.service文件也相当简单

#move this file to  /etc/systemd/system/
[Unit]
Description=Start up test_demo[Service]
Type=simple
ExecStart=/home/lijilei/xlib_xdnd/test_demo
Restart=on-failure[Install]
WantedBy=multi-user.target

脚本被systemd执行的时候会拉起ExecStart指定路径下的/home/lijilei/xlib_xdnd/test_demo程序;
将脚本放到/etc/systemd/system/目录下,按循序执行如下指令:

  • sudo systemctl enable test_demo.service 启用服务,以便在系统启动时自动启动
  • sudo systemctl start test_demo.service 启动test_demo.service服务,也就是变相的拉起配置的ExecStart=/home/lijilei/xlib_xdnd/test_demo程序
  • sudo systemctl status test_demo.service 停止服务

当修改.service文件后执行

  • sudo systemctl daemon-reload 当有修改.service文件时,需重新加载
    上述的配置已经可以实现开机自启一个服务运行

自定义自启动守护进程

自启动守护进程的业务场景

在上述自启服务的基础上,将业务服务程序改为守护进程程序,使用守护进程去守护目标业务程序会更方便的控制业务程序的生命周期;
比如将守护进程改为看门狗程序,业务程序一直给看门狗发指令(喂狗),当业务程序因为业务崩溃了,则守护进程(看门狗主动拉起)业务程序,当然了我这里不会演示如何写一个看门狗程序,这里用定时查看进程快照的方式检测目标业务程序是否在执行,如果不在执行则拉起

什么是守护进程

守护进程是个孤儿进程,它的运行脱离了进程组的管控,无法接受进程退出信号,会一直运行在后台直到本身发生崩溃退出

为什么使用守护进程

守护进程的特性决定了它不会因为任何退出信号而关闭,所以适合用来执行监控任务,只要守护进程自带的业务逻辑足够简单,那守护进程将永远运行,直到系统关机,能让守护进程退出的方法只有三种

  1. 系统关机
  2. 找到守护进程的pid,手动kill
  3. 守护进程因自己的运行bug崩溃退出

因为systemd的功能,我们可以克服第一个方法跟第三个方法导致的守护进程因关机或崩溃而无法再次运行的问题

怎么写一个守护进程

这里创建一个名为daemond.c的文件,文件内容如下:

// daemon.c
#include <stdio.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <time.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <assert.h>static FILE *g_fp = NULL;
static time_t g_now;
static const char* PIDFile = "/var/daemond.pid";
//static const char* LOCKDir = "/var/run/daemond";
static const char* LOGFile = "/var/log/daemond.txt";//看护的程序名字,可以是多个
static const char*  PROCESSName1 = "test_demo";
static const char*  PROCESSName2 = NULL;static int init_daemon(void)
{pid_t pid;int i;pid = fork();if(pid > 0){//第一步,结束父进程,使得子进程成为后台exit(0);}else if(pid < 0){return -1;}/*第二步建立一个新的进程组,在这个新的进程组中,子进程成为这个进程组的首进程,以使该进程脱离所用终端*/setsid();/*再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端*/pid = fork();if(pid > 0){exit(0);}else if(pid < 0){return -1;}//第三步:关闭所用从父进程继承的不再需要的文件描述符for(i = 0;i < NOFILE;close(i++));//第四步:改变工作目录,使得进程不与任何文件系统联系chdir("/");//第五步:将文件屏蔽字设置为0umask(0);//第六步:忽略SIGCHLD信号   执行第二步后就不需要执行该步骤signal(SIGCHLD,SIG_IGN);// 1. 忽略其他异常信号// 忽略子进程结束信号,防止产生僵尸进程//signal(SIGCLD, SIG_IGN);// 忽略管道破裂信号,防止程序因向已关闭的管道写入而异常退出//signal(SIGPIPE, SIG_IGN);// 忽略停止信号,守护进程通常不应被外部信号随意停止//signal(SIGSTOP, SIG_IGN);return 0;
}static int program_running_number(const char *prog)
{if(prog == NULL) {return 0;}FILE *fp;int count = 0;char buf[8] = {0};char command[128];snprintf(command, sizeof(command), \"ps -ef | grep -v grep | grep -w -c %s", prog);command[sizeof(command) - 1] = '\0';fp = popen(command, "r");if (fp == NULL) {time(&g_now);fprintf(g_fp,"系统时间:\t%s\t\t execute %s failed: %s",ctime(&g_now),command, strerror(errno));fflush(g_fp);return 0;}if (fgets(buf, sizeof(buf), fp)) {count = atoi(buf);}pclose(fp);return count;
}static int createPIDFile(const char* File)
{umask(000);FILE *pidfile = fopen(File, "w");if (pidfile) {fprintf(pidfile, "%d", getpid());fclose(pidfile);return 0;} else {return -1;}
}static int createLOCKDir(const char* dir)
{char cmd[256] = {0};sprintf(cmd,"mkdir %s",dir);int ret = system(cmd);if (ret == 0) {return 0;} else {return -1;}
}static void watchProcess(const char** prcsessList)
{for (const char **prog = prcsessList; *prog; prog++) {if (program_running_number(*prog) > 0) {//fprintf(g_fp,"%s is running.\n", *prog);} else {time(&g_now);fprintf(g_fp,"系统时间:%s %s isn't running.\n",ctime(&g_now),*prog);fflush(g_fp);//再次执行唤起目标程序指令(可替换拉起进程指令)char cmd[256] = {0};sprintf(cmd,"sudo systemctl start %s.service",*prog);fprintf(g_fp,"执行命令: %s\n",cmd);int value = system(cmd);if (value == -1) {time(&g_now);fprintf(g_fp,"系统时间:%s %s : system() failed\n",ctime(&g_now),cmd);fflush(g_fp);} else if (WIFEXITED(value)) {time(&g_now);fprintf(g_fp,"系统时间:%s %s executed successfully with exit code %d: succeed\n",ctime(&g_now),cmd,WEXITSTATUS(value));fflush(g_fp);} else if (WIFSIGNALED(value)) {time(&g_now);fprintf(g_fp,"系统时间:%s %s : terminated by signal %d\n",ctime(&g_now),cmd,WTERMSIG(value));fflush(g_fp);} else {time(&g_now);fprintf(g_fp,"系统时间:%s %s : Unknown status\n",ctime(&g_now),cmd);fflush(g_fp);printf("Unknown status\n");}}        }
}int main()
{init_daemon(); createPIDFile(PIDFile);//createLOCKDir(LOCKDir);while(1) {sleep(3);g_fp = fopen(LOGFile,"a+");if(g_fp == NULL) {return -1;}const char *program_name_list[] = {PROCESSName1, PROCESSName2};//这里修改进程看护逻辑watchProcess(program_name_list);fflush(g_fp);fclose(g_fp);}return 0;
}

使用cc -o daemond daemon.c 可编译出daemond守护进程程序
该daemond逻辑比较简单,就是负责监视test_demo程序,如果test_demo程序退出了就调用systemctl指令,执行test_demo.service,再次拉起test_demo

daemond.service的写法就稍微跟test_demo.service不同了

#move this file to  /etc/systemd/system/
[Unit]
Description=Start up daemond
After=network.target
[Service]
User=root
Group=root
ExecStart=/home/lijilei/xlib_xdnd/daemond   --single-instance
#当进程退出时自动重启
Restart=always
#适用于后台运行的服务,systemd 等待父进程退出,并且通过 PID 文件确认进程启动
Type=forking
#适用于后台运行的服务,systemd 等待父进程退出,并且通过 PID 文件确认进程启动
PIDFile=/var/daemond.pid
#只终止主进程,不终止子进程
KillMode=process
#RestartSec=5              #服务崩溃后会等待 5 秒钟再重启
#StartLimitIntervalSec=10  #定义了一个 10 秒的时间窗口
#StartLimitBurst=1         #在 10 秒内,服务最多重启 1 次。如果超过这个次数,systemd 将不会再重启服务
#删除PID文件
ExecStopPost=/bin/rm -f /var/daemond.pid
#删除日志文件
ExecStopPost=/bin/rm -f /var/log/daemond.txt
[Install]
WantedBy=multi-user.target

将脚本放到/etc/systemd/system/目录下,按顺序执行如下指令:

  • sudo systemctl enable daemond.service 启用服务,以便在系统启动时自动启动
  • sudo systemctl start daemond.service daemond.service服务,也就是变相的拉起配置的/home/lijilei/xlib_xdnd/daemond程序

执行效果

把test_demo.service和daemond.service都加入开机自启后会出现如下现象:

  1. test_demo.service会拉起test_demo程序
  2. test_demo程序在完成打印后退出
  3. daemond查找进程快照发现test_demo退出,就执行systemctl脚本test_demo.service
  4. test_demo.service会拉起test_demo程序
  5. …如此反复执行

查看下daemon.service的执行状态

$ sudo systemctl status daemond.service ● daemond.service - Start up daemondLoaded: loaded (/etc/systemd/system/daemond.service; enabled; vendor preset: enabled)Active: active (running) since Fri 2024-11-22 01:43:28 UTC; 2 weeks 0 days agoMain PID: 125749 (daemond)Tasks: 1 (limit: 14203)Memory: 13.9MCGroup: /system.slice/daemond.service└─125749 /home/lijilei/xlib_xdnd/daemond --single-instanceWarning: journal has been rotated since unit was started, output may be incomplete.

发现这个服务已经连续运行两周了
查看下1.txt内容:
在这里插入图片描述

发现已经打印了20几万行信息了

附录

如果你在 systemd 单元文件中使用了其他不熟悉或不常见的配置项,建议通过以下命令来验证服务单元文件的正确性:

  • sudo systemd-analyze verify /etc/systemd/system/your_service.service
    这个框架有个问题就是daemon在调用system()函数时能执行但是返回值是-1,猜测是由systemctl导致的.后面我再研究研究
    以上

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

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

相关文章

ros项目dual_arm_pick-place(moveit和gazebo联合仿真)(一)

目录 前言正文创建功能包具体代码运行 总结 前言 dual_arm_pick-place项目中&#xff0c;实现了两套的moveit和gazebo联合仿真。 启动文件分别是bringup_moveit.launch和arm_bringup_moveit.launch。 在这个项目中&#xff0c;我将代码重新创建了一个包&#xff0c;co_simula…

MySQL 索引(B+树)详解

MySQL 索引&#xff08;B树&#xff09;详解 MySQL逻辑架构对比InnoDB与MyISAM存储结构存储空间可移植性、备份及恢复事务支持AUTO_INCREMENT表锁差异全文索引表主键表的具体行数CRUD操作外键 sql优化简介什么情况下进行sql优化sql语句执行过程sql优化就是优化索引 索引索引的优…

MySQL生产环境备份脚本

全量备份脚本&#xff0c;其中BakDir&#xff0c;ZlbakDir&#xff0c;LogFile需要自己创建 #!/bin/bash export LANGen_US.UTF-8# 指定备份目录 BakDir/root/beifen/data/mysqlbak/data/allbak # 指定增量备份目录 ZlbakDir/root/beifen/data/mysqlbak/data/zlbak # 备份日志…

HTTP multipart/form-data 请求

序言 最近在写项目的过程中有一个需求是利用 HTTP 协议传输图片和视频&#xff0c;经过查询方法相应的方法发现使用 multipart/form-data 的方式&#xff0c;这是最常见处理二进制文件的表单编码类型。  学习了一下午&#xff0c;现在总结一下使用的方法和相关的知识点&#x…

Linux下redis环境的搭建

1.redis的下载 redis官网下载redis的linux压缩包&#xff0c;官网地址:Redis下载 网盘链接&#xff1a; 通过网盘分享的文件&#xff1a;redis-5.0.4.tar.gz 链接: https://pan.baidu.com/s/1cz3ifYrDcHWZXmT1fNzBrQ?pwdehgj 提取码: ehgj 2.redis安装与配置 将包上传到 /…

day09性能测试(1)——纯理论

document.querySelector(video).playbackRate 2.5 //可以写任何数字 【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、性能测试概念 2、功能测试 vs 性能测试 3、小结&#xff08;习题&#xff09; 4、性能测试的策略 4.1 基准测试 4.2 负载测试 …

内部类和Object类

匿名对象 格式&#xff1a; 匿名对象只可以调用一次成员 &#xff1a; 1. 调用一次成员变量 &#xff1a; new 类名(实参).成员变量名&#xff1b; 2.调用一次成员方法&#xff1a; new 类名(实参).成员方法名(实参)&#xff1b; 匿名对象存在的必要&#xff1a;为了提高…

Python的3D可视化库vedo 1-3 (visual模块)网格对象的线和面、图片的属性

文章目录 4 MeshVisual4.1 线条4.1.1 线宽和颜色4.1.2 线条渲染为管 4.2 曲面4.2.1 物体展示为实心或框架4.2.2 曲面插值4.2.3 面的剔除 4.3 纹理4.4 相机跟随 5 ImageVisual5.1 图片属性5.1.1 占用内存大小5.1.2 颜色标量范围 5.2 渲染属性5.2.1 透明度5.2.2 亮度5.2.3 对比度…

基于JAVA的旅游网站系统设计

摘要 随着信息技术和网络技术的迅速发展&#xff0c;人们的生活质量和观念也在发生着改变&#xff0c;各地争相发展旅游业&#xff0c;传统的 旅游社已经无法满足人们的需求&#xff0c;旅游网站将突破传统在时间和地域的限制&#xff0c;成为方便、快捷、安全、可靠的旅游 方…

H5游戏出海如何获得更多增长机会?

海外H5小游戏的崛起给了国内众多中小厂商出海发展的机会&#xff0c;开发者如何在海外市场获得更多的增长机会&#xff1f;#APP出海# H5游戏如何在海外获得核心用户&#xff1f; HTML5游戏的开发与运营者们首先可以利用量多质高的HTML5游戏&#xff0c;维持海外用户粘性&…

国际荐酒师Peter助力第六届地博会,推动地理标志产品国际化发展

国际荐酒师Peter Lisicky助力第六届知交会暨地博会&#xff0c;推动地理标志产品国际化发展 第六届粤港澳大湾区知识产权交易博览会暨国际地理标志产品交易博览会于2024年12月9日至11日在中新广州知识城盛大举行&#xff0c;吸引了全球众多行业专家、企业代表及相关机构齐聚一…

2024 亚马逊云科技re:Invent:Werner Vogels架构哲学,大道至简 六大经验助力架构优化

在2024亚马逊云科技re:Invent全球大会第四天的主题演讲中&#xff0c;亚马逊副总裁兼CTO Dr.Werner Vogels分享了 The Way of Simplexity&#xff0c;繁简之道&#xff0c;浓缩了Werner在亚马逊20年构建架构的经验。 Werner表示&#xff0c;复杂性总是会“悄无声息”地渗透进来…

ThinkPHP框架审计--基础

基础入门 搭建好thinkphp 查看版本方法&#xff0c;全局搜version 根据开发手册可以大致了解该框架的路由 例如访问url http://127.0.0.1:8094/index.php/index/index/index 对应代码位置 例如在代码下面添加新方法 那么访问这个方法的url就是 http://127.0.0.1:8094/index.…

如何在vue中使用ECharts

一. 打开ECharts官网,点击快速入门 下面是ECharts官网的链接 https://echarts.apache.org/ 二.在vue中使用 1.首先先引入Echarts js文件 如下图&#xff0c;下面的第一张图片是官网的实现&#xff0c;第二章图片是我根据官网的实现 2.给ECharts 创建一个DOM容器 3. 使用ec…

网络原理之 IP 协议

目录 1. IP 协议报文格式 2. 网段划分 3. 地址管理 1) 动态分配 2) NAT 机制 (网络地址转换) 3) IPv6 4. 路由选择 1. IP 协议报文格式 IP 协议是网络层的重点协议。 网络层要做的事情&#xff0c;主要就是两方面&#xff1a; 1) 地址管理 制定一系列的规则&#xff…

HyperMesh CFD功能详解:后处理功能Part 2

Clips Clips 按钮包含两个工具。Box Clip用于空间上的裁剪&#xff0c;Scalar Clip可以根据物理量的范围裁剪。 示例&#xff1a;Box Clips 裁剪 示例&#xff1a;Scalar Clips 裁剪 通过裁剪&#xff0c;仅显示density范围是10~20的等值面 示例&#xff1a;显示效果控制 部分透…

Java项目实战II基于微信小程序的跑腿系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在快节奏的现代生活中&…

【机器学习与数据挖掘实战案例01】基于支持向量回归的市财政收入分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支&#xff0c;专注于让计算机系统通过数据学习和改进。它利用统计和计算方法&#xff0c;使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数…

JavaEE 【知识改变命运】03 多线程(3)

文章目录 多线程带来的风险-线程安全线程不安全的举例分析产出线程安全的原因&#xff1a;1.线程是抢占式的2. 多线程修改同一个变量&#xff08;程序的要求&#xff09;3. 原子性4. 内存可见性5. 指令重排序 总结线程安全问题产生的原因解决线程安全问题1. synchronized关键字…

【力扣】409.最长回文串

问题描述 思路解析 因为同时包含大小写字母&#xff0c;直接创建个ASCII表大小的桶来标记又因为是要回文子串&#xff0c;所以偶数个数的一定可以那么同时&#xff0c;对于出现奇数次数的&#xff0c;我没需要他们的次数-1&#xff0c;变为偶数&#xff0c;并且可以标记出现过…