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

news2025/1/9 21:48:58

系统服务

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

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

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

相关文章

windows将文件推给Android真机/实机

记录一下 因为以前只试过从真机实机中将文件推给windows 但是从windows只简单复制粘贴的话会一直报错。 1.电脑安装adb 2.手机开启开发者模式 usb调试 3.usb连接选择文件传输 4.推送命令adb push 文件路径 /sdcard/download 步骤1和2和3不作赘述&#xff0c;可以搜相关配置教程…

区块链钱包开发:全面功能设计方案解析

区块链钱包是连接用户与区块链世界的核心工具&#xff0c;为用户提供了存储、管理和交易加密资产的便捷途径。随着区块链应用的广泛普及&#xff0c;钱包的功能需求和技术复杂度也在不断增加。如何设计和开发一款功能全面、安全可靠的区块链钱包&#xff0c;成为区块链项目成功…

超详细搭建PhpStorm+PhpStudy开发环境

刚开始接触PHP开发&#xff0c;搭建开发环境是第一步&#xff0c;网上下载PhpStorm和PhpStudy软件&#xff0c;怎样安装和激活就不详细说了&#xff0c;我们重点来看一看怎样搭配这两个开发环境。 前提&#xff1a;现在假设你已经安装完PhpStorm和PhpStudy软件。 我的PhpStor…

双十二投影仪推品牌推荐哪个?当贝卓越画质与智能系统的完美结合

双十二购物狂欢节临近&#xff0c;加上国补至高20%的优惠&#xff0c;这场投影仪年末战役也成为了各家品牌必争之地。越来越多的朋友都在考虑入手一台投影仪&#xff0c;打造专属的私人家庭影院&#xff0c;在家也能享受影院级影音乐趣。在众多投影仪品牌中&#xff0c;当贝投影…

技术岗面试准备总结

该总结除个人经验外&#xff0c;参考&#xff1a; 求职】手把手教你写求职简历&#xff0c;8分钟掌握简历的套路科注意事项&#xff01; 【求职干货】从面试官的角度&#xff0c;教你如何面试中小企业研发岗 面试一家公司&#xff0c;需要做哪些准备&#xff1f;&#xff08;…

Quill富文本实现内容自定义格式format

在使用quill富文本编辑器时&#xff0c;我们输入文本会被作为类似DOM节点的数据对象存储在内部&#xff0c;渲染时生成相应的DOM节点。这是quill的文档模型Parchment,它提供了多种内容节点类型&#xff0c;如Inline \ Block \ Embed等。 quill 扩展了 Parchment 提供的的基础类…

MQTT知识要点

介绍 MQTT (Message Queuing Telemetry Transport) 是一种轻量级的发布/订阅消息协议&#xff0c;专为低带宽环境M2M而设计。是物联网&#xff08;IoT&#xff09;最常用的消息传递协议。 轻量高效双向通信可以扩展以连接数百万台物联网设备。可靠的消息传递&#xff08;支持…

Linux -基础指令3

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【Linux】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 文章目录 &#x1f4da; 前言⏰ 时间相关&#x1f511; 概念一&#xff1a;日志 date&#x1f511; 概念二&#xff1a;时间戳 Cal &#x1f50d; 查找findwhichwhereisgr…

如何在 Redis 上配置 SSL/TLS ?

在数据泄露非常普遍的时代&#xff0c;数据安全传输对于各种规模的应用程序来说都变得至关重要。 Redis 作为一种非常流行的内存数据结构存储&#xff0c;被广泛用于缓存、消息代理和数据库。鉴于其广泛使用&#xff0c;使用SSL/TLS 加密保护 Redis 连接&#xff0c;对于保护敏…

选择大于一切!Amazon Bedrock重塑大模型领域的竞合规则

文 | 智能相对论 作者 | 陈泊丞 早些年&#xff0c;“百模大战”打得火热&#xff0c;但是随着模型发展的深入&#xff0c;人们发现如果只是争抢市场份额&#xff0c;意义并不大&#xff0c;产业链上下游需要协作共进&#xff0c;才能为市场和社会提供更优质的生成式AI服务。…

MySQL 性能优化详解

MySQL 性能优化详解 硬件升级系统配置优化调整buffer_pool数据预热降低日志的磁盘落盘 表结构设计优化SQL语句及索引优化SQL优化实战案例 MySQL性能优化我们可以从以下四个维度考虑&#xff1a;硬件升级、系统配置、表结构设计、SQL语句和索引。 从成本上来说&#xff1a;硬件升…

RK3568平台开发系列讲解(pinctrl 子系统篇)pinctrl_debug

🚀返回专栏总目录 文章目录 1. Overview2. debug信息2.1 pinctrl-devices2.2. pinctrl-handles2.3. pinctrl-handles3. debug信息3.1. 查看(pinctrl_register_pins)注册了哪些pins3.2. 查看pin groups;3.3. 查看每种functions所占用的gpio groups信息:3.4. pinconf沉淀、…

目标跟踪算法:SORT、卡尔曼滤波、匈牙利算法

目录 1 目标检测 2 卡尔曼滤波 3《从放弃到精通&#xff01;卡尔曼滤波从理论到实践》视频简单学习笔记 3.1 入门 3.2 进阶 3.2.1 状态空间表达式 3.2.2 高斯分布 3.3 放弃 3.4 精通 4 匈牙利算法 5 《【运筹学】-指派问题&#xff08;匈牙利算法&#xff09;》视…

5G Multicast/Broadcast Services(MBS) (八) MBS多播DRX

这里简单看下多播DRX的内容。 1 MBS multicast 对于MBS多播,RRC可配置 MAC entity使其具备per G-RNTI 或per G-CS-RNTI DRX 功能,从而控制 UE 对 MAC entity的G-RNTI和G-CS-RNTI 的 PDCCH 监听活动。当处于 RRC_CONNECTED 状态时,如果为 G-RNTI 或 G-CS-RNTI 配置了多播…

【JavaEE】多线程(7)

一、JUC的常见类 JUC→java.util.concurrent&#xff0c;放了和多线程相关的组件 1.1 Callable 接口 看以下从计算从1加到1000的代码&#xff1a; public class Demo {public static int sum;public static void main(String[] args) throws InterruptedException {Thread …

宝塔面板-java项目 spring 无法正常启动 java spring 宝塔 没有显示日志 问题解决方案-spring项目宝塔面板无日志

宝塔面板-java项目 spring 无法正常启动 java spring 宝塔 没有显示日志 -优雅草央千澈问题解决方案-spring项目宝塔面板无日志 问题描述 昨天安排了一个新项目的开发&#xff0c;搭建兄弟搭建完但是通信有问题&#xff0c;spring服务无法正常启动&#xff0c;于是交代后端兄…

关于一些游戏需要转区的方法

当玩非国区游戏时有时会出现乱码导致无法启动&#xff0c;此时多半需要转区来进行解决 1.下载转区软件 【转区工具】Locale Emulator 下载链接&#xff1a;Locale.Emulator.2.5.0.1.zip - 蓝奏云 用此软件可以解决大部分问题。 2.进行系统转区 首先打开控制面板选择时间与…

浅谈网络 | 应用层之云网络隔离GRE/VXLAN

目录 前言GRE 隧道技术VXLANGRE/VXLAN接入云平台 前言 之前提到&#xff0c;为云平台中的租户实现隔离时&#xff0c;常用的策略是基于 VLAN。然而&#xff0c;VLAN 只有 12 位&#xff0c;共支持 4096 个 ID&#xff0c;这在最初设计时看似足够&#xff0c;但随着云计算的快速…

【Python】批量下载抖音视频

1、代码 import os import re from concurrent.futures import ThreadPoolExecutor import requestsdef get_urls(max_cursor):# 请求头 &#xff08;页面获取&#xff09;headers {Cookie: ,Referer: ,User-Agent: }# 请求地址&#xff08;页面获取&#xff09;url # max_c…

刚入行Java,如何深入学习JVM底层原理?

对于JVM&#xff0c;我想大部分小伙伴都是要面试了才会去学&#xff0c;其余时间基本不会去看&#xff08;掐指一算&#xff0c;你们书架上面的深入理解Java虚拟机第三版应该都一层灰了吧【手动狗头】&#xff09;。但值得一说的是&#xff0c;当你工作多年之后&#xff0c;你遇…