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安装与配置 将包上传到 /…

如何使用靜態IP代理?【詳細教程】

靜態IP地址是手動分配給設備或伺服器的固定不變的 IP。與動態 IP 地址不同&#xff0c;動態 IP 地址由 DHCP 伺服器自動分配&#xff0c;並且會隨時間而變化。​​​​​​​ 如何獲取和設置靜態IP地址 一、檢查是否需要靜態IP&#xff1f; 在配置靜態 IP 之前&#xff0c;請…

监控组态软件的构成与功能

监控组态软件的构成包括系统开发环境、图形界面系统、实时数据库系统等。其功能包括数据采集与控制、人机交互界面、数据处理与存储等。 监控组态软件的构成&#xff1a; 系统开发环境&#xff1a;这是自动化工程设计工程师为实施其控制方案&#xff0c;在组态软件的支持下进行…

Java 基础之 XQuery:强大的 XML 查询语言

一、什么是 XQuery XQuery 是一种函数式语言&#xff0c;专门用于检索以 XML 格式存储的信息。它负责从 XML 文档中查找和提取元素及属性&#xff0c;在网络服务中有着广泛的应用&#xff0c;比如提取信息、生成摘要报告、进行数据转换等。 XQuery 是 W3C 设计的一种针对于 X…

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

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

docker-3.docker权限问题

docker权限,每次开机都要重新sudo 目录 1.简介2.解决办法1:当前用户添加到docker用户组(这个方法还是缺权限??)3.解决办法2:临时更改Docker socket文件权限1.简介 Ubuntu下Docker提示permission denied (权限不足)解决办法 安装docker后,执行docker ps命令时提示 perm…

easyExcel实现表头批注

背景&#xff1a; 网上大部分都不能直接使用&#xff0c;为此总结一个方便入手且可用的工具&#xff0c;用自定义注解实现 依赖包&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>…

内部类和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 对比度…

19 设计模式之享元模式(电影院座位预定案例)

一、享元模式的定义 享元模式是一种结构型设计模式&#xff0c;它通过共享对象来支持大量细粒度的对象&#xff0c;减少内存消耗。享元模式的核心思想是&#xff1a;将对象分为共享部分和非共享部分&#xff0c;只有共享部分是被多个对象共享的&#xff0c;而非共享部分则是每个…

使用redis 的stream 做消息中间件 多线程消费消息

1.redis stream 特点 1.支持消息持久化 2.消费者组模式 3.消息确认机制 4. 消息重试机制 5. 死信队列2. 消息生产者服务 2.1 如下代码Service Slf4j public class StreamMessageProducer {Autowiredprivate StringRedisTemplate redisTemplate;private static final String S…

Python100道练习题

Python100道练习题 BIlibili 1、两数之和 num1 20 num2 22result num1 num2print(result)2、一百以内的偶数 list1 []for i in range(1,100):if i % 2 0:list1.append(i) print(list1)3、一百以内的奇数 # 方法一 list1 [] for i in range(1,100):if i % 2 ! 0:lis…

Java转C之并发和多线程

提纲&#xff1a; 概念介绍与对比概述 简述Java与C在并发和多线程方面的核心区别解释C11标准、POSIX、C11 <threads.h>、Pthread等名词 Java多线程与并发回顾 线程、Runnable、ExecutorService概念说明同步关键字与工具类含义 C并发基础 没有Java式的内置线程类&#xf…

Ubuntu系统本地化搭建Maxakb+Ollama

安装docker 最详细的ubuntu 安装 docker教程-腾讯云开发者社区-腾讯云 安装Ollama Ollama官网 执行命令&#xff1a; curl -fsSL https://ollama.com/install.sh | sh安装完成后下载模型 执行命令&#xff1a; ollama run llama3.3:70b安装MaxKb 执行命令&#xff1a; d…

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

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

【Flux.jl】 卷积神经网络

Flux.jl 是包含卷积神经网络的, 但是官方API文件中没有给出一个完整的程序框架, 只是对所需神经元给了局部解释, 此外对 model-zoo 模型动物园中的案例没有及时跟着 Flux.jl 的版本更新, 也无法运行出来结果。 因此本文搭建了一个完整可训练的卷积神经网络。 Conv 卷积算子…