2.6 KiB
2.6 KiB
app_cmd 线程 CPU 108% 问题修复
日期
2026-06-12
背景
app_cmd 线程是 CLI 命令行处理线程,属于 9 个应用线程之一。其主循环采用定时器驱动的事件模型(EV_TIMER1=10ms、EV_TIMER2=100ms、EV_TIMER3=1000ms)。原本 cmd_recv() 在 EV_TIMER1 中每次被调用,但该调用已被注释掉,原因是启用后 CPU 占用飙升至 108%。
根因
直接原因:read() 返回 EOF 未处理
linenoiseEdit()(my_cmd.cpp:107)中读取 stdin 的代码:
if (read(STDIN_FILENO, &c, 1) == -1) return -1;
只检查了 -1(错误),未处理返回 0(EOF)。当进程无真正控制终端时(RTU 嵌入式部署环境常见),stdin 处于 EOF 状态,read() 立即返回 0。代码不匹配 -1,继续执行,c 为未定义值,后落入 buf[len++] = c 分支,循环回 read() — 死循环,CPU 100%+。
架构层面问题
| 问题 | 说明 |
|---|---|
| 阻塞 I/O 在定时器线程中 | linenoise() 是同步阻塞调用,放在 10ms 定时器中导致线程被阻塞 |
| 无 stdin 就绪检查 | 没有用 select()/poll() 先检查 stdin 是否有数据 |
| 终端模式反复切换 | 每次 linenoise() 调用都 tcgetattr+tcsetattr,频率过高 |
tcsetattr 返回值未检查 |
linenoise() 中调用 enableRawMode() 未检查返回值 |
定时器机制
定时器使用 POSIX timer_create(CLOCK_REALTIME, SIGEV_THREAD, ...),每次超时内核创建新线程执行回调 → task_event_send → pthread_cond_signal 唤醒 app 线程。
修复内容
1. my_cmd.cpp — 修复 EOF 处理和返回值检查
read()返回值判断从== -1改为<= 0,覆盖 EOF 场景linenoise()中检查enableRawMode()返回值,失败直接返回 NULL,避免在有问题的终端上执行读取循环
2. app_cmd.cpp — 重构为非阻塞模式
- 新增
isatty(STDIN_FILENO)前置检查,非终端环境直接跳过 - 用
select()零超时检测 stdin 可读性,仅在数据就绪时才调用linenoise() - 从 EV_TIMER1(10ms)移至 EV_TIMER2(100ms),降低终端模式切换频率
验证
- 编译:零错误零警告
- 测试:
./test/RTU < /dev/null运行 3 秒,CPU 占用 0.0%(修复前为 108%)
影响文件
| 文件 | 改动 |
|---|---|
| my_cmd.cpp | read() EOF 处理;enableRawMode() 返回值检查 |
| app_cmd.cpp | 非阻塞 cmd_recv_nonblock();isatty + select;移至 EV_TIMER2 |
| CLAUDE.md | 全文翻译为中文 |