openwrt procd启动流程和脚本分析
|
Linux内核执行start_kernel函数时会调用kernel_init来启动init进程,流程如下图:
graph LR A[start_kernel] -->B(rest_init) B --> C(kernel_init) C --> D[try_to_run_init_process]
kernel_init部分代码如下: 994 if (execute_command) {
995 ret = run_init_process(execute_command);
996 if (!ret)
997 return 0;
998 panic("Requested init %s failed (error %d).",999 execute_command,ret);
1000 }
1001 if (!try_to_run_init_process("/sbin/init") ||
1002 !try_to_run_init_process("/etc/init") ||
1003 !try_to_run_init_process("/bin/init") ||
1004 !try_to_run_init_process("/bin/sh"))
1005 return 0;
1006
1007 panic("No working init found. Try passing init= option to kernel. "
1008 "See Linux Documentation/init.txt for guidance.");
接着分析openwrt中package/system/procd/Makefile,这里将procd源码编译生成的可执行文件安装到文件系统的*/sbin*目录中。 define Package/procd/install
$(INSTALL_DIR) $(1)/sbin $(1)/etc $(1)/lib/functions
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/{init,procd,askfirst,udevtrigger} $(1)/sbin/
$(INSTALL_DATA) $(PKG_INSTALL_DIR)/usr/lib/libsetlbf.so $(1)/lib
$(INSTALL_BIN) ./files/reload_config $(1)/sbin/
$(INSTALL_DATA) ./files/hotplug*.json $(1)/etc/
$(INSTALL_DATA) ./files/procd.sh $(1)/lib/functions/
endef
查看procd源码目录的CMakeList.txt,以init为例,对应源码编译文件如下 56 IF(DISABLE_INIT)
57 ADD_DEFINITIONS(-DDISABLE_INIT)
58 ELSE()
59 ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c watchdog.c
60 utils/utils.c ${SOURCES_ZRAM})
61 TARGET_LINK_LIBRARIES(init ${LIBS})
62 INSTALL(TARGETS init
63 RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
64 )
65
66 ADD_EXECUTABLE(udevtrigger plug/udevtrigger.c)
67 INSTALL(TARGETS udevtrigger
68 RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
69 )
70 ENDIF()
main函数入口位于initd/init.c int
main(int argc,char **argv)
{
pid_t pid;
ulog_open(ULOG_KMSG,LOG_DAEMON,"init");
sigaction(SIGTERM,&sa_shutdown,NULL);
sigaction(SIGUSR1,NULL);
sigaction(SIGUSR2,NULL);
early();
cmdline();
watchdog_init(1);
pid = fork();
if (!pid) {
char *kmod[] = { "/sbin/kmodloader","/etc/modules-boot.d/",NULL };
if (debug < 3)
patch_stdio("/dev/null");
execvp(kmod[0],kmod);
ERROR("Failed to start kmodloadern");
exit(-1);
}
if (pid <= 0) {
ERROR("Failed to start kmodloader instancen");
} else {
int i;
for (i = 0; i < 1200; i++) {
if (waitpid(pid,NULL,WNOHANG) > 0)
break;
usleep(10 * 1000);
watchdog_ping();
}
}
uloop_init();
preinit();
uloop_run();
return 0;
}
uloop_init实现位于libubox源码uloop.c int uloop_init(void)
{
if (uloop_init_pollfd() < 0)
return -1;
if (waker_init() < 0) {
uloop_done();
return -1;
}
return 0;
}
static int uloop_init_pollfd(void)
{
if (poll_fd >= 0)
return 0;
poll_fd = epoll_create(32);
if (poll_fd < 0)
return -1;
fcntl(poll_fd,F_SETFD,fcntl(poll_fd,F_GETFD) | FD_CLOEXEC);
return 0;
}
preinit实现位于procd源码文件initd/preinit.c static struct uloop_process preinit_proc;
static struct uloop_process plugd_proc;
void
preinit(void)
{
char *init[] = { "/bin/sh","/etc/preinit",NULL };
char *plug[] = { "/sbin/procd","-h","/etc/hotplug-preinit.json",NULL };
int fd;
LOG("- preinit -n");
plugd_proc.cb = plugd_proc_cb;
plugd_proc.pid = fork();
if (!plugd_proc.pid) {
execvp(plug[0],plug);
ERROR("Failed to start plugdn");
exit(-1);
}
if (plugd_proc.pid <= 0) {
ERROR("Failed to start new plugd instancen");
return;
}
uloop_process_add(&plugd_proc);
setenv("PREINIT","1",1);
fd = creat("/tmp/.preinit",0600);
if (fd < 0)
ERROR("Failed to create sentinel filen");
else
close(fd);
preinit_proc.cb = spawn_procd;
preinit_proc.pid = fork();
if (!preinit_proc.pid) {
execvp(init[0],init);
ERROR("Failed to start preinitn");
exit(-1);
}
if (preinit_proc.pid <= 0) {
ERROR("Failed to start new preinit instancen");
return;
}
uloop_process_add(&preinit_proc);
DEBUG(4,"Launched preinit instance,pid=%dn",(int) preinit_proc.pid);
}
这里fork出2个子进程,执行procd和*/etc/preinit*,
int main(int argc,char **argv)
{
int ch;
char *dbglvl = getenv("DBGLVL");
int ulog_channels = ULOG_KMSG;
if (dbglvl) {
debug = atoi(dbglvl);
unsetenv("DBGLVL");
}
while ((ch = getopt(argc,argv,"d:s:h:S")) != -1) {
switch (ch) {
case ‘h‘:
return hotplug_run(optarg);
case ‘s‘:
ubus_socket = optarg;
break;
case ‘d‘:
debug = atoi(optarg);
break;
case ‘S‘:
ulog_channels = ULOG_STDIO;
break;
default:
return usage(argv[0]);
}
}
ulog_open(ulog_channels,"procd");
setsid();
uloop_init();
procd_signal();
if (getpid() != 1)
procd_connect_ubus();
else
procd_state_next();
uloop_run();
uloop_done();
return 0;
}
hotplug实现如下,这里是建立netlink通信机制,完成用户层和内核的交互,监听内核的uevent事件。 void hotplug(char *rules)
{
struct sockaddr_nl nls;
int nlbufsize = 512 * 1024;
rule_file = strdup(rules);
memset(&nls,sizeof(struct sockaddr_nl));
nls.nl_family = AF_NETLINK;
nls.nl_pid = getpid();
nls.nl_groups = -1;
if ((hotplug_fd.fd = socket(PF_NETLINK,SOCK_DGRAM | SOCK_CLOEXEC,NETLINK_KOBJECT_UEVENT)) == -1) {
ERROR("Failed to open hotplug socket: %sn",strerror(errno));
exit(1);
}
if (bind(hotplug_fd.fd,(void *)&nls,sizeof(struct sockaddr_nl))) {
ERROR("Failed to bind hotplug socket: %sn",strerror(errno));
exit(1);
}
if (setsockopt(hotplug_fd.fd,SOL_SOCKET,SO_RCVBUFFORCE,&nlbufsize,sizeof(nlbufsize)))
ERROR("Failed to resize receive buffer: %sn",strerror(errno));
json_script_init(&jctx);
queue_proc.cb = queue_proc_cb;
uloop_fd_add(&hotplug_fd,ULOOP_READ);
}
int hotplug_run(char *rules)
{
uloop_init();
hotplug(rules);
uloop_run();
return 0;
}
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
# 初始化hook链
boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
# 依次执行/lib/preinit目录中的脚本,将函数调用添加到hook链中
for pi_source_file in /lib/preinit/*; do
. $pi_source_file
done
# 执行preinit_essential注册的hook链的所有函数
boot_run_hook preinit_essential
# 执行preinit_main注册的hook链的所有函数
boot_run_hook preinit_main
lib/functions/preinit.sh中定义 boot_hook_init() {
local hook="${1}_hook"
export -n "PI_STACK_LIST=${PI_STACK_LIST:+$PI_STACK_LIST }$hook"
export -n "$hook="
}
/lib/preinit/10_sysinfo中添加hook函数 boot_hook_add preinit_main do_sysinfo_generic /etc/preinit脚本执行完成后,调用spawn_procd static void
spawn_procd(struct uloop_process *proc,int ret)
{
char *wdt_fd = watchdog_fd();
char *argv[] = { "/sbin/procd",NULL};
struct stat s;
char dbg[2];
if (plugd_proc.pid > 0)
kill(plugd_proc.pid,SIGKILL);
if (!stat("/tmp/sysupgrade",&s))
while (true)
sleep(1);
unsetenv("INITRAMFS");
unsetenv("PREINIT");
unlink("/tmp/.preinit");
DEBUG(2,"Exec to real procd nown");
if (wdt_fd)
setenv("WDTFD",wdt_fd,1);
check_dbglvl();
if (debug > 0) {
snprintf(dbg,2,"%d",debug);
setenv("DBGLVL",dbg,1);
}
//调用procd
execvp(argv[0],argv);
}
此时getpid()等于1,所以调用procd_state_next,进入到状态机处理中。 对应的procd log如下,procd state不断迁移,包括STATE_EARLY,STATE_UBUS,STATE_INIT等。 [ [email?protected]] init: Console is alive [ [email?protected]] init: Ping [ [email?protected]] init: Ping [ [email?protected]] kmodloader: loading kernel modules from /etc/modules-boot.d/* [ [email?protected]] init: Ping [ [email?protected]] kmodloader: done loading kernel modules from /etc/modules-boot.d/* [ [email?protected]] init: Ping [ [email?protected]] init: - preinit - [ [email?protected]] init: Launched preinit instance,pid=1308 [ [email?protected]] init: Exec to real procd now [ [email?protected]] procd: - early - [ [email?protected]] procd: Finished udevtrigger [ [email?protected]] procd: Coldplug complete [ [email?protected]] procd: - ubus - [ [email?protected]] procd: Create service ubus [ [email?protected]] procd: Create instance ubus::instance1 [ [email?protected]] procd: Started instance ubus::instance1[1554] [ [email?protected]] procd: Connected to ubus,id=459ede6c [ [email?protected]] procd: - init - [ [email?protected]] procd: Launched new askconsole action,pid=1555 [ [email?protected]] procd: Launched new askfirst action,pid=1556 以STATE_INIT为例,执行procd_inittab_run("xxx")会调用对应handlers的callback,对应所有的init_action是在*procd_inittab()*中添加的。 case STATE_INIT:
LOG("- init -n");
procd_inittab();
procd_inittab_run("respawn");
procd_inittab_run("askconsole");
procd_inittab_run("askfirst");
procd_inittab_run("sysinit");
static struct init_handler handlers[] = {
{
.name = "sysinit",.cb = runrc,},{
.name = "shutdown",{
.name = "askfirst",.cb = askfirst,.multi = 1,{
.name = "askconsole",.cb = askconsole,{
.name = "respawn",.cb = rcrespawn,}
};
void procd_inittab_run(const char *handler)
{
struct init_action *a;
list_for_each_entry(a,&actions,list) {
if (!strcmp(a->handler->name,handler)) {
if (a->handler->multi) {
a->handler->cb(a);
continue;
}
a->handler->cb(a);
break;
}
}
}
这里来看runrc的实现,代码位于inittab.c static void runrc(struct init_action *a)
{
if (!a->argv[1] || !a->argv[2]) {
ERROR("valid format is rcS <S|K> <param>n");
return;
}
/* proceed even if no init or shutdown scripts run */
if (rcS(a->argv[1],a->argv[2],rcdone)) {
printf("---rcdone---n");
rcdone(NULL);
} else {
printf("----rcdone errorn");
}
}
rcS.c int rcS(char *pattern,char *param,void (*q_empty)(struct runqueue *))
{
runqueue_init(&q);
q.empty_cb = q_empty;
q.max_running_tasks = 1;
return _rc(&q,"/etc/rc.d",pattern,"*",param);
}
执行*/etc/rc.d*目录下S/K开头的脚本 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
