Linux systemd & init.d

一、守护进程

在Linux进程中,普通程序会随着自己所属的Shell关闭而终止,如果需要实现像windows服务程序一样的效果,第一步就需要将普通进程变成守护进程。

守护进程特征:

  • 生命周期长,在系统启动的时候被创建并且一直运行知道系统被关闭。
  • 守护进程不需要终端,它是在后台运行的

创建一个守护进程步骤:

  1. 执行一个fork,之后父进程退出。这样daemon成为了init进程的子进程
  2. 子进程调用setsid开启一个新会话,释放它和终端之间的关联
  3. 清楚进程的umask,确保daemon拥有所需的权限
  4. 修改进程当前工作目录(如果不这样做,可能导致无法卸载某文件系统)
  5. 关闭daemon从父进程那里继承来的文件描述符
  6. 将标准IO重定向到/dev/null (dup函数)

在Linux新版本中提供了一个系统API daemon将上面的所有步骤封装了起来, 第一个参数如果是0将改变工作目录到根目录,如果第二个参数是0将标准IO重定向到/dev/null

if (daemon(0, 0) != 0) {
        printf("daemon function error, errno : %ld", errno);
        return -1;
}

 

 

二、init.d

在Linux中如果需要让守护进程以服务启动,需要在/etc/init.d 中编写一个自己服务脚本

#!/bin/bash
# Provides:             daemon_test
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Description:  This shell script takes care of starting and stopping dennis
# Hu Dennis created on Sep. 24th, 2010
#
 
#the service name  for example: dennis
 
#the full path and name of the daemon program
#Warning: The name of executable file must be identical with service name
PATH=/sbin:/usr/sbin:/bin:/usr/bin
NAME=daemon_test
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
DAEMON=/usr/bin/$NAME
DESC="deamon_test"
 
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
 
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
 
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
 
# start function
do_start() {
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
        || return 1
    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
        || return 2
    # Add code here, if necessary, that waits for the process to be ready
    # to handle requests from services started subsequently which depend
    # on this one.  As a last resort, sleep for some time.
}
 
#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
   #   other if a failure occurred
    start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
    # Wait for children to finish too if this is a daemon that forks
    # and if the daemon is only ever run from this initscript.
    # If the above conditions are not satisfied then add some other code
    # that waits for the process to drop all resources that could be
    # needed by services started subsequently.  A last resort is to
    # sleep for some time.
    start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
    [ "$?" = 2 ] && return 2
    # Many daemons don't delete their pidfiles when they exit.
    rm -f $PIDFILE
    return "$RETVAL"
}
 
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
    #
    # If the daemon can reload its configuration without
    # restarting (for example, when it is sent a SIGHUP),
    # then implement that here.
    #
    start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
    return 0
}
 
#stop function
stop() {
    log_daemon_msg "Stopping $SNAME service..."
    killproc -p $SNAME
    rm -rf /var/lock/subsys/$SNAME
}
 
case "$1" in
  start)
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  stop)
    [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  status)
    status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
    ;;
  #reload|force-reload)
    #
    # If do_reload() is not implemented then leave this commented out
    # and leave 'force-reload' as an alias for 'restart'.
    #
    #log_daemon_msg "Reloading $DESC" "$NAME"
    #do_reload
    #log_end_msg $?
    #;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
            0) log_end_msg 0 ;;
            1) log_end_msg 1 ;; # Old process is still running
            *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
        # Failed to stop
        log_end_msg 1
        ;;
    esac
    ;;
  *)
    #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    exit 3
    ;;
esac
 
:

 

实际守护进程代码

#if !defined(DEBUG) && defined(OS_LINUX)
    // Converts the current process into a daemon.
    // daemon first arg : 0 if changes the process's current working dieactory
    // to the root dircatory
    // daemon second arg : 0 if changes the standard input, standard output
    // and standard error to /dev/null
    // detail : http://man7.org/linux/man-pages/man3/daemon.3.html
    if (daemon(0, 0) != 0) {
        printf("daemon function error, errno : %ld", errno);
        return -1;
    }
 
    // Save pid to a file that will be used later in service script.
    using namespace std::experimental;
    filesystem::path appPath(argv[0]);
    std::string pidFile = "/var/run" + appPath.filename().u8string() + ".pid";
    auto fd = open(pidFile.data(), O_WRONLY | O_CREAT | O_TRUNC);
    if (fd == -1) {
        printf("open %s failed, errno : ", pidFile.data(), errno);
        return -1;
    }
 
    std::string pidStr = std::to_string(getpid());
    if (write(fd, pidStr.c_str(), pidStr.size()) < 0) {
        printf("write pid failed, errno : ", errno);
        return -1;
    }
    close(fd);
#endif
# 添加一个服务
update-rc.d daemon_test defaults
# 卸载服务
update-rc.d -f daemon_test remove

 

在编写完上面的服务脚本之后,就可以用添加一个服务命令update-rc.d daemon_test defaults,默认开始在2,3,4,5 ,结束在0,1,6

# 开始一个服务
service daemon_test start
# 停止一个服务
service daemon_test stop
# 重启一个服务
service daemon_test restart

 

 

三、使用Linux Systemd进程服务注册(推荐使用)

systemd 是一种新的Linux服务管理方法,通过systemd实现linux服务程序需要编写systemd.service放到/etc/systemd/system/ 或者/usr/lib/systemd/system目录下

XXX.service 例子

[Unit]
Description=RemoteClient server daemon
Wants=network.target
Before=network.target network.service
 
[Service]
Type=simple
ExecStart=/usr/local/bin/remoteclient/core/remote_client_core
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
RestartSec=5s
TimeoutSec=1s
 
[Install]
WantedBy=multi-user.target

 

Unit记录服务文件的通用信息

  • Description:对于本服务的描述
  • Before:记录者这个服务必须在什么服务之前启动
  • After:记录者这个服务必须在什么服务开始之后启动
  • Requires:这个服务启动了,那么它“需要”的服务也会被启动; 它“需要”的服务被停止了,它自己也活不了。但是请注意,这个设定并不能控制某服务与它“需要”的服务的启动顺序(启动顺序是另外控制的),                      即 Systemd 不是先启动 Requires 再启动本服务,而是在本服务被激活时,并行启动两者。于是会产生争分夺秒的问题,如果 Requires 先启动成功,那么皆大欢喜; 如果 Requires 启动得慢,那本服                      务就会失败(Systemd 没有自动重试)。所以为了系统的健壮性,不建议使用这个标记,而建议使用 Wants 标记。可以使用多个 Requires。
  • Want:本服务启动了,他“Want”的服务也会被启动,就算“Want”服务启动失败了对本服务也不会有影响

Service记录着服务程序对一些特定行为和属性

  • Type(服务启动类型)
 –  如果设为 simple (当设置了 ExecStart= 、 但是没有设置 Type= 与 BusName= 时,这是默认值), 那么 ExecStart= 进程就是该服务的主进程, 并且 systemd 会认为在创建了该服务的主服务进程之                          后,该服务就已经启动完成。 如果此进程需要为系统中的其他进程提供服务, 那么必须在该服务启动之前先建立好通信渠道(例如套接字), 这样,在创建主服务进程之后、执行主服务进程之前,即可启动                      后继单元, 从而加快了后继单元的启动速度。 这就意味着对于 simple 类型的服务来说, 即使不能成功调用主服务进程(例如 User= 不存在、或者二进制可执行文件不存在), systemctl start 也仍然会执                    行成功。

 - exec 与 simple 类似,不同之处在于, 只有在该服务的主服务进程执行完成之后,systemd 才会认为该服务启动完成。 其他后继单元必须一直阻塞到这个时间点之后才能继续启动。换句话说, simple 表                  示当 fork() 函数返回时,即算是启动完成,而 exec 则表示仅在 fork() 与 execve() 函数都执行成功时,才算是启动完成。 这就意味着对于 exec 类型的服务来说, 如果不能成功调用主服务进程(例                        如 User= 不存在、或者二进制可执行文件不存在), 那么 systemctl start 将会执行失败。

 - 如果设为 forking ,那么表示 ExecStart= 进程将会在启动过程中使用 fork() 系统调用。 也就是当所有通信渠道都已建好、启动亦已成功之后,父进程将会退出,而子进程将作为主服务进程继续运行。                  这是传统UNIX守护进程的经典做法。 在这种情况下,systemd 会认为在父进程退出之后,该服务就已经启动完成。 如果使用了此种类型,那么建议同时设置 PIDFile= 选项,以帮助 systemd 准确可靠的定                  位该服务的主进程。 systemd 将会在父进程退出之后 立即开始启动后继单元。

 - oneshot 与 simple 类似,不同之处在于, 只有在该服务的主服务进程退出之后,systemd 才会认为该服务启动完成,才会开始启动后继单元。 此种类型的服务通常需要设置 RemainAfterExit= 选项。                  当 Type= 与 ExecStart= 都没有设置时, Type=oneshot 就是默认值。

 - dbus 与 simple 类似,不同之处在于, 该服务只有获得了 BusName= 指定的 D-Bus 名称之后,systemd 才会认为该服务启动完成,才会开始启动后继单元。 设为此类型相当于隐含的依赖                                             于 dbus.socket 单元。 当设置了 BusName= 时, 此类型就是默认值。

 - notify 与 exec 类似,不同之处在于, 该服务将会在启动完成之后通过 sd_notify(3) 之类的接口发送一个通知消息。systemd 将会在启动后继单元之前, 首先确保该进程已经成功的发送了这个消息。如果设               为此类型,那么下文的 NotifyAccess= 将只能设为非 none 值。如果未设置 NotifyAccess= 选项、或者已经被明确设为 none ,那么将会被自动强制修改为 main 。注意,目前 Type=notify 尚不能                       与 PrivateNetwork=yes 一起使用。

 - idle 与 simple 类似,不同之处在于, 服务进程将会被延迟到所有活动任务都完成之后再执行。 这样可以避免控制台上的状态信息与shell脚本的输出混杂在一起。 注意:(1)仅可用于改善控制台输出,切                     勿将 其用于不同单元之间的排序工具; (2)延迟最多不超过5秒, 超时后将无条件的启动服务进程。
  • xecStart (在启动该服务时需要执行的 命令行(命令+参数))
  • ExecStop (在停止服务时需要执行的命令行)
  • ExecReload(在重启服务时需要执行的命令行)
  • KillMode(表示进程终止方式,在ExecStop执行之后执行)
 - contorl-group 表示杀死该单元类所有cgroup进程
 - process 表示杀死主进程
 - mixed 表示首先向主进程发送 SIGTERM 信号(见下文), 然后再向该单元的 cgroup 内的所有其他进程发送 SIGKILL 信号
 - none 表示仅执行 ExecStop= 动作, 而不杀死任何进程。 这会导致即使单元已经停止, 但是该单元的 cgroup 依然一直存在, 直到其中的进程 全部死亡。
        杀死进程的时候, 第一步首先使用 KillSignal= 信号(默认为 SIGTERM) (如果 SendSIGHUP=yes ,那么还会立即紧跟一个 SIGHUP 信号), 若等候 TimeoutStopSec= 时间后, 进程仍然未被杀死, 则继续第           二步使用 SIGKILL 或 FinalKillSignal= 信号(除非 SendSIGKILL=no)强制杀死
  • Restart (当服务进程 正常退出、异常退出、被杀死、超时的时候, 是否重新启动该服务。)
- no (默认值,表示不会被重启)
- always (表示会被无条件重启)
- on_success (在服务进程正常退出的情况下重启,退出码为0)
- on_failure (在服务进程异常退出的情况下重启)
  • RestartSec (在多久之后重启服务)
  • TimeoutSec(同时设置服务启动超时时常和停止超时时常)
  • TimeoutStartSec(设置服务启动超时时间)
  • TimeoutStopSec(设置服务停止超时时间)
  • RuntimeMaxSec (允许服务运行的最大时常)
# 初始化某服务
systemctl enable xxx.service
# 卸载莫服务
systemctl disable xxx.service
 
# 开始某服务
systemctl start xxx.service
# 停止某服务
systemctl stop xxx.service

 

posted @ 2021-07-18 23:06  gd_沐辰  阅读(1059)  评论(0编辑  收藏  举报