OpenWRT 启动流程(三) /sbin/procd 分析

本文深入剖析OpenWRT的启动流程,详细介绍了/sbin/procd如何读取/etc/inittab并执行/etc/rc.d下的应用启动脚本,揭示了OpenWRT系统从启动到运行的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列导航:
OpenWRT 启动流程(一) /sbin/init 进程分析
OpenWRT 启动流程(二) /etc/preinit 脚本分析
OpenWRT 启动流程(三) /sbin/procd 分析

在 /sbin/init 执行完后,调用了 spawn_procd 函数 ,启动了 procd 进程。

static void
spawn_procd(struct uloop_process *proc, int ret)
{
	char *wdt_fd = watchdog_fd();
	char *argv[] = { "/sbin/procd", NULL};
	char dbg[2];
	//......
	execvp(argv[0], argv);
}

procd 代码位于 procd-2018-03-28-dfb68f85\procd.c

跟踪代码:

main
  procd_state_next
    state_enter
      

这里主要来看 state_enter

static void state_enter(void)
{
	char ubus_cmd[] = "/sbin/ubusd";

	switch (state) {
	case STATE_EARLY:
		LOG("- early -\n");
		watchdog_init(0);
		hotplug("/etc/hotplug.json");
		procd_coldplug();
		break;

	case STATE_UBUS:
		// try to reopen incase the wdt was not available before coldplug
		watchdog_init(0);
		set_stdio("console");
		LOG("- ubus -\n");
		procd_connect_ubus();
		service_start_early("ubus", ubus_cmd);
		break;

	case STATE_INIT:
		LOG("- init -\n");
		procd_inittab();
		procd_inittab_run("respawn");
		procd_inittab_run("askconsole");
		procd_inittab_run("askfirst");
		procd_inittab_run("sysinit");

		// switch to syslog log channel
		ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
		break;

	case STATE_RUNNING:
		LOG("- init complete -\n");
		procd_inittab_run("respawnlate");
		procd_inittab_run("askconsolelate");
		break;

	case STATE_SHUTDOWN:
		/* Redirect output to the console for the users' benefit */
		set_console();
		LOG("- shutdown -\n");
		procd_inittab_run("shutdown");
		sync();
		break;

	case STATE_HALT:
		// To prevent killed processes from interrupting the sleep
		signal(SIGCHLD, SIG_IGN);
		LOG("- SIGTERM processes -\n");
		kill(-1, SIGTERM);
		sync();
		sleep(1);
		LOG("- SIGKILL processes -\n");
		kill(-1, SIGKILL);
		sync();
		sleep(1);
#ifndef DISABLE_INIT
		if (reboot_event == RB_POWER_OFF)
			LOG("- power down -\n");
		else
			LOG("- reboot -\n");

		/* Allow time for last message to reach serial console, etc */
		sleep(1);

		/* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
		 * in linux/kernel/sys.c, which can cause the machine to panic when
		 * the init process exits... */
		if (!vfork( )) { /* child */
			reboot(reboot_event);
			_exit(EXIT_SUCCESS);
		}
		while (1)
			sleep(1);
#else
		exit(0);
#endif
		break;

	default:
		ERROR("Unhandled state %d\n", state);
		return;
	};
}
  • case STATE_EARLY
    这里主要是 调用 hotplug procd_coldplug ,自动创建设备节点,生成/dev/xxx

  • case STATE_UBUS
    初始化 ubus 连接

  • case STATE_INIT
    读取 /etc/inittab

    ::sysinit:/etc/init.d/rcS S boot
    ::shutdown:/etc/init.d/rcS K shutdown
    tts/0::askfirst:/usr/libexec/login.sh
    ttyS0::askfirst:/usr/libexec/login.sh
    tty1::askfirst:/usr/libexec/login.sh
    
    
    void procd_inittab(void)
      if (add_action(a, tags[TAG_ACTION]))
          for (i = 0; i < ARRAY_SIZE(handlers); i++)   // 见 handlers 定义
            if (!strcmp(handlers[i].name, name)) {
       	    a->handler = &handlers[i];
       	    list_add_tail(&a->list, &actions);
       	    return 0;
           }
    // handlers 定义	    
    static struct init_handler handlers[] = {
    {
       .name = "sysinit",
       .cb = runrc,
    }, {
       .name = "shutdown",
       .cb = runrc,
    }, {
       .name = "askfirst",
       .cb = askfirst,
       .multi = 1,
    }
       //...
    }
    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))
       	rcdone(NULL);
    }
    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/ 下的启动脚本,逐个执行。 /etc/rc.d/ 目录文件实例:

    root@172:/etc/rc.d# ls |sort
    K10gpio_switch
    K10mysqld
    K10openvpn
    K50dropbear
    K50lighttpd
    K81redis
    K85odhcpd
    K89log
    K90network
    K90sysfixtime
    K98boot
    K99umount
    S00sysfixtime
    S10boot
    S10system
    S11sysctl
    S12log
    S19dnsmasq
    S19firewall
    S20network
    S35odhcpd
    S36asterisk
    S50cron
    S50dropbear
    S50lighttpd
    S50odbc
    S50php5-fastcgi
    S50php5-fpm
    S50yate
    S90openvpn
    S94gpio_switch
    S95done
    S95mysqld
    S96led
    S98redis
    S98sysntpd
    S99urandom_seed
    
  • case STATE_RUNNING:
    初始化完成

  • case STATE_SHUTDOWN:
    暂时不细究

  • case STATE_HALT:
    暂时不细究

总结:

/sbin/procd 会读取 /etc/inittab ,然后执行 /etc/rc.d/目录下的应用启动脚本。

<think>好的,我现在需要帮助用户解决关于SIGTERM导致进程终止,同时涉及Valgrind检测内存泄漏的问题。用户提到在OpenWrt环境下,可能与cellularmanager相关。首先,我需要回顾之前的对话,确保回答的一致性。 首先,用户之前的提问是关于SIGTERM和Valgrind错误分析的,我之前给出的回答涵盖了SIGTERM的常见原因、Valgrind错误分析、分步排查方案以及实战案例。现在用户的新问题结合了引用内容,提到在条件判断中使用未初始化变量可能导致SIGSEGV,以及Valgrind的使用方法,用户现在想解决SIGTERM和内存泄漏问题,特别是在OpenWrt的cellularmanager中。 首先,我需要确认用户的具体场景:OpenWrt系统下的cellularmanager组件在运行过程中被SIGTERM终止,并且Valgrind检测到内存泄漏。用户需要解决方案。 接下来,我需要结合之前的回答和用户提供的引用内容。引用[1]指出未初始化变量可能导致严重问题,甚至SIGSEGV。引用[2]和[3]涉及Valgrind的使用,强调内存泄漏、越界访问等问题,并提到Valgrind不需要干预编译过程。用户现在的问题是SIGTERM和内存泄漏,可能与未初始化变量有关,但SIGTERM通常是外部触发的终止信号,而内存泄漏可能是Valgrind检测到的结果。 可能的思路是:内存泄漏导致资源耗尽,触发系统发送SIGTERM终止进程。或者,内存泄漏本身可能不是直接原因,但需要解决,而SIGTERM可能来自其他原因,如系统监控工具或用户操作。 需要分步骤分析: 1. 使用Valgrind检测内存泄漏:用户需要运行Valgrind并分析结果,定位泄漏位置。 2. 检查SIGTERM的来源:确定是外部触发还是程序内部问题。 3. 结合OpenWrt和cellularmanager的环境特性,可能涉及资源限制或特定配置。 需要确保用户正确使用Valgrind参数,如--leak-check=full,并正确编译程序(带调试符号)。此外,检查信号处理函数是否正确释放资源,尤其是在收到SIGTERM时。在OpenWrt环境下,可能需要交叉编译Valgrind或使用适合嵌入式环境的工具。 还要考虑cellularmanager的日志,查看是否有OOM(内存不足)事件,或者系统日志中是否有相关记录。用户可能需要优化内存使用,修复泄漏,并确保正确处理SIGTERM信号,释放所有分配的资源。 最后,整合这些步骤,给出具体的解决方案,包括Valgrind命令、代码检查点、信号处理建议,以及OpenWrt特定的调试方法。</think>### SIGTERM 信号与内存泄漏的综合排查及解决方案(OpenWrt/CellularManager 场景) --- #### **1. 问题关联性分析** `SIGTERM` 信号终止进程与 Valgrind 报告的内存泄漏可能通过以下方式关联: - **内存泄漏累积导致资源耗尽**:长期运行的程序(如 OpenWrt 的 `cellularmanager`)若存在内存泄漏,可能逐步消耗系统内存,触发 OOM Killer 或监控系统发送 `SIGTERM` 终止进程[^1]。 - **信号处理逻辑缺陷**:未在 `SIGTERM` 处理函数中释放动态分配的内存,导致 Valgrind 报告泄漏。 - **未初始化变量间接引发问题**:引用[1]提到的未初始化变量可能引发程序逻辑错误(如野指针),间接导致进程异常终止。 --- #### **2. OpenWrt 环境下 Valgrind 的针对性使用** ##### **步骤 1:交叉编译 Valgrind(若需)** OpenWrt 默认不包含 Valgrind,需交叉编译并安装: ```bash # 在 OpenWrt SDK 中操作 ./scripts/feeds update packages ./scripts/feeds install valgrind make package/valgrind/compile V=s make package/valgrind/install ``` ##### **步骤 2:运行 Valgrind 检测内存泄漏** 针对 `cellularmanager` 进程: ```bash valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --log-file=valgrind.log cellularmanager ``` - **关键参数解释**: - `--leak-check=full`:详细报告泄漏位置。 - `--show-leak-kinds=all`:显示所有类型泄漏(直接、间接、可能)。 - `--log-file`:输出到文件(OpenWrt 终端可能无滚动缓冲)。 --- #### **3. 内存泄漏与 SIGTERM 终止的关联排查** ##### **场景 1:内存泄漏触发 OOM Killer** - **现象**: Valgrind 报告 `definitely lost` 类型泄漏,系统日志(`dmesg`)显示 `Out of memory: Killed process`。 - **解决方案**: 1. 根据 Valgrind 日志定位泄漏点(如未配对的 `malloc/free`)。 2. 修复代码后,使用 `valgrind-massif` 分析内存增长趋势: ```bash valgrind --tool=massif --massif-out-file=massif.out cellularmanager ms_print massif.out > massif_analysis.txt ``` ##### **场景 2:SIGTERM 处理函数未释放资源** - **现象**: Valgrind 报告 `still reachable` 泄漏,且程序未在 `SIGTERM` 处理函数中释放资源。 - **代码修复示例**: ```c // 全局资源指针 static CellularContext *ctx = NULL; void sigterm_handler(int sig) { if (ctx != NULL) { cellular_manager_free(ctx); // 自定义释放函数 ctx = NULL; } exit(EXIT_SUCCESS); } int main() { signal(SIGTERM, sigterm_handler); ctx = cellular_manager_init(); // 初始化分配资源 // 主循环逻辑 return 0; } ``` --- #### **4. CellularManager 常见内存泄漏点(OpenWrt 特例)** 根据开源代码常见问题,重点检查以下模块: 1. **网络连接句柄泄漏** 如未关闭的 `ubus` 连接或 `netifd` 接口: ```c struct ubus_context *ctx = ubus_connect(NULL); // 需在退出时调用 ubus_free(ctx); ``` 2. **动态字符串未释放** 使用 `strdup` 或 `asprintf` 后未 `free`: ```c char *config_path = asprintf("/etc/cellular/%s.conf", device_id); // 使用后需 free(config_path); ``` 3. **异步事件回调未注销** 未在退出时移除事件监听: ```c uloop_fd_add(&socket_fd, ULOOP_READ); // 需在退出时调用 uloop_fd_delete(&socket_fd); ``` --- #### **5. OpenWrt 系统级排查** - **检查资源限制**: 通过 `ulimit -a` 查看进程内存限制,或修改 `/etc/security/limits.conf`。 - **监控系统日志**: ```bash logread -f | grep cellularmanager dmesg | grep -i "sigterm\|kill" ``` - **使用 `procd` 管理进程**: 在 `/etc/init.d/cellularmanager` 中配置 `reload_signal` 和 `stop_signal`,确保优雅终止: ```bash procd_set_param command /usr/sbin/cellularmanager procd_set_param reload_signal SIGUSR1 # 自定义重载信号 procd_set_param stop_signal SIGTERM # 默认终止信号 ``` --- #### **6. 验证修复效果** - **内存泄漏修复验证**: 重新运行 Valgrind,确保 `ERROR SUMMARY: 0 errors`。 - **压力测试**: 使用 `cellular_stress_test.sh` 模拟长期运行,观察是否仍触发 `SIGTERM`: ```bash while true; do cellularmanager --test-interface=ppp0 done ``` --- ### 相关问题 1. 如何在 OpenWrt 中交叉编译支持调试符号的 `cellularmanager`? 2. Valgrind 的 `still reachable` 泄漏是否必须修复? 3. 如何通过 `ubus` 监控 OpenWrt 进程的内存使用情况? [^1]: OOM Killer 根据 `oom_score` 选择终止进程,可通过 `/proc/<pid>/oom_score_adj` 调整优先级。 [^2]: OpenWrt 的 `procd` 默认发送 `SIGTERM`,若进程未在指定时间内退出,将发送 `SIGKILL`。 [^3]: `still reachable` 泄漏通常为全局变量未释放,需结合程序生命周期评估修复必要性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值