智能路由器开发之创建一个procd init脚本示例
Procd init脚本默认提供了许多好用的功能,例如重启策略和能够从UCI系统中存储和读取配置。
设置
举个例子,假设我们想创建一个作为服务的Shell脚本,并且这个服务可以通过消息和超时时间进行配置,以便提醒我们时不时地离开座位休息一下。我们的服务名称将为"myservice",它依赖于以下脚本。
#!/bin/sh
#these if statements will check input and place default values if no input is given
#they will also check if input is a number so you can call
#this script with just a time and it will still work correctly
if [ "$1" = '' ]; then
name="You"
else
if echo "$1" | egrep -q '^[0-9]+$'; then
name="You"
else
name="$1"
fi
fi
if [ "$2" = '' ]; then
every="5"
else
every="$2"
fi
if echo "$1" | egrep -q '^[0-9]+$'; then
every="$1"
fi
#endless loop, will print the message every X seconds as indicated in the $every variable
while [ 1 ]; do
echo "Hey, $name, it's time to get up"
sleep $every
done
exit 0
将脚本放置在 /var/myscript.sh
下,并在OpenWrt上运行测试
$ /bin/sh /var/myscript.sh "Name Surname"
创建一个基本的procd脚本
既然我们有一个可用的脚本,我们可以将其转化为一个服务。在"/etc/init.d/myservice"路径下创建一个文件,并将以下内容添加到文件中。
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
STOP=01
start_service() {
procd_open_instance
procd_set_param command /bin/sh "/var/myscript.sh"
procd_close_instance
}
首先,它包含了一个服务所需的常用的"/etc/rc.common"文件,该文件定义了几个函数,用于管理服务的生命周期,它支持旧式的init脚本和procd脚本。为了告诉系统我们要使用新的样式,我们设置了USE_PROCD标志。
START选项基本上告诉系统在OpenWrt启动和关闭期间,服务应该何时启动和停止。
目前这个init脚本并不是很有用,但它展示了我们将进一步开发脚本所需的基本构建模块。
启用服务
告诉OpenWrt我们有一个新的服务将要运行
/etc/init.d/myservice enable
这将在目录"/etc/rc.d/“中为我们安装一个符号链接,名称为"S90myservice”,它指向位于"/etc/init.d/“中的相应服务脚本。OpenWrt将根据”/etc/rc.d/"中S*脚本的顺序启动服务。要查看顺序,您可以简单地运行以下命令:
$ ls -la /etc/rc.d/S*
能够影响服务启动顺序非常有用,如果我们的服务依赖于网络,我们需要确保START顺序的索引至少比网络服务的START顺序大1。
对于可选的STOP参数,同样适用相同的规则,只是这次它定义了服务关闭的顺序。要查看激活的关闭脚本,您可以运行以下命令:
$ ls -la /etc/rc.d/K*
您总是需要在脚本中定义一个START
或STOP
序列(您也可以同时定义两者)。如果您定义了STOP
序列,您还需要在初始化脚本中定义一个stop_service()
处理程序。该处理程序通常负责清理服务资源或在服务重新启动时需要的数据持久化。测试服务
最后,让我们测试一下我们的服务。打开第二个终端连接到OpenWrt设备,并运行以下命令:
$ logread -f
这将在设备上跟踪系统日志。然后启用(如果尚未启用)并启动服务。
$ /etc/init.d/myservice enable
$ /etc/init.d/myservice start
大约5秒后,我们应该在日志中看到消息重复出现,但实际上并没有…我们仍然需要将stdout和stderr重定向到logd,以便在系统日志中看到console.log的输出,使用procd_set_param stdout 1
设置log输出终端。
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
STOP=01
start_service() {
procd_open_instance
procd_set_param command /bin/sh "/var/myscript.sh"
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
重启服务后,使用logread -f
命令,我们能看到下面的log输出:
$ logread -f
... ommitted ... Hey, You, it's time to get up
... ommitted ... Hey, You, it's time to get up
... ommitted ... Hey, You, it's time to get up
... ommitted ... Hey, You, it's time to get up
... ommitted ... Hey, You, it's time to get up
... ommitted ... Hey, You, it's time to get up
...
像上面那样设置一个简单的procd脚本已经给我们带来了一些优势:
- 共享的API来管理服务
- 该服务将在每次启动时自动启动
服务配置
是时候变得更加个性化了,为此我们将使用OpenWrt的UCI配置界面。创建一个名为/etc/config/myservice
的配置文件,内容如下:
config myservice 'hello'
option name 'Joost'
option every '5'
UCI将立即识别这个配置,并且可以像下面这样查看我们服务的配置:
$ uci show myservice
myservice.hello=myservice
myservice.hello.name=Joost
myservice.hello.every='5'
也可以单个选项单独设置
$ uci get myservice.hello.name
也可以修改UCI配置项
$ uci set myservice.hello.name=Knight
$ uci commit
现在,我们将对服务脚本进行一些更改,以便在脚本中读取和使用配置。
加载服务配置项
我们已经可以通过将参数传递给节点脚本来传递配置。我们需要做的唯一事情是加载与配置匹配的服务,将我们需要的选项的值存储到某些变量中,并将它们传递给启动脚本的命令。
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
STOP=01
CONFIGURATION=myservice
start_service() {
# Reading config
config_load "${CONFIGURATION}"
local name
local every
config_get name hello name
config_get every hello every
procd_open_instance
# pass config to script on start
procd_set_param command /bin/sh "/var/myscript.sh" "$name" "$every"
procd_set_param file /etc/config/myservice
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
我们也可以通过下面的命令传递新的配置项
$ uci set myservice.hello.name=Woodrow Wilson Smith
$ uci commit
请注意,在服务脚本中,参数被引号引起来,这样我们就可以在name选项中使用空格。如果我们不这样做,每个名称部分都将被视为单独的参数。
除了加载和传递配置到我们的脚本之外,我们还添加了其他内容。
#//
procd_set_param file /etc/config/myservice
#//
有了这行代码,我们只需要在配置发生更改时重新启动服务。
$ /etc/init.d/myservice reload
高级选项
还有一些在procd脚本的"instance block"中可以配置的选项,这些选项可能会很有用。我在这里列举了一些,但并不意味着包含了所有内容。
- respawn
在某些情况下,当您的服务意外停止时,可以自动重新启动它。
procd_set_param respawn \
${respawn_threshold:-3600} \
${respawn_timeout:-5} ${respawn_retry:-5}
- pidfile
存储pid的文件
procd_set_param pidfile $PIDFILE
- env vars
传递环境变量到你的进程
procd_set_param env A_VAR=avalue
- ulimit
如果您需要为进程设置资源限制,可以使用以下方法:
procd_set_param limits core="unlimited"
要查看在OpenWrt设备上的系统范围内的ulimit设置,您可以运行以下命令:
$ ulimit -a
-f: file size (blocks) unlimited
-t: cpu time (seconds) unlimited
-d: data seg size (kb) unlimited
-s: stack size (kb) 8192
-c: core file size (blocks) 0
-m: resident set size (kb) unlimited
-l: locked memory (kb) 64
-p: processes 475
-n: file descriptors 1024
-v: address space (kb) unlimited
-w: locks unlimited
-e: scheduling priority 0
-r: real-time priority 0
- user
修改运行服务的用户,请使用下面的命令:
procd_set_param user nobody
默认情况下,OpenWrt只有一个’root’用户或’nobody’作为进程所有者。
您可以按照通常的Linux方式添加用户,请参阅在OpenWrt中创建非特权用户。如果您正在创建一个实际的软件包,您可以使用buildpackage定义,在软件包安装时让OpenWrt生成用户。