Linux systemd & init.d
一、守护进程
在Linux进程中,普通程序会随着自己所属的Shell关闭而终止,如果需要实现像windows服务程序一样的效果,第一步就需要将普通进程变成守护进程。
守护进程特征:
- 生命周期长,在系统启动的时候被创建并且一直运行知道系统被关闭。
- 守护进程不需要终端,它是在后台运行的
创建一个守护进程步骤:
- 执行一个fork,之后父进程退出。这样daemon成为了init进程的子进程
- 子进程调用setsid开启一个新会话,释放它和终端之间的关联
- 清楚进程的umask,确保daemon拥有所需的权限
- 修改进程当前工作目录(如果不这样做,可能导致无法卸载某文件系统)
- 关闭daemon从父进程那里继承来的文件描述符
- 将标准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