第 13 章 守护进程

守护进程

在 Linux 系统编程中,守护进程(daemon)是一种在后台运行的进程,通常是由系统启动时自动启动的。它们通常没有与用户交互的终端,而是在系统运行时执行各种系统任务,如网络服务、日志记录等。

shell 终端是一个会话
一个会话里面可以有多个进程组
一个进程组由一个进程或多个进程组成
一个进程可以有多个线程

会话

一个 shell 登录就可看为一个会话
那么怎么在一个会话中产生多个进程呢?
例如使用管道符,前面的输出为后面的输入

gaowanlu@DESKTOP-QDLGRDB:/$ ls | more
README.md
SUMMARY.md
bing-fa-bian-cheng
c++-even-better
cao-zuo-xi-tong
ji-suan-ji-wang-luo
server-dev
she-ji-mo-shi
shu-ju-jie-gou-yu-suan-fa
shu-ju-ku
testcode

前台进程组与后台进程组:最多只能有一个前台进程组

会话的标识 sid

会话的标识为 sid

#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);//创建新对话
pid_t getsid(pid_t pid);//获取指定进程sid

setsid 用于创建一个会话在调用 setsid 的进程不是其所在进程组的 leader 的话。当进程使用 setsid 后成为新的进程组的 leader 且脱离控制终端、且称为新对话的 leader

守护进程脱离控制终端 tty:? ,PID、PGID、SID 相同

gaowanlu@DESKTOP-QDLGRDB:/$ ps axj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 ?            0 Ssl      0   0:00 /init
    1     6     1     1 ?            0 Sl       0   0:00 plan9 --control-socket 6 --log-level 4 --server-fd 7 --pipe-fd 9 --socket-
    1     9     9     9 tty1         0 Ss       0   0:00 /init
    9    10    10     9 tty1         0 S     1000   0:00 -bash
    1    96    96    96 tty2         0 Ss       0   0:00 /init
   96    97    97    96 tty2         0 S     1000   0:00 -bash
   97   724   724    96 tty2         0 S        0   0:00 su gaowanlu
  724   725   725    96 tty2         0 S     1000   0:00 bash
    1   921   921   921 tty3         0 Ss       0   0:00 /init
  921   922   922   921 tty3         0 S     1000   0:00 -bash
  922   993   993   921 tty3         0 R     1000   0:00 ps axj

进程组 gid

// set/get process group
#include <sys/types.h>
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);                 /* POSIX.1 version */
pid_t getpgrp(pid_t pid);            /* BSD version */
int setpgrp(void);                   /* System V version */
int setpgrp(pid_t pid, pid_t pgid);  /* BSD version */

守护进程实例

创建一个不停在/tmp/out 文件中追加内容的守护进程

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;

#define FILENAME "/tmp/out"

void myfunc()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    if (pid > 0)
    {
        exit(0);
    }
    // 子进程
    int fd = open("/dev/null", O_RDWR);
    if (fd < 0)
    {
        perror("open error");
        exit(1);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2)
    {
        close(fd);
    }
    setsid();
    chdir("/"); // 设置工作目录位置
}

int main(int argc, char **argv)
{
    myfunc();
    FILE *fp;
    fp = fopen(FILENAME, "w");
    if (fp == nullptr)
    {
        perror("fopen error");
        exit(1);
    }
    for (long long i = 0;; i++)
    {
        fprintf(fp, "%lld\n", i);
        fflush(fp);
        sleep(1);
    }
    return 0;
}

// 父进程提前结束
// 子进程setsid称为守护进程,PPID变为1即init进程接管

运行

gaowanlu@DESKTOP-QDLGRDB:/$ ./main
gaowanlu@DESKTOP-QDLGRDB:/$ tail -f /tmp/out
0
1
2
3
4
5
6
7
8
^C
gaowanlu@DESKTOP-QDLGRDB:/$ ps axj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 ?            0 Ssl      0   0:00 /init
    1  1133  1133  1133 ?            0 Ss    1000   0:00 ./main
gaowanlu@DESKTOP-QDLGRDB:/$ kill -9 1133

系统日志

每个应用都有必要去写系统日志,在守护进程中在运行过程中记录有效的过程信息
一般存放在 /var/log 目录下

gaowanlu@DESKTOP-QDLGRDB:/$ ls /var/log
alternatives.log  apt   dist-upgrade  journal    lastlog  private               unattended-upgrades
apache2           btmp  dpkg.log      landscape  mysql    ubuntu-advantage.log  wtmp

syslogd 服务,将要写的日志内容提交给 syslogd 服务

gaowanlu@DESKTOP-QDLGRDB:/$ ps axj | grep "syslogd"
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
  922  1141  1140   921 tty3         0 S     1000   0:00 grep syslogd

相关函数

//send messages to the system logger
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
//option 与 facility可以看man手册
void syslog(int priority, const char *format, ...);
//priority 看 man手册
void closelog(void);
void vsyslog(int priority, const char *format, va_list ap);
openlog->syslog->closelog

option:  LOG_CONS |LOG_NDELAY| LOG_NOWAIT |  LOG_ODELAY  |  LOG_PERROR |  LOG_PID
facility: LOG_AUTH  |LOG_AUTHPRIV | LOG_CRON |LOG_DAEMON|LOG_FTP  | LOG_KERN   | LOG_LOCAL0 through LOG_LOCAL7| LOG_LPR  |  LOG_MAIL| LOG_NEWS | LOG_SYSLOG | LOG_USER (default)|LOG_UUCP
priority: LOG_CRIT|LOG_ERR | LOG_WARNING | LOG_NOTICE | LOG_INFO | LOG_DEBUG

实例

#include <iostream>
#include <syslog.h>
#include <unistd.h>

using namespace std;

int main(int argc, char **argv)
{
    openlog("gaowanlu", LOG_PID, LOG_DAEMON);
    for (int i = 0; i < 10; i++)
    {
        syslog(LOG_INFO, "%d", i);
        sleep(1);
    }
    closelog();
    return 0;
}
//ubuntu
root@drecbb4udzdboiei-0626900:/tmp# make main
g++     main.cpp   -o main
root@drecbb4udzdboiei-0626900:/tmp# ./main
root@drecbb4udzdboiei-0626900:/tmp# tail /var/log/syslog
Feb 22 00:52:41 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 0
Feb 22 00:52:42 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 1
Feb 22 00:52:43 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 2
Feb 22 00:52:44 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 3
Feb 22 00:52:45 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 4
Feb 22 00:52:46 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 5
Feb 22 00:52:47 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 6
Feb 22 00:52:48 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 7
Feb 22 00:52:49 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 8
Feb 22 00:52:50 drecbb4udzdboiei-0626900 gaowanlu[4046718]: 9
root@drecbb4udzdboiei-0626900:/tmp#

syslog 也有相关配置、配置文件一般在

/etc/rsyslog.conf

单例守护进程

有时要确保某些守护进程只运行一个实例,不能有多个?这怎么实现
一般是利用锁文件/var/run/name.pid

root@drecbb4udzdboiei-0626900:/etc# cat /var/run/docker.pid
1034
root@drecbb4udzdboiei-0626900:/etc# ps axf | grep "docker"
   1034 ?        Ssl   10:11 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
4046887 pts/0    R+     0:00          \_ grep --color=auto docker
root@drecbb4udzdboiei-0626900:/etc#

会对比 name.pid 内容中的 pid 对应想在的响应进程 pid,对比是否运行的 name 程序

root@drecbb4udzdboiei-0626900:/etc# service docker start
root@drecbb4udzdboiei-0626900:/etc# service docker start
root@drecbb4udzdboiei-0626900:/etc# cat /var/run/docker.pid
1034root@drecbb4udzdboiei-0626900:/etc#

启动脚本文件

/etc/rc*.d

但现在都使用 systemd
使用 systemd 设置开机启动

# 检查systemd
root@drecbb4udzdboiei-0626900:/etc# systemd --version
systemd 249 (249.11-0ubuntu3.6)
+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified
# 编写自己的自启动service
root@drecbb4udzdboiei-0626900:/etc/systemd/system# ls
apache2.service                             dbus-org.freedesktop.timesync1.service  multi-user.target.wants         sshd.service
apache-htcacheclean.service                 default.target.wants                    network-online.target.wants     sudo.service
cloud-init.target.wants                     emergency.target.wants                  open-vm-tools.service.requires  sysinit.target.wants
cloudResetPwdAgent.service                  getty.target.wants                      paths.target.wants              syslog.service
cloudResetPwdUpdateAgent.service            graphical.target.wants                  rescue.target.wants             timers.target.wants
dbus-org.freedesktop.ModemManager1.service  iscsi.service                           sleep.target.wants              vmtoolsd.service
dbus-org.freedesktop.resolve1.service       mdmonitor.service.wants                 sockets.target.wants
dbus-org.freedesktop.thermald.service       multipath-tools.service                 sshd-keygen@.service.d

如写一个/etc/systemd/system/autostart.service

#/etc/systemd/system/autostart.service
[Unit]
Description=python_detection
Documentation=
After=network.target
Wants=
Requires=

[Service]
ExecStart=/home/nvidia/autostart.sh
ExecStop=
ExecReload=/home/nvidia/autostart.sh
Type=forking

[Install]
WantedBy=multi-user.target

/home/nvidia/autostart.sh

#!/bin/bash
sleep 10

启动与设置自启动

$sudo systemctl start autostart.service
$sudo journalctl -f -u autostart.service # 查看程序输出
$sudo systemctl enable autostart.service # 设置开机自启

service 文件格式,Unit、Service、Install 必须要有

Description:运行软件描述
Documentation:软件的文档
After:因为软件的启动通常依赖于其他软件,这里是指定在哪个服务被启动之后再启动,设置优先级
Wants:弱依赖于某个服务,目标服务的运行状态可以影响到本软件但不会决定本软件运行状态
Requires:强依赖某个服务,目标服务的状态可以决定本软件运行。
ExecStart:执行命令
ExecStop:停止执行命令
ExecReload:重启时的命令
Type:软件运行方式,默认为simple
WantedBy:这里相当于设置软件,选择运行在linux的哪个运行级别,只是在systemd中不在有运行级别概念,但是这里权当这么理解。

问题总结

代码样例都存在问题、因为没有解决信号杀死问题等