子进程中按Ctrl Z导致ncurses的reset_prog_mode失效问题排查
解决ncurses程序调用vim挂起恢复后的终端异常问题
问题分析
当在ncurses应用中启动vim并通过Ctrl-Z挂起、恢复后,出现光标可见无法隐藏、屏幕缓冲区残留、退出后终端状态未恢复的问题,根源是未正确处理作业控制信号(SIGTSTP/SIGCONT)导致终端模式和ncurses状态混乱,且子进程的暂停/继续未同步主进程的终端状态切换。
解决方案
1. 为主进程注册SIGTSTP信号处理函数
用户按Ctrl-Z时,整个进程组(主进程+vim子进程)都会收到SIGTSTP信号,必须在主进程中捕获该信号,正确保存/恢复ncurses状态:
#include <signal.h> #include <unistd.h> void handle_sigtstp(int sig) { // 保存当前ncurses程序模式,切换到终端原始模式 def_prog_mode(); endwin(); // 发送SIGTSTP给自己,使进程挂起(必须做这一步,否则进程不会真正暂停) kill(getpid(), SIGTSTP); // 进程恢复执行后,重置ncurses模式 reset_prog_mode(); // 强制刷新所有窗口,清除残留画面 redrawwin(stdscr); refresh(); // 重新隐藏光标 curs_set(0); } // 在程序初始化阶段(initscr()之后)注册信号 signal(SIGTSTP, handle_sigtstp);
2. 简化子进程等待逻辑
无需在waitpid循环中处理WIFSTOPPED/WIFCONTINUED,信号处理函数会自动处理终端状态切换,等待逻辑只需等待子进程退出:
def_prog_mode(); endwin(); pid_t pid = fork(); if (pid == -1) { perror("fork"); } else if (pid == 0) { // 子进程中无需保留ncurses相关状态,直接启动vim execl("vim", "vim", "<file>", NULL); perror("execl"); // execl失败时执行 _exit(1); } else { // 只等待子进程退出,暂停/恢复由信号处理函数处理 int child_return; waitpid(pid, &child_return, 0); } reset_prog_mode(); redrawwin(stdscr); refresh(); curs_set(0); // 确保光标隐藏
3. 优化程序退出逻辑
退出时先确保所有窗口被正确删除,然后调用reset_shell_mode()强制恢复终端原始状态,再执行endwin():
exit: // 删除所有自定义窗口 delwin(win); delwin(bottom_win); delwin(win_border); delwin(bottom_border); // 强制恢复shell终端模式 reset_shell_mode(); endwin(); // 刷新终端,确保状态生效 refresh(); return 0;
关键说明
- SIGTSTP信号是终端发送给整个前台进程组的,因此主进程必须捕获并处理,否则ncurses的状态会和终端实际状态脱节。
kill(getpid(), SIGTSTP)是必须的:信号处理函数会拦截SIGTSTP,所以需要手动发送该信号让进程真正挂起。redrawwin(stdscr)比refresh()更彻底,因为它会强制重绘整个窗口,清除残留的终端画面。
内容的提问来源于stack exchange,提问作者Plasmaist of Plasmas
相关产品推荐
相关产品推荐

