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("/");
    //第五步:将文件屏蔽字设置为0
    umask(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 daemond
     Loaded: 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 ago
   Main PID: 125749 (daemond)
      Tasks: 1 (limit: 14203)
     Memory: 13.9M
     CGroup: /system.slice/daemond.service
             └─125749 /home/lijilei/xlib_xdnd/daemond --single-instance

Warning: 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导致的.后面我再研究研究
    以上

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

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

相关文章

Unity 设计模式-策略模式(Strategy Pattern)详解

策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了一系列算法&#xff0c;并将每种算法封装到独立的类中&#xff0c;使得它们可以互相替换。策略模式让算法可以在不影响客户端的情况下独立变化&#xff0c;客户端通过与这些策略对象进…

Android环境搭建

Android环境搭建 第一步&#xff1a;安装 Homebrew 执行以下命令来安装 Homebrew&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"检测是否安装成功&#xff1a; brew --version第二步&#xff1a;安装 No…

摄影后期学什么_好学吗?

当你按下相机快门&#xff0c;捕捉到那珍贵的瞬间&#xff0c;摄影可还没画上句号哦&#xff01;摄影后期就像是一场神奇的魔法秀&#xff0c;能让你的照片从平凡瞬间变身惊艳大片。那在这场魔法之旅中&#xff0c;咱们得学习哪些厉害的法术呢&#xff1f; 先来说说光影调整这…

短视频矩阵系统saas源码 ---技术源头搭建部署

短视频矩阵系统源码 短视频矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框架&#xff0c;用于将数据模型、Web应用…

mock.js介绍

mock.js http://mockjs.com/ 1、mock的介绍 *** 生成随机数据&#xff0c;拦截 Ajax 请求。** 通过随机数据&#xff0c;模拟各种场景&#xff1b;不需要修改既有代码&#xff0c;就可以拦截 Ajax 请求&#xff0c;返回模拟的响应数据&#xff1b;支持生成随机的文本、数字…

Spring Authorization Server入门 (十二) 实现授权码模式使用前后端分离的登录页面

基于Spring Session的前后端分离文章已发布&#xff1a;《Spring Authorization Server基于Spring Session的前后端分离实现》 2023-12-01修改&#xff1a;在session-data-redis(Github)分支中添加了基于spring-session-data-redis的实现&#xff0c;无需借助nonceId来保持认证…

Codeforces Round 991 (Div. 3) F. Maximum modulo equality(区间gcd模板)

思路&#xff1a;我们由题意可以知道我们只需要维护区间gcd即可&#xff0c;因为差分一下后&#xff0c;维护的差分数组的区间gcd即为原数组所要求的值 #include<bits/stdc.h>using namespace std;typedef long long ll; typedef pair<ll, ll>PII; const int N 2…

智创 AI 新视界 -- 优化 AI 模型训练效率的策略与技巧(16 - 1)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

基于 NXP S32K312+FS23 的汽车通用评估板方案

S32K3 系列是 NXP 推出的面向汽车电子和工业应用的微控制器&#xff0c;基于 ARMCortex-M7 内核&#xff0c;支持单核、双核和锁步内核配置。S32K3 系列具有内核、内存和外设数量方面的可扩展性&#xff0c;符合 ISO26262 标准&#xff0c;能达到 ASIL B/D 安全等级&#xff0c…

Y20030002 微信+Java+Jsp+Servlet+MySQL的问卷调查小程序的设计与实现 源代码 配置文档 全套资料

问卷调查微信小程序 1.摘要2. 系统开的背景和意义3. 国内外研究现状4. 系统功能5.界面展示6.源码获取 1.摘要 摘 要&#xff1a;本文深入研究并实现了一个基于微信小程序的问卷调查系统。微信小程序问卷调查系统借助于微信小程序的便捷性和普及性&#xff0c;为用户提供了一个…

C语言程序设计P5-4【应用函数进行程序设计 | 第四节】——知识要点:数组作函数参数

知识要点&#xff1a;数组作函数参数 视频&#xff1a; 目录 一、任务分析 二、必备知识与理论 三、任务实施 一、任务分析 任务要求用选择法对数组中的 10 个整数按由小到大的顺序排序&#xff0c;前面在讲解数组时讲冒泡法排序曾提到选择法排序的思想。 所谓选择法就是…

C语言连接数据库

文章目录 一、初始化数据库二、创建数据库连接三、执行增删改查语句1、增删改2、查 四、执行增删改查语句 接下来我简单的介绍一下怎么用C语言连接数据库。 初始化数据库创建数据库连接执行增删改查语句关闭数据库连接 一、初始化数据库 // 数据库初始化 MYSQL mysql; MYSQL* r…

微信小程序里的小游戏研发需要什么技术栈

研发小程序里的小游戏通常需要以下技术栈&#xff1a; 前端技术 HTML5 / CSS3&#xff1a;用于构建游戏的界面布局和样式。JavaScript&#xff1a;作为核心编程语言&#xff0c;实现游戏的逻辑和交互。小程序开发框架&#xff1a;如微信小程序的开发框架&#xff0c;了解其 API…

vue导出excel,自定义多级表头高度

先看结果&#xff1a; 1、我的列表由于我的是有动态列的&#xff0c;所以下面的代码会对动态列进行判断处理&#xff1a; 导出结果&#xff1a; 2、直接贴代码&#xff0c;我对1、2、3行做了适配业务处理 导入依赖&#xff0c;没有的直接搜一下安装&#xff0c;然后导入即可 …

【知识点】图与图论入门

何为图论 见名知意&#xff0c;图论 (Graph Theory) 就是研究 图 (Graph) 的数学理论和方法。图是一种抽象的数据结构&#xff0c;由 节点 (Node) 和 连接这些节点的 边 (Edge) 组成。图论在计算机科学、网络分析、物流、社会网络分析等领域有广泛的应用。 如下&#xff0c;这…

【AI模型对比】AI新宠Kimi与ChatGPT的全面对比:技术、性能、应用全揭秘

文章目录 Moss前沿AI技术背景Kimi人工智能的技术积淀ChatGPT的技术优势 详细对比列表模型研发Kimi大模型的研发历程ChatGPT的发展演进 参数规模与架构Kimi大模型的参数规模解析ChatGPT的参数体系 模型表现与局限性Kimi大模型的表现ChatGPT的表现 结论&#xff1a;如何选择适合自…

攻防世界 ctf刷题 新手区1-10

unserialize3 因为我上个笔记写了 php返序列化 所以先趁热打铁 看这个题目名字 我们就知道是 反序列化呀 因为flag有值所以 我们先输个 111 看看有没有线索 没线索但是这边 有个发现就是他是使用get方式传参的 可能他会把我们的输入 进行传入后台有可能进行反…

FlightGear+MATLAB+飞行手柄实现实时飞控视景系统

文章目录 一、软件配置二、FlightGearMATLAB联合仿真第一步 复制文件第二步 新建文件夹第三步 打开demo第四步 demo说明第五步 打开Simulink第六步 连接FlightGear第七步 设置FlightGear第八步 生成FlightGear连接文件FlightGear的设置Network的设置File的设置生成.bat文件 第九…

AC高可靠

在真实网络中&#xff0c;一台AC可能要管理上百台AP&#xff0c;因此对与AC的可靠性要求目前有4种解决方案 分别是VRRP双机热备&#xff0c;双链路冷备&#xff0c;双链路热备&#xff0c;N1备份 简述 VRRP双机热备份 主备AC两个独立的IP地址&#xff0c;通过VRRP对外虚拟为同…

动手学深度学习d2l包M4芯片 gpu加速

conda创建环境 CONDA_SUBDIRosx-arm64 conda create -n ml python3.9 -c conda-forge conda env config vars set CONDA_SUBDIRosx-arm64 conda activate mlpip安装包 pip install --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/n…