OpenWRT(4):启动流程以及添加自己的服务
OpenWRT的启动流程主要如下:
- 启动/init脚本,挂载tmpfs,并切换到/sbin/init运行。
- 启动/sbin/init,然后依次启动/etc/preinit和/sbin/procd。
- 如需要在/sbin/procd之前处理工作,需要在/lib/preinit按序号添加脚本。
- /sbin/procd根据/etc/inittab执行,包括/etc/rc.d中的启动脚本。
- 在/etc/init.d中按照规则添加启动脚本,启动守护进程。
总体执行顺序如下:
1 /init启动脚本
init启动脚本并没有直接启动系统,而是做了一些准备工作,然后调用/sbin/init启动OpenWRT系统:
- 创建/new_root,并关在一个tmpfs文件系统到其上。
- 将当前文件系统内容拷贝到/new_root里面。
- 切换到/new_root作为新的rootfs,并且启动里面的/sbin/init程序作为init进程。
#!/bin/sh # Copyright (C) 2006 OpenWrt.org export INITRAMFS=1 # switch to tmpfs to allow run daemons in jail on initramfs boot DIRS=$(echo *) NEW_ROOT=/new_root mkdir -p $NEW_ROOT mount -t tmpfs tmpfs $NEW_ROOT cp -pr $DIRS $NEW_ROOT exec switch_root $NEW_ROOT /sbin/init
2 /sbin/init启动进程
OpenWRT的init进程来自于procd软件包:
- 初始化日志。
- 挂载文件系统、创建节点文件、创建目录等。
- 启动kmodloader加载内核module。
- 通过procd启动hotplug功能。
- 执行/etc/preinit脚本。
- 执行procd,替换当前init进程。
main
ulog_open--用于初始化日志系统的函数。用于设置日志记录的目标和日志级别。
early--检查pid必须为1,否则返回。
early_mounts--挂载proc、sysfs、tmpfs、cgroup2、devpts等文件系统,创建一些目录或者文件。
early_dev--创建/dev/null。
early_console--将标准输入输出错误重定向到/dev/console。
early_env--设置PATH变量。
cmdline--解析命令行中是否存在init_debug,并根据这个值配置debug等级。
/sbin/kmodloader--启动kmodloader加载/etc/modules-boot.d/里面的module。
waitpid--循环等待kmodloader结束。
uloop_init--用于初始化事件循环的函数。负责处理定时器、信号、socket 事件等。
preinit
execvp--启动/sbin/procd。在procd中,启动一个hotplug程序,配置文件为/etc/hotplug-preinit.json。
uloop_process_add--用于将一个进程添加到uloop
的管理中。确保在调用uloop_process_add
之前,子进程已经创建,并且pid
已经设置为子进程的实际PID。
execvp--执行/etc/preinit脚本。
uloop_process_add--执行完的uloop回调函数为spawn_procd。
spawn_procd--退出/etc/preinit之后,执行procd替换init。
check_sysupgrade
sysupgrade_exec_upgraded
execvp--执行upgraded。
execvp--执行procd。
uloop_run--用于启动事件循环的函数。一旦调用这个函数,事件循环就会开始运行,处理注册的事件,直到接收到退出信号或发生错误。
3 kmodloader
kmodloader是一个在 Linux 系统中用于加载和卸载内核模块的工具。内核模块是动态加载到 Linux 内核中的代码,它们可以扩展内核的功能。
kmod 命令是kmodloader的一部分,它提供了一些用于管理内核模块的实用功能。
kmodloader还实现了insmod/lsmod/rmmod/modinfo/modprobe功能:
/sbin/insmod -> /sbin/kmodloader /sbin/kmodloader /sbin/lsmod -> /sbin/kmodloader /sbin/modinfo -> /sbin/kmodloader /sbin/modprobe -> /sbin/kmodloader /sbin/rmmod -> /sbin/kmodloader
其代码执行流程如下:
main
main_insmod
main_rmmod
main_lsmod
main_modinfo
main_modprobe
main_loader--以上命令之外,kmodloader对应的路径。
scan_module_folders--遍历/etc/modules.d目录下的模块名称。
init_module_folders
scan_module_folder
scan_loaded_modules
find_module
load_modprobe
insert_module
__NR_init_module--系统调用加载内核模块。
两次调用kmodeloader的地方分别是:
- /sbin/init中启动kmodeloader加载/etc/modules-boot.d/中指定的模块。
- 在procd中调用/etc/rc.d脚本/etc/rc.d/S10boot中执行/sbin/kmodloader,默认加载/etc/modules.d中指定模块。
4 /etc/preinit
/etc/preinit在执行/sbin/procd之前做一些准备工作:
- source一些脚本,获取函数定义。
- boot_hook_init:函数用于初始化一个新的启动钩子。这个函数接收一个参数,即钩子的名称,然后设置一个环境变量来存储与该钩子相关的函数列表。
- boot_hook_add:函数用于向已初始化的钩子添加函数。这个函数接收两个参数:钩子的名称和要添加的函数名称。
- boot_run_hook:函数用于执行与特定钩子关联的所有函数。
- 遍历/lib/preinit中preinit脚本,添加hook。
- 依次执行preinit_essential和preinit_main两个hook。
/etc/preinit调用 boot_hook_init来初始化一系列钩子;/etc/preinit按序号依次执行/lib/preinit中的脚本调用boot_hook_add来向该钩子添加需要执行的函数;/etc/preinit调用boot_run_hook 来执行所有已添加到该钩子preinit_essential和preinit_main的函数。
#!/bin/sh # Copyright (C) 2006-2016 OpenWrt.org # Copyright (C) 2010 Vertical Communications [ -z "$PREINIT" ] && exec /sbin/init export PATH="%PATH%" . /lib/functions.sh . /lib/functions/preinit.sh . /lib/functions/system.sh boot_hook_init preinit_essential boot_hook_init preinit_main boot_hook_init failsafe boot_hook_init initramfs boot_hook_init preinit_mount_root for pi_source_file in /lib/preinit/*; do--按照从00->99顺序遍历/lib/preinit下面的脚本执行。 . $pi_source_file done boot_run_hook preinit_essential pi_mount_skip_next=false pi_jffs2_mount_success=false pi_failsafe_net_message=false boot_run_hook preinit_main
/lib/preinit的配置和脚本包括:
lib/preinit/
├── 00_preinit.conf
├── 02_default_set_state
├── 02_sysinfo
├── 10_indicate_failsafe
├── 10_indicate_preinit
├── 30_failsafe_wait
├── 40_run_failsafe_hook
├── 50_indicate_regular_preinit
├── 70_initramfs_test
├── 80_mount_root
├── 81_urandom_seed
├── 99_10_failsafe_dropbear
├── 99_10_failsafe_login
└── 99_10_run_init
以添加一个preinit_main hook函数为例:
define_default_set_state() { . /etc/diag.sh } boot_hook_add preinit_main define_default_set_state
其中define_default_set_state()脚本函数会在/etc/preinit执行期间被调用到。
4.1 mount_root
80_mount_root中判断是否执行do_mount_root:
[ "$INITRAMFS" = "1" ] || boot_hook_add preinit_main do_mount_root
如果INITRAMFS不为1,则将do_mount_root进入到preinit_main钩子中。
do_mount_root做如下处理:
- 执行mount_root。
- 执行所有注册到preinit_mount_root钩子的脚本或函数。
- 检查/sysupgrade.tgz或/tmp/sysupgrade.tar文件是否存在,存在则解压恢复数据。
do_mount_root() { mount_root boot_run_hook preinit_mount_root [ -f /sysupgrade.tgz -o -f /tmp/sysupgrade.tar ] && { echo "- config restore -" cp /etc/passwd /etc/group /etc/shadow /tmp cd / [ -f /sysupgrade.tgz ] && tar xzf /sysupgrade.tgz [ -f /tmp/sysupgrade.tar ] && tar xf /tmp/sysupgrade.tar missing_lines /tmp/passwd /etc/passwd >> /etc/passwd missing_lines /tmp/group /etc/group >> /etc/group missing_lines /tmp/shadow /etc/shadow >> /etc/shadow rm /tmp/passwd /tmp/group /tmp/shadow # Prevent configuration corruption on a power loss sync } }
5 /sbin/procd
更多参考:《[OpenWrt Wiki] Procd system init and daemon management》、《[OpenWrt Wiki] Init Scripts》、《[OpenWrt Wiki] procd init scripts》。
5.1 procd启动流程
/sbin/procd被/sbin/init启动,并处理如下工作:
- 初始化日志。
- 注册信号处理函数。
- 依次进入如下四种状态:
- STATE_EARLY:处理coldplug和hotplug。
- STATE_UBUS:启动ubus服务。
- STATE_INIT:处理inittab中respawn/askconsole/askfirst/sysinit等action。
- STATE_RUNNING:处理inittab中respawnlate/askconsolelate等action。
main
hotplug_run---h选项时调用,rulles参数为/etc/hotplug-preinit.json。
hotplug
ulog_open
uloop_init
procd_signal
procd_state_next
state_enter
STATE_EARLY
hotplug--根据/etc/hotplug.json定义的规则处理热插拔事件。
--打开PF_NETLINK域的NETLINK_KOBJECT_UEVENT协议socket,并循环读取内容。
hotplug_handler--热插拔时间处理函数。
recv--从socket读取uevent内容。
json_script_run--执行热插拔相关脚本。
procd_coldplug
execvp--启动udevtrigger进程。
uloop_process_add--udevtrigger结束后回调函数udevtrigger_complete。
udevtrigger_complete
hotplug_last_event--启动一个uloop定时器,超时执行回调函数coldplug_complete。
coldplug_complete
hotplug_last_event--参数为NULL,停止定时器。
procd_state_next
STATE_UBUS
procd_connect_ubus
timeout_retry--启动ubus_timer。
ubus_connect_cb--
service_start_early--启动ubusd守护进程此后可以提供ubus IPC服务。
STATE_INIT
procd_inittab--解析/etc/inittab。
procd_inittab_run--依次执行inittab中的respawn/askconsole/askfirst/sysinit。
rcrespawn
askconsole
askfirst
runrc--sysinit action对应的操作函数。
rcS
runqueue_init--初始化一个运行队列。
_rc--遍历/etc/rc.d目录下所有启动脚本。
add_initd
runqueue_task_add--将一个init脚本加入到运行队列,执行函数为q_initd_run。
q_initd_run--创建一个进程执行脚本,并将脚本输出通过pipe中定向到procd进行处理。
pipe_cb--procd读取并处理/etc/rc.d中脚本执行生成的日志。
rcdone
procd_state_next
STATE_RUNNING
procd_inittab_run--执行inittab的respawnlate/askconsolelate。
STATE_SHUTDOWN
procd_inittab_run--执行inittab的shutdown。
STATE_HALT
signal-发送SIGTERM、SIGKILL关闭进程。
uloop_run
uloop_done
5.2 hotplug配置文件
/etc/hotplug-preinit.json定义了add FIRMWARE热插拔行为:
[ [ "case", "ACTION", { "add": [ [ "if", [ "has", "FIRMWARE" ], [ [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ], [ "load-firmware", "/lib/firmware" ], [ "return" ] ] ] ] } ], [ "if", [ "eq", "SUBSYSTEM", "button" ], [ "exec", "/etc/rc.button/failsafe" ] ] ]
/etc/hotplug.json定义了add/remove等热插拔行为:
[ [ "case", "ACTION", { "add": [ [ "if", [ "and", [ "has", "MAJOR" ], [ "has", "MINOR" ] ], [ [ "if", [ "eq", "DEVNAME", "null" ], [ [ "makedev", "/dev/%DEVNAME%", "0666" ], [ "exec", "/bin/ln", "-s", "/proc/self/fd/0", "/dev/stdin" ], [ "exec", "/bin/ln", "-s", "/proc/self/fd/1", "/dev/stdout" ], [ "exec", "/bin/ln", "-s", "/proc/self/fd/2", "/dev/stderr" ], [ "return" ] ] ], ... ] ], [ "if", [ "has", "FIRMWARE" ], [ [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ], [ "load-firmware", "/lib/firmware" ], [ "return" ] ] ], [ "if", [ "regex", "DEVNAME", "^ttyGS" ], [ "start-console", "%DEVNAME%" ] ] ], "remove" : [ [ "if", [ "and", [ "has", "DEVNAME" ], [ "has", "MAJOR" ], [ "has", "MINOR" ] ], [ "rm", "/dev/%DEVNAME%" ] ] ] } ], [ "if", [ "and", [ "has", "BUTTON" ], [ "eq", "SUBSYSTEM", "button" ] ], [ "button", "/etc/rc.button/%BUTTON%" ] ], [ "if", [ "and", [ "eq", "SUBSYSTEM", "usb-serial" ], [ "regex", "DEVNAME", [ "^ttyUSB", "^ttyACM" ] ] ], [ "exec", "/sbin/hotplug-call", "tty" ], [ "if", [ "isdir", "/etc/hotplug.d/%SUBSYSTEM%" ], [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ] ] ] ]
5.3 procd配置文件/etc/inittab
/etc/inittab定义了procd不同action操作:
::sysinit:/etc/init.d/rcS S boot ::shutdown:/etc/init.d/rcS K shutdown--sysinit和shutdown都调用/etc/rc.d中的启动脚本。 ttyAMA0::askfirst:/usr/libexec/login.sh ttyS0::askfirst:/usr/libexec/login.sh hvc0::askfirst:/usr/libexec/login.sh
5.4 /etc/init.d到/etc/rc.d转换
在include/rootfs.mk中,执行prepare_rootfs会在/etc/rc.d目录中生成/etc/init.d目录中脚本的链接:
define prepare_rootfs $(if $(2),@if [ -d '$(2)' ]; then \ $(call file_copy,$(2)/.,$(1)); \ fi) @mkdir -p $(1)/etc/rc.d @mkdir -p $(1)/var/lock @( \ cd $(1); \ ... for script in ./etc/init.d/*; do \ grep '#!/bin/sh /etc/rc.common' $$script >/dev/null || continue; \ if ! echo " $(3) " | grep -q " $$(basename $$script) "; then \ IPKG_INSTROOT=$(1) $$(command -v bash) ./etc/rc.common $$script enable; \ echo "Enabling" $$(basename $$script); \ else \ IPKG_INSTROOT=$(1) $$(command -v bash) ./etc/rc.common $$script disable; \ echo "Disabling" $$(basename $$script); \ fi; \ done || true \ ) ... endef
在/etc/rc.comm中生成链接文件:
disable() { name="$(basename "${initscript}")" rm -f "$IPKG_INSTROOT"/etc/rc.d/S??$name rm -f "$IPKG_INSTROOT"/etc/rc.d/K??$name } enable() { err=1 name="$(basename "${initscript}")" [ "$START" ] && \ ln -sf "../init.d/$name" "$IPKG_INSTROOT/etc/rc.d/S${START}${name##S[0-9][0-9]}" && \ err=0 [ "$STOP" ] && \ ln -sf "../init.d/$name" "$IPKG_INSTROOT/etc/rc.d/K${STOP}${name##K[0-9][0-9]}" && \ err=0 return $err }
START=和STOP=行确定了这个脚本在sysinit初始化中何时被执行。在启动时,procd只是开始执行它在/etc/rc.d中找到的脚本,根据它们的文件名顺序来执行。初始化脚本可以作为符号链接放置在这里,链接到 /etc/init.d/ 中的 init.d 脚本。使用 enable 和 disable 命令,这个过程是/etc/rc.comm中处理完成的。在这种情况下:
START=10 - 这意味着文件将作为符号链接 /etc/rc.d/S10example - 换句话说,它将在 START=9 及以下的初始化脚本之后启动,但在 START=11 及以上之前。
STOP=15 - 这意味着文件将作为符号链接 /etc/rc.d/K15example - 这意味着它将在 STOP=14 及以下的初始化脚本之后停止,但在 STOP=16 及以上之前。这是可选的。
如果多个初始化脚本具有相同的启动值,调用顺序将由初始化脚本名称的字母顺序决定。
不要忘记确保脚本具有执行权限,通过运行chmod +x /etc/init.d/example。
START和STOP值应该在 1-99 范围内,因为它们是按字母顺序运行的,这意味着100会在10之后执行。
OpenWrt 将在构建期间在宿主系统上运行初始化脚本(当前使用动作 “enable” 或 “disable”),并且它必须正确处理这个特殊情况,避免不必要的副作用。
5.5 创建启动脚本实例
创建package/base-files/files/etc/init.d/example如下:
#!/bin/sh /etc/rc.common START=88 STOP=88 USE_PROCD=1 start() { echo "start(): $@" # commands to launch application } stop() { echo "stop(): $@" # commands to kill application } boot() { echo "boot(): $@" } shutdown() { # The service is finished, so turn off the hardware stop echo "shutdown(): $@" } EXTRA_COMMANDS="custom1 custom2 custom3" EXTRA_HELP=<<EOF custom1 Help for the custom1 command custom2 Help for the custom2 command custom3 Help for the custom3 command EOF custom1 () { echo "custom1" # do the stuff for custom1 } custom2 () { echo "custom2" # do the stuff for custom2 } custom3 () { echo "custom3" # do the stuff for custom3 }
5.6 显示启动日志
q_initd_run运行inittab启动脚本:
static void q_initd_run(struct runqueue *q, struct runqueue_task *t) { struct initd *s = container_of(t, struct initd, proc.task); int pipefd[2]; pid_t pid; clock_gettime(CLOCK_MONOTONIC_RAW, &s->ts_start); DEBUG(2, "start %s %s \n", s->file, s->param); if (pipe(pipefd) == -1) {--创建pipe。 ERROR("Failed to create pipe: %m\n"); return; } pid = fork();--创建一个新进程。 if (pid < 0) return; if (pid) { close(pipefd[1]); fcntl(pipefd[0], F_SETFD, FD_CLOEXEC);--父进程关闭pipe[1],通过pipefd[0]接收子进程发送的消息。 s->fd.stream.string_data = true, s->fd.stream.notify_read = pipe_cb, runqueue_process_add(q, &s->proc, pid); ustream_fd_init(&s->fd, pipefd[0]); return; } close(pipefd[0]);--子进程关闭pipe[0]。 int devnull = open("/dev/null", O_RDONLY); dup2(devnull, STDIN_FILENO);--子进程输入指向/dev/null,即关闭输入。 dup2(pipefd[1], STDOUT_FILENO);--子进程输出指向pipe[1],通过pipe输出给父进程。 dup2(pipefd[1], STDERR_FILENO);--子进程错误指向pipe[1],通过pipe输出给父进程。 if (devnull > STDERR_FILENO) close(devnull); printf("arnoldlu %s: %s %s\n", __func__, s->file, s->param); execlp(s->file, s->file, s->param, NULL); exit(1); }
父进程在pipe_cb中处理通过pipe接收的消息:
static void pipe_cb(struct ustream *s, int bytes) { struct initd *initd = container_of(s, struct initd, fd.stream); char *newline, *str; int len; do { str = ustream_get_read_buf(s, NULL); if (!str) break; newline = strchr(str, '\n'); if (!newline) break; *newline = 0; len = newline + 1 - str; ULOG_NOTE("%s: %s", initd->file, str); #ifdef SHOW_BOOT_ON_CONSOLE--打开这个宏,可以显示子进程通过pipe发送过来的消息。 fprintf(stderr, "%s: %s\n", "arnoldlu", str); #endif ustream_consume(s, len); } while (1); }
6 udevtrigger
udevtrigger是一个与 udev(用户空间的设备管理器)相关的命令行工具,它用于触发 udev 系统去处理设备相关的事件。
具体来说,udevtrigger扫描 sysfs 文件系统,生成相应的硬件设备 hotplug 事件,这些事件随后由procd处理,以创建或移除相应的设备节点。
在系统启动时,udevtrigger执行 coldplug 操作,这个过程会检测到系统上所有已经存在的硬件设备,并通过 sysfs 内核虚拟文件系统获取这些设备的相关信息。然后,udevtrigger根据这些信息生成 hotplug 事件,procd再读取这些事件并生成对应的硬件设备文件。
main
scan_subdir--依次扫描/sys/bus、/sys/class、/sys/block目录。
device_list_insert
trigger_uevent--往设备的uevent写add,出发uevent事件。procd在接收到uevent事件后,根据hotplug.json定义的热插拔行进行处理。
7 启动实例
以uhttpd启动脚本为例,在package/network/services/uhttpd/Makefile中:
define Package/uhttpd/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/uhttpd.init $(1)/etc/init.d/uhttpd--inittab对应的启动脚本。 $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/uhttpd.config $(1)/etc/config/uhttpd--uhttpd配置文件。 $(VERSION_SED_SCRIPT) $(1)/etc/config/uhttpd $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd $(1)/usr/sbin/uhttpd--uhttpd守护进程。 endef
在uhttpd.init中启动uhttpd守护进程:
start_service() { config_load uhttpd config_foreach start_instance uhttpd } start_instance() { UHTTPD_CERT="" UHTTPD_KEY="" local cfg="$1" local realm="$(uci_get system.@system[0].hostname)" local listen http https interpreter indexes path handler httpdconf haveauth local enabled config_get_bool enabled "$cfg" 'enabled' 1 [ $enabled -gt 0 ] || return procd_open_instance procd_set_param respawn procd_set_param stderr 1 procd_set_param command "$UHTTPD_BIN" -f config_get config "$cfg" config if [ -z "$config" ]; then mkdir -p /var/etc/uhttpd httpdconf="/var/etc/uhttpd/httpd.${cfg}.conf" rm -f ${httpdconf} config_list_foreach "$cfg" httpauth create_httpauth if [ "$haveauth" = "1" ]; then procd_append_param command -c ${httpdconf}--启动uhttpd守护进程。 [ -r /etc/httpd.conf ] && cat /etc/httpd.conf >>/var/etc/uhttpd/httpd.${cfg}.conf fi fi ... procd_close_instance }
最终启动结果如下:
UID PID PPID CMD root 2 0 [kthreadd] root 11 2 [rcu_sched] root 10 2 [ksoftirqd/0] root 12 2 [migration/0] root 7 2 [kworker/u4:0-events_unbound] root 6 2 [kworker/0:0H-events_highpri] root 8 2 [mm_percpu_wq] root 4 2 [rcu_par_gp] root 3 2 [rcu_gp] root 5 2 [kworker/0:0-events] root 9 2 [rcu_tasks_trace] root 17 2 [kworker/1:0-events] root 16 2 [ksoftirqd/1] root 18 2 [kworker/1:0H-events_highpri] root 13 2 [cpuhp/0] root 14 2 [cpuhp/1] root 15 2 [migration/1] root 19 2 [netns] root 20 2 [kworker/u4:1-events_power_efficient] root 37 2 [kworker/1:1-events] root 35 2 [kworker/0:1-events] root 171 2 [kworker/u4:2-events_unbound] root 205 2 [oom_reaper] root 206 2 [writeback] root 208 2 [kcompactd0] root 232 2 [blkcg_punt_bio] root 222 2 [cryptd] root 218 2 [pencrypt_serial] root 220 2 [pdecrypt_serial] root 230 2 [kblockd] root 261 2 [kworker/0:1H] root 311 2 [kswapd0] root 318 2 [kthrotld] root 400 2 [kworker/1:1H-kblockd] root 399 2 [ipv6_addrconf] root 593 2 [kworker/1:2-rcu_gp] root 1 0 /sbin/procd ubus 501 1 /sbin/ubusd root 502 1 /bin/ash --login root 2683 502 ps -AFH root 534 1 /sbin/urngd root 735 1 /usr/sbin/uhttpd -f -h /www -r OpenWrt -x /cgi-bin -l /cgi-bin/luci -L /usr/lib/lua/luci/sgi/uhttpd.lua -u /ubus -t 60 -T 30 -k 20 -A 1 -n 3 -N 100 -R -p 0.0.0.0:80 -p [::]:80 -C /etc/uhttpd.crt -K /etc/uhttpd.key -s 0.0.0.0:443 -s [: logd 976 1 /sbin/logd -S 64 root 1031 1 /sbin/rpcd -s /var/run/ubus/ubus.sock -t 30 root 1249 1 /usr/sbin/dropbear -F -P /var/run/dropbear.1.pid -p 22 -K 300 -T 3 root 1370 1 /sbin/netifd root 1432 1 /usr/sbin/odhcpd root 2115 1 /sbin/ujail -t 5 -n ntpd -U ntp -G ntp -C /etc/capabilities/ntpd.json -c -u -r /bin/ubus -r /usr/bin/env -r /usr/bin/jshn -r /usr/sbin/ntpd-hotplug -r /usr/share/libubox/jshn.sh -- /usr/sbin/ntpd -n -N -S /usr/sbin/ntpd-hotplug -p 0.o ntp 2123 2115 /usr/sbin/ntpd -n -N -S /usr/sbin/ntpd-hotplug -p 0.openwrt.pool.ntp.org -p 1.openwrt.pool.ntp.org -p 2.openwrt.pool.ntp.org -p 3.openwrt.pool.ntp.org root 2206 1 /sbin/ujail -t 5 -n dnsmasq -u -l -r /bin/ubus -r /etc/TZ -r /etc/dnsmasq.conf -r /etc/ethers -r /etc/group -r /etc/hosts -r /etc/passwd -w /tmp/dhcp.leases -r /tmp/dnsmasq.d -r /tmp/hosts -r /tmp/resolv.conf.d -r /usr/bin/jshn -r /us dnsmasq 2212 2206 /usr/sbin/dnsmasq -C /var/etc/dnsmasq.conf.cfg01411c -k -x /var/run/dnsmasq/dnsmasq.cfg01411c.pid