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--mount proc/sysfs/tmpfs等文件系统,创建一些目录或者文件。
early_dev
early_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启动一个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。
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
. $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执行期间被调用到。
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。
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