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.pswp.cn/web/62400.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62400.shtml
英文地址,請注明出處:http://en.pswp.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 卷積算子…