openSUSE:Packaging init scripts

跳转至: 导航, 搜索


注意: 自 openSUSE 12.3/SUSE Linux Enterprise 12 起,我们开始使用 Systemd 来管理系统的引导,因此 SysVinit 风格的引导脚本已经被 Systemd 服务取代.用户学习此章并无意义.保留此文旨在针对软件包的维护者,在面临 Systemd 服务的确无法满足要求时,编写 SysVinit 风格的脚本并利用 Systemd 对其的兼容来实现目的(反正都需要 wrapper 那为什么不写个 SysVinit 也能用的);或者参与类似包(如 mysql/postgresql)维护时作为背景知识使用.
本指南教您如何制作、使用和打包 SysVinit 风格的引导脚本。这种引导脚本是 LSB 兼容的。(LSB:全称为 Linux 标准基础核心规范,目前版本是 3.2)

基本逻辑

包括 SysVinit 和 Systemd 在内的几乎全部引导系统,其程序逻辑均为:

  • 判断是否处于某种状态来决定是否启动服务,所谓“启用”/“禁用”,意即是否允许这种判断。SysVinit 中的状态即运行级别。
  • 进入某种状态,执行服务的启动(start),离开某种状态,执行服务的停止(stop)。只有这一种逻辑,只存在达到状态后启动离开状态前停止,不存在达到状态后停止离开状态前启动达到状态前启动/停止离开状态后启动/停止等合理想象。
  • 启动和停止总是成对出现。你可以没有对应的实现,但必须有这些关键字。
  • 状态下的执行,不保证执行成功。所以单纯依赖状态来启动/停止服务是错误的。因为状态本身并不意味着你的服务依赖的其他引导设施都是可用的。比如运行级别 5 意味着有网络和图形界面,但是你拔了网线就没图形界面了吗?显然不是。即达到状态后会执行联网,但不保证联网成功。
  • 特殊状态如关机、重启下并不执行任何东西。关机状态即为断电,断电还怎么执行?
    正确的做法是依赖于进入该特殊状态之后的状态来启动,依赖于离开该特殊状态之前的状态来停止。
    ------
    批注:开关机可视为在不同状态间的转换,比如开机为运行级别 0->1->2->3->5,关机为 5->3->2->1->0,重启为 5->3->2->1->6->1->2->3->5。所以比如想要实现关机自动关闭,开机自动启动,并不需要依赖 0 或 6 状态,只要保证进入 5 状态后启动,离开 5 状态前停止就能达到目的。


  • 服务的启动/停止是有次序的。可以是并发、可以是排队。并不是随机的。但是未知次序等于没有次序。你需要利用服务之间的依赖关系覆盖系统自动的排序,保证你的服务所需引导设施在其启动前均是可用的,在其停止前不能先行停止。

制作任何引导脚本必须遵守其程序逻辑。

命名

引导脚本的命名必须与 LSB 兼容,它必须被列示在 http://www.lanana.org/lsbreg/init/init.txt。http://www.lanana.org/lsbreg/instructions.html 描述了如何注册新名称。

openSUSE 除非是基础核心包,用户软件没必要去注册这种名称,一般命名就是包名,守护程序是包名 + 字母d(daemon)。

结构

/etc/init.d/skeleton 文件是一个空白的引导脚本,体现了引导脚本的结构。该文件也可以作为引导脚本的模板。

强烈建议打开这份文件再继续阅读下面内容,否则就会像听天书一样。

LSB 头部

引导脚本本身也是终端脚本,因此文件的开头和其他终端脚本的常规开头一样:

#!/bin/sh

或者

#!/bin/bash

接着通常是注释。注释应该说明作者、版权或授权许可等信息。作为引导脚本,还必须包含一个特殊的头部注释来提供关于引导脚本本身的元数据信息。

LSB 头部是由注释作为与上下文区分的边界,该头部的上界是:

### BEGIN INIT INFO

下界是:

### END INIT INFO

所有的 LSB 头部必须有这样的上下边界。

下面例子出自 /etc/init.d/esound :

 # 1995-2002, 2008 SUSE Linux Products GmbH, Nuernberg, Germany.
 # All rights reserved.
 #
 # Author: Stanislav Brabec, feedback to http://www.suse.de/feedback
 #
 ### BEGIN INIT INFO
 # Provides:          esound
 # Required-Start:    alsasound $remote_fs
 # Should-Start:      $network $portmap
 # Required-Stop:     alsasound $remote_fs
 # Should-Stop:       $network $portmap
 # Default-Start:     5
 # Default-Stop:
 # Short-Description: Sound daemon with network support
 # Description:       Starts esound server to allow remote access to sound
 #       card. To use esound locally, you do not need to start this
 #       server on boot. You should edit server settings before
 #       starting it via sysconfig editor: Network/Sound/Esound
 ### END INIT INFO

LSB 头部由以下几个部分组成:

# Provides: 行

LSB 头部的 # Provides: 行列出了该脚本服务能提供的全部引导设施。其他服务可以在它们的 # Required-Start:# Required-Stop: 行中引入我们提供的这些引导设施。

引导设施通常是守护程序的名字。
------
批注:必须是。因为现在我们依赖于 Systemd 的转换。如果 Provides 提供的引导设施与 /etc/init.d/name、(/usr)/sbin/rcname 的名字不同,就会导致转换出两个服务,其中 Provides 提供的那个是 name.service 的链接。世界已经够乱了。

如果多个软件包提供了相同的引导设施(例如 sendmail vs. postfix, dhcpcd vs. dhclient),两者的引导脚本应该提供相同的引导设施名称。

# Provides: boot_facility_1 [boot_facility_2...]

当引导脚本带着一个 start 参数运行时,即 /etc/init.d/NetworkManager start 这样,Provides 关键词指定的引导设施(们)就被视为可用的,因此其他要求这些引导设施的引导脚本就可以随后启动。当引导脚本带着一个 stop 参数运行时,即 /etc/init.d/NetworkManager stop 这样,Provides 关键词指定的引导设施就被视为不再可用。

# Required-Start: 行

LSB 头部的 # Required-Start: 行列出了启动此脚本服务必须的全部引导设施。

# Required-Start: boot_facility_1 [boot_facility_2...]
本行是必须的,
即使你的引导脚本不需要任何其他引导脚本提供的引导设施
------
批注:多数情况下不可能是空的,因为即使你的引导脚本不需要任何“其他脚本提供的引导设施”,你至少也需要如 $syslog(系统日志)和 $remote_fs(远程文件系统已挂载,即 init 3 状态,等同于 systemd 中的 multiuser.target)这些系统预设的引导设施,否则就可能产生非预期行为。

,也必须有这行,哪怕它是空的!

# Required-Stop: 行

LSB 头部的 # Required-Stop: 行列出了在停止该脚本服务之前必须不能先行停止的全部引导设施。

# Required-Stop: boot_facility_1 [boot_facility_2...]

本行是可选的,如果一个引导脚本不需要任何在它停止前不能被停止的引导设施,那么就不需要该行。

# Should-Start: 行

LSB 头部的 # Should-Start: 行列出了一旦可用就应该在启动该脚本服务前被启动的全部引导设施。目的是允许额外的即使不可用也不会造成该脚本服务失败的依赖。

# Should-Start: boot_facility_1 [boot_facility_2...]

本行为可选,如果一个引导脚本不需要启动任何可选依赖,那么可以没有本行。

# Should-Stop: 行

LSB 头部的 # Should-Stop: 行列出了一旦可用就不能在停止该脚本服务前停止的全部引导设施。目的是允许额外的即使不可用也不会造成该脚本服务失败的依赖。

# Should-Stop: boot_facility_1 [boot_facility_2...]

本行为可选,如果一个引导脚本不需要任何在它停止前不能停止的额外依赖,本行可以没有。

LSB 允许为本头部定义发行版专有的插件。这样的插件应该以 X-出品商标签- 开头,比如 X-SuSE-。以下是 openSUSE 发行版专有的插件:

# X-Start-Before: 行

LSB 头部的 # X-Start-Before: 行的意思是说本行中所提到的引导设施应该在该脚本启动前被启动。

# X-Start-Before: boot_facility_1 [boot_facility_2...]

本行是可选的,目前是 openSUSE 发行版专有的。

# X-Stop-After: 行

LSB 头部的 # X-Stop-After: 行的意思是说这里列出的引导设施应该在该脚本服务停止后也被停止。

# X-Stop-After: boot_facility_1 [boot_facility_2...]

本行是可选的,目前也是 openSUSE 发行版专有的。

# X-Start-Before:# X-Stop-After: 都允许脚本作者在新引导脚本中直接使用依赖而不去改动其他的比如系统引导脚本的内容。

# Default-Start: 行

LSB 头部的 # Default-Start: 行列出了一些运行级别,在这些运行级别下,该脚本服务应该默认启用。运行级别间以空格分隔。

# Default-Start: run_level_1 [run_level_2...]
每个在任何运行级别需要默认启动的 SysVinit 风格的引导脚本的 LSB 头部中都必须有本行。
只有 系统真正需要的脚本服务才可以指定运行级别。如果脚本服务不在任何运行级别下默认启动,那么就不需要提供本行。
------
批注:这通常也是不可能的。既然制作了引导脚本,目的就是想要启用它,那么就至少要有一个运行级别。否则,在任何运行级别下都不启用,即 chkconfig -a/d 也无法启用/禁用该脚本,该脚本只能手动启动和停止。这句话的本意是想说,如果你的目的只是做一个启动器,而不是一个后台服务,那么不要指定运行级别,手动启动/停止即可。openSUSE 并不会阻止开发者和用户定义新的后台服务,只要你判断该服务是运行某程序的系统真正需要的即可。


例如,如果一个服务只在运行级别 3,4,和 5 时默认启动,其引导脚本的 LSB 头部就应该指定:

# Default-Start: 3 4 5
运行级别 描述
0 关机断电。运行不了任何服务。
1 单用户 root 环境。一般只有忘记 root 密码才会想起这个运行级别。
2 多用户环境。但是没有网络文件系统(NFS,比如 /home 可能在云上)。简单说,没网。
3 完全多用户环境。有网。
4 系统未使用。保留。一般等同于 3。
5 有图形界面的多用户环境。多数时候我们都在这个级别下。
6 重启。类似于 0

# Default-Stop: 行

LSB 头部的 # Default-Stop: 列出了脚本服务没有默认启动的运行级别。运行级别间以空格分隔,且必须包含所有 # Default-Start: 行没有使用过的运行级别。

# Default-Stop: run_level_1 [run_level_2...]

每个需要在任何运行级别下默认启动的 SysVinit 风格的引导脚本必须在 LSB 头部包含本行 (如果有 # Default-Start: 行就必须有 # Default-Stop: 行,它们总是成对出现。)

例如,一个脚本服务只在运行级别 3,4, 和 5 默认启动,那么 LSB 头部的 # Default-Stop: 行就必须指定运行级别 0, 1, 2, 和 6:

# Default-Stop: 0 1 2 6
请注意,openSUSE 会忽略掉 # Default-Stop: 行,因为 openSUSE 的引导脚本设置使用了在 init.d(7) 的 man 帮助手册里说明的差分链接方案。但该行依然不能省略。

# Short-Description: 行

LSB 头部的 # Short-Description: 行提供了对引导脚本行为的简要介绍。本行只有一行且不能超过 80 个字符。

# Short-Description: 这是一个邮件服务器服务。(当然要用英文写)

openSUSE 下全部 SysVinit 风格的引导脚本都必须在 LSB 头部包含 # Short-Description: 行。它有点类似于 RPM spec 文件的 Summary: 标签。这是用于在 YaST 运行级别编辑器中显示的。

# Description: 行

LSB 头部的 # Description: 行提供了对引导脚本行为更加完整的描述。它可以是多行,后续的行必须以 '#' 加一个 tab (也就是至少两个空格不能多于四个空格)开头。一旦某行首不满足该条件就意味着多行描述的终止。这也是用来在 YaST 运行级别编辑器里显示的。

例如:

# Description: Bluetooth services for service discovery, authentication,
#              Human Interface Devices, etc.

openSUSE 下全部 SysVinit 风格的引导脚本必须在 LSB 头部包含 # Description: 行。它可以被视为 RPM spec 文件中的 %description 章节。

系统引导设施

根据 LSB 的要求, openSUSE 的引导脚本已经提供了一些预设的引导设施。它们在 /etc/insserv.conf 里。目前,有以下系统引导设施可供使用:

  • $local_fs — 全部本地文件系统均已挂载。大多数脚本服务都需要这个。
  • $remote_fs — 全部远程文件系统也已挂载。因为 /usr 可能是远程目录,许多脚本服务也应该需要这个。
  • $syslog — 系统日志可用。
  • $network — 底层网络设备(比如有线网卡)可用。
  • $named — 主机名解析可用。
  • $netdaemons — 全部网络守护进程均已运行。这在 LSB 1.2 中被移除了。但目前我们还提供它以便于向后兼容。
  • $time — 系统时间正确。
  • $portmap — SunRPC portmapping 服务可用。
  • $null — 强制性的空依赖。用于 Required-StopShould-Stop 否则 insserv 会假设依赖关系跟 *-Start 的一样。

除了定义在 /etc/insserv.conf 中的 LSB 兼容的系统引导设施,openSUSE 上还有以下系统引导设施:

  • $all 该引导设施表示服务应被插入到全部服务的最后。显然,全部使用该设施的服务将被分进同一组启动。</dd>
  • $null强制的空依赖。用于# Should-Stop:# Required-Stop: 否则 insserv(8) 会假设依赖跟 # Should-Start:

# Required-Start: 中指定的相同。

其他非系统引导设施在 LSB 头部中的 # Provides: 行里定义。

行为

/etc/init.d/skeleton 里面有详细的示范代码和一些有价值的注释。下面的例子取自 /etc/init.d/cron:

case "$1" in
    start)
        echo -n "Starting CRON daemon"
        startproc $CRON_BIN
        rc_status -v
        ;;
    stop)
        echo -n "Shutting down CRON daemon"
        killproc -TERM $CRON_BIN
        rc_status -v
        ;;
    try-restart)
        $0 status >/dev/null &&  $0 restart
        rc_status
        ;;
    restart)
        $0 stop
        $0 start
        rc_status
        ;;
    force-reload)
        echo -n "Reload service Cron"
        checkproc $CRON_BIN
        rc_status -v
        ;;
    reload)
        rc_status -v
        ;;
    status)
        echo -n "Checking for Cron: "
        checkproc $CRON_BIN
        rc_status -v
        ;;
    probe)
        ;;
    *)
        echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
        exit 1
        ;;
esac

根据 LSB 的定义,所有的引导脚本都必须知道如何处理如下行为:

  • start — 启动脚本服务。
  • stop — 停止脚本服务。
  • restart — 如果脚本服务已在运行,重启它。如果没在运行,启动它。
  • reload — 不重启脚本服务直接重新加载它的配置。
  • force-reload — 如果脚本服务支持的话,就重新加载配置,不然就重启。
  • status — 输出脚本服务目前的状态。
  • usage - 默认如果该引导脚本没有附加任何行为选项,它应该列出所有可用的行为以供用户选择。

所有引导脚本都必须支持 start, stop, restart, force-reload, 和 status 行为。reload 行为是可选支持的。

openSUSE 定义了如下额外行为:

  • try-restart — 只重启运行着的脚本服务。这个行为现在已被 LSB 1.9+ 收录。红帽也有一个类似的行为叫做 condrestart
  • probe — 探测是否有必要重加载配置文件。如果有必要,根据脚本服务提供的功能来重新加载或重启。如果没必要的话,什么也不做。这个行为目前是可选的。

SysVinit 风格的引导脚本应该支持 try-restartcondrestart 行为。这两个行为的目的是相同的,因此在行为上不能有所不同。实际操作上,强烈建议打包者将 try-restartcondrestart 在行为声明中作为同等选项:

try-restart|condrestart)
    $0 status
    if test $? = 0; then
        $0 restart
    else
        rc_reset
    fi
    rc_status
    ;;

你也可以添加其他行为,但你必须在 “status” 行为中交互输出它以让用户知道并能够使用。

退出状态代码

参考 LSB 定义。它为引导脚本定义了如下的退出状态代码(status 行为没有定义退出状态代码,详情见下)。

退出状态代码 描述
0 成功
1 一般或未定义错误
2 无效或多余的参数
3 没有这个行为 (例如 "reload")
4 用户权限不足
5 程序未安装
6 程序未配置
7 程序未运行
8-99 保留,以供 LSB 未来使用
100-149 保留,以供发行版使用
150-199 保留,以供应用程序使用
200-254 保留

对于所有其他的引导脚本行为,引导脚本必须在行为成功时返回退出状态 0。除了直接成功,下列情况也应该被认为是成功的:

  • 使用 force-reload 参数重启服务(不是重加载配置文件)。
  • 服务已在运行时 start
  • 服务不在运行时或已经终止时 stop
  • 服务不在运行时或已经终止时 restart
  • 服务不在运行时或已经终止时 condrestarttry-restart

状态函数

LSB 为脚本行为定义了如下返回状态代码:

任何一个程序在运行完退出后,都会返回一个状态,如成功/失败等。这个状态是以整数的方式来显示的,每个整数代表一个状态。这么做是为了方便后续程序检查前面程序的状态从而做出对应的响应。
------
批注:这在编程中是很普遍的,这个状态代码可以在编程时通过编程语言预定义的一个特定变量来获取。而对一般用户一般来说是不可见的,所以在终端运行程序后没有看到数字不要觉得很奇怪。


现有状态代码 描述
0 程序运行中或脚本服务没问题。
1 程序僵死,/var/run pid 进程文件仍存在。
2 程序僵死,/var/lock lock 文件仍存在。
3 程序没有运行。
4 程序或脚本服务的状态未知。
5-99 保留,以供 LSB 未来使用
100-149 保留给发行版使用
150-199 保留给应用程序使用
200-254 保留

定义在 /etc/rc.status 里的函数可以帮助记录、显示,和返回引导脚本中真实的 rc status 状态信息。它们可以这样引入到引导脚本中:

看不懂 SysVinit 脚本很大程度在于打开 skeleton 模板后发现了很多不知道用途的函数,又不知道是哪里定义的。rc_* 系列的函数都是在 /etc/rc.status 里定义的。这个文件是每个发行版自己写的,比如 openSUSE 实际上是把命令传给 systemd 再获取 systemd 返回的状态,而一些仍在使用 SysVinit 来引导系统的发行版可能是直接检查命令的返回状态。目的是返回上面那些 LSB 定义的标准返回代码;而 start/check/killproc 看起来像是一个函数,实际上是由 sysvinit-tools 软件包提供的一系列工具,跟 cp/mv 是一样的,可以用 man killproc 查看其用法。
------
批注:因此作为脚本的编写者,我们没有必要计较它们的具体实现是什么,只需要它们能够返回什么就好。这样就可以把脚本的难度降低为一般 Bash 脚本。


. /etc/rc.status

有如下函数可用:

rc_active

该函数通过检测链接来检查脚本服务是否启动。如果脚本服务在某运行级别下启动就返回 “0”,否则返回 “1”

rc_exit

该函数结束启动脚本,返回合适的退出状态代码。

rc_failed [num]

该函数将本地和全部的 rc status 设定为由 num 参数定义的值。默认使用 “1”

rc_check

该函数检测最后一条命令的退出状态值 ($?),如果退出状态的值不是 “0” 就把本地 rc_status 的状态设置为现在的值。然后本地 rc 状态的值不是 “0” 又会导致全局的 rc 状态被设定为本地的值。这个函数是在内部被其他 rc 状态函数使用的。

rc_reset

该函数把本地和全局的 rc 状态都重置为 “0”

rc_status [-r] [-s] [-u] [-v[num]]

该函数检查,设定和显示 rc 状态。默认它是静默的:它只调用 rc_check。因此,它必须带参数调用以显示想要的状态。下面是参数的定义:

  • -r 调用 rc_reset。这个选项必须和 -v 参数一起调用。命令 rc_status -v -r 检查,设定和显示目前的 rc 状态。之后呼叫 rc_reset 来重置。
  • -s 显示 “跳过” 然后设定状态为 “3”。它意味着是一个还没写出来的功能。
  • -u 显示 “无用” 然后设定状态为 “3”。它意味着是一个还没写出来的功能。
  • -v[num] 显示真实状态并且重置本地状态为 “0”。默认地,状态显示在目前的行上。num 参数定义了它应该显示在目前鼠标位置之上的第 num 行。

安装

引导脚本通常被视为软件包源代码,作为一个额外的 %source 文件,在 %install 部分安装。通常引导脚本不应该被标记为 %config 文件。

虽然引导脚本的位置在 /etc,但是它们是需要被执行而不是被配置的脚本。任何配置都应该通过 /etc/sysconfig/<service> 服务来做,而不是引导脚本自身。一个有力的豁免是已有的通过引导脚本进行配置的软件包。在这种情况下,遵照 配置文件 部分的规定,引导脚本应该被标记为 %config,以便于升级时能够保留用户配置。当然,用户最好迁移这些配置到一个新的 /etc/sysconfig/<service> 配置文件。

引导脚本必须有 0755 或者 0700 权限。

也应该有一个名为 rcname 的系统链接指向 /etc/init.d/name。根据服务安装的路径前缀,系统链接既可以放在 /sbin 也可以放在 /usr/sbin。这可以方便引导脚本的手动启动、停止和重启。

最后,引导脚本可以在软件包安装后启用,但必须在软件包卸载前禁用。升级之后也应该重启服务,但卸载前必须停用。宏 %fillup_and_insserv %insserv_force_if_yast %restart_on_update %insserv_cleanup,和 %stop_on_removal 是用来执行这些操作的。

注意 openSUSE 提供了 insserv 工具来启用或禁用引导脚本。参考 insserv(8) 的用户手册以获取更详细的信息。该工具也被 %fillup_and_insserv%insserv_force_if_yast,和 %insserv_cleanup 宏使用。

在 openSUSE 上引导脚本默认是禁用的,除非是那些提供最小化系统功能所需的引导脚本。因此 %fillup_and_insserv%insserv_force_if_yast 宏只供提供基础服务的软件包使用。

Spec 文件实例:

...

Requires(pre): %insserv_prereq %fillup_prereq

...

%install

...

install -D -m 755 service.init %{buildroot}%{_initrddir}/service

...

mkdir -p %{buildroot}%{_sbindir}
ln -sf %{_initrddir}/service %{buildroot}%{_sbindir}/rcservice

...

%post
%fillup_and_insserv service

%preun
%stop_on_removal service

%postun
%restart_on_update service
%insserv_cleanup

...

%files
%defattr(-,root,root)
...
%{_initrddir}/service
%{_sbindir}/rcservice

...

如果引导脚本必须被启用、重启或停止,它们的名字必须作为相关宏的参数列出。唯一有区别的是 %insserv_cleanup 宏。它不需要任何参数,因为无论如何它都会删除全部的跨硬盘分区系统链接。

软件包必须把 SysVinit 风格的引导脚本放到 /etc/init.d。该目录有一个替代的 RPM 宏,%_initrddir

LSB Provides 实际是怎样在 openSUSE 工作的

基于 LSB 的系统使用 /usr/lib/lsb/install_initd 来启动脚本,/usr/lib/lsb/remove_initd 来禁用脚本。当需要进行这些操作时,先读取 LSB 依赖关系,然后调整脚本们的 start 和 stop 属性以满足依赖关系。

这意思就是说 LSB 头部写明的依赖关系是被遵守的(尽管是一个静态的机制)。 可以使用 insserv(8) 工具来覆盖 LSB 头部的依赖关系,更多解释请阅读 insserv(8) 的用户手册。

初始化环境变量

由于引导脚本可能需要被系统管理员手动运行以支持非标准的环境变量值如 PATH、USER、LOGNAME 等等,引导脚本不应该依赖这些环境变量。需要它们时,它们应该被设定为众所周知的/默认的值。

引导脚本必须表现完美

当 SysVinit 风格的引导脚本在服务已经启动后再启动、或服务已不再运行时停止的时候,其行为必须明智。脚本必须不能杀良冒功,不能杀死无关的 (但是也许,名称相近)用户进程来作为其正常操作的结果。达到此目的的最好办法是使用 /etc/rc.status 里提供的引导脚本通用函数:

# 导入函数库(Bash 里一个 "." 表示把那个文件“加载”到我们的这个文件中,也就是说在那个文件定义的东西这里都能用)
. /etc/rc.status
rc_reset

并使用系统工具 /sbin/start_daemon/sbin/startproc 来启动进程、/sbin/killproc 来杀死进程,以及 /sbin/checkproc 来检查正在运行的进程。详细解释可以查阅 start_daemon(8) / startproc(8), killproc(8), 和 checkproc(8) 的手册页。

startproc 命令会启动可由该可执行文件路径标识的进程。退出状态是 LSB 状态代码,细节参考 “退出状态代码”。也可以替换为 LSB 命令 start_daemon,该命令在启动可执行文件前不会 fork。但那样就需要新启动的进程自己能够 fork 并从终端断开 (参考 startproc(8), daemon(3), fork(2), 和 setsid(2) 的手册页)。 LSB 命令 killproc 会发送信号给由该可执行文件的完整路径标识的进程。 (参考 killproc(8) 手册页)。LSB 命令 pidofproc 使用基准名来查找进程 checkproc 命令使用该可执行文件的完整路径来做同样的事情 (参考 pidofproc(8) 手册页)。

然后通常是检查是否正确安装了服务,是否读取到了相关的 sysconfig 文件。遇到问题必须返回与 LSB 兼容的错误代码。细节参考 “退出状态代码”

以下范例来自 /etc/init.d/ypbind

YPBIND_BIN=/usr/sbin/ypbind
 test -x $YPBIND_BIN || { echo "$YPBIND_BIN not installed";
         if [ "$1" = "stop" ]; then exit 0; else exit 5; fi; }
 
 YPBIND_CONFIG=/etc/sysconfig/ypbind
 test -r $YPBIND_CONFIG || { echo "$YPBIND_CONFIG not existing";
         if [ "$1" = "stop" ]; then exit 0; else exit 6; fi; }
 
 # 读取配置文件
 . $YPBIND_CONFIG

如果服务会自动重新加载其配置(例如 cron),引导脚本的 reload 操作必须也像配置已成功重新加载了那样(即不要再重新加载配置了)。restart, condrestart, try-restart, reload 和 force-reload 操作必须精密,即如果已知服务在重启或重新加载后无法正常作业,那么脚本也得返回错误并不进行任何进一步操作。

用于 SysVinit 引导脚本中的系统工具

start_daemon(8) 和 startproc(8) 工具

  • start_daemon 启动一个守护程序,如果该程序当前没有运行的话。
  • startproc 将一个进程作为守护程序启动,如果该进程当前没有运行的话。
start_daemon [-fLve] [[-n 数字]+/-<增减优先级>] [-u 用户] [-g 组] [-l 日志文件|-q|-d] [-p 进程文件]
          [-i 要忽略的文件][-c 以根用户执行] 到可执行文件的完整路径 [可执行文件的参数]


startproc [-fLves] [[-n 数字]+/-<增减优先级>] [-(t|T) <秒数>] [-u 用户] [-g 组] [-l 日志文件|-q|-d] [-p 进程文件]
          [-i 要忽略的文件] [-c 以根用户执行] 到可执行文件的完整路径 [可执行文件的参数]

killproc(8) 工具

向程序发送一个信号; 默认发送 SIGTERM 结束信号, 如果进程没死的话,会在数秒后发送一个 SIGKILL 杀死信号。它也会试着移除能找到的进程文件。

killproc [-vqLN] [-g|-G] [-p 进程文件] [-i 要忽略的文件] [-c 以根用户运行] [-t <秒数>] [-<SIG>] 到可执行文件的完整路径

killproc -n [-vq] [-g|-G] [-t <秒数>] [-<SIG>] 内核 thread 会话的名字

killproc    [-vq] [-g|-G] [-t <秒数>] [-<SIG>] 可执行文件的 basename 基础名称

checkproc(8) 和 pidofproc(8) 工具

试着找到一个程序的状态和进程编号; 检查进程文件。pidofproc 主要是 checkproc 的调试版本。

checkproc [-vLkNz] [-p 进程文件] [-i 要忽略的文件] [-c 以根用户运行] 可执行文件的完整路径

checkproc -n [-vk] 内核 thread 会话的名称

checkproc    [-vk] 可执行文件的 basename 基础名称

pidofproc [-LkNz] [-p 进程文件] [-i 要忽略的文件] [-c 以根用户运行] 可执行文件的完整路径

pidofproc -n [-k] 内核 thread 会话的名称

pidofproc    [-k] 可执行文件的 basename 基础名称

LSB 命令行函数

如上系统工具也可以由 /lib/lsb/init-functions 内的命令行函数提供:

# 加载 LSB 命令行函数
. /lib/lsb/init-functions

加上 LSB 3.1 或更高版本指定要的提要:

start_daemon [-f] [-n +/-<优先级>] 到可执行文件的完整路径 [可执行文件的参数]
killproc [-p 进程文件] 到可执行文件的完整路径 [-<SIG>]
pidofproc [-p 进程文件] 到可执行文件的完整路径

调试

由于 SysVinit 脚本是动态转换成 Systemd 服务来运行的,调试起来相对就比较困难。

主要是不知道转换的 Systemd 服务是否正确(此类错误多出现在开发者没有遵守引导程序的程序逻辑,导致转换的 Systemd 服务很奇葩),以及无法在系统日志中看到(因为 Systemd 是使用 Journal 的,而 SysVinit 脚本多半依赖于 /var/log/messages)。

SysVinit 脚本是由 /lib/systemd/system-generators/systemd-sysv-generator 转换成 Systemd 服务的,转换的结果在 /run/systemd/generator.late/。刚写完脚本立即用 "systemctl enable 脚本名.service" 或者 "chkconfig -a" 是无法看到的,一定要重启一次才能看到。

在 SysVinit 脚本中,我们也可以把消息写入到 Systemd Journal。

   echo "message" | systemd-cat -t name -p info/warning/emerg

systemd-cat 的 -t 选项是自定义程序的名字,info/warning/emerg 是日志的类型。这样就可以在 journalctl 里面看到对应的输出。