链式 ncurses 程序中的终端大小调整 (fork/exec/wait)
Terminal resize in chained ncurses programs (fork/exec/wait)
有一些基于 curses 的程序被启动,使用 fork/exec/wait
在它们之间创建一个进程链。
当 xterm
调整大小并且只有第一个程序 运行ning 时一切正常。但是,当第二个(或第三个)程序是 运行ning:
savetty{};
endwin();
// forking
if (fork() != 0) {
// at parent
wait(&status);
} else {
// child
if (execlp(cmd, cmd, (char*) NULL) < 0) {
exit(1);
}
}
// child exited: parent refresh its screen
resetty(); refresh();
所有程序似乎都尝试同时刷新屏幕。从这一点开始,屏幕变得一团糟。
每个程序在 exec
下一个程序之前需要做什么,以 "freezes" 诅咒直到 wait
returns ?
编辑:
当我将 fork/exec/wait
替换为 system()
时,一切正常。但我不能保留它(这只是一个测试),它是一个很大的遗留系统,强烈依赖于 fork/exec/wait
系统调用。
我也试过运行execlp("/bin/sh", "-c", cmd)
也是一样的问题
编辑 2:从头开始的完整示例代码:
// Uncomment to switch from fork/exec/wait to system() function
//#define USE_SYSTEM
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <curses.h>
#include <term.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
void mybox(int y, int x, int height, int width);
void msg(char* format, ...);
void draw();
int myexec(char* cmd);
int spawn_new_instance(char* cmd);
char* argid = NULL;
int main(int argc, char* argv[]) {
initscr();
cbreak();
keypad(stdscr, TRUE);
noecho();
draw();
if (argc > 1) {
argid=argv[1];
msg("That is a child process");
}
int pid;
int key;
do {
key = getch();
switch(key) {
case KEY_RESIZE:
clear(); draw();
msg("KEY_RESIZE");
break;
case 'r':
case 'R':
pid = spawn_new_instance(argv[0]);
if (argid) {
#ifdef USE_SYSTEM
msg("Came back from system()");
#else
msg("Came back from pid %d", pid);
#endif
} else {
msg("Came back from pid %d - THAT IS THE ROOT PROCESS", pid);
}
break;
default:
msg("Unsupported key '%d'. Type '.' (dot) to exit", key);
}
} while (key != '.');
endwin();
}
void fullbox(void) {
mybox(0, 0, LINES, COLS);
}
void mybox(int y, int x, int height, int width) {
int x2 = x + width - 1;
int y2 = y + height - 1;
for (int ct = x; ct < x2; ct++) {
mvaddch(y, ct, ACS_HLINE);
mvaddch(y2, ct, ACS_HLINE);
}
for (int ct = y; ct < y2; ct++) {
mvaddch(ct, x, ACS_VLINE);
mvaddch(ct, x2, ACS_VLINE);
}
mvaddch(y, x, ACS_ULCORNER);
mvaddch(y, x2, ACS_URCORNER);
mvaddch(y2, x, ACS_LLCORNER);
mvaddch(y2, x2, ACS_LRCORNER);
refresh();
}
void msg(char* format, ...) {
for (int ct = 2; ct < COLS - 2; ct++) {
mvaddch(LINES-3, ct, ACS_CKBOARD);
}
char buffer[512];
va_list argptr;
va_start(argptr, format);
vsprintf(buffer, format, argptr);
int msglen = strlen(buffer) + 2;
int msgx = (COLS - msglen)/2;
mvprintw(LINES-3, msgx, " %s ", buffer);
}
void draw() {
mybox(0, 0, LINES, COLS);
char sbuf[128];
sprintf(sbuf, "PID: %d, LINES: %d, COLS: %d", getpid(), LINES, COLS);
int msglen = strlen(sbuf);
int msgy = (LINES - 1) / 2;
int msgx = (COLS - msglen)/2;
mvprintw(msgy, msgx, "%s", sbuf);
mybox(msgy-2, msgx-2, 1 + 4, msglen + 4);
mybox((LINES - LINES/3)/2, (COLS - COLS/3)/2, LINES/3, COLS/3);
mybox(LINES-4, 1, 3, COLS-2);
msg("Resize the terminal emulator, or type R to chain new process instance");
refresh();
}
int spawn_new_instance(char* cmd) {
savetty();
endwin();
int pid;
#ifdef USE_SYSTEM
char buf[512];
sprintf(buf, "%s child", cmd);
system(buf);
// we haven't pid using system()
pid=0;
#else
pid = myexec(cmd);
#endif
resetty();
refresh();
return pid;
}
int myexec(char* cmd) {
sigset_t blockSigchld;
sigset_t previousBlock;
sigaddset(&blockSigchld, SIGCHLD);
sigprocmask(SIG_BLOCK, &blockSigchld, &previousBlock);
int ret = 0, status = 0;
int retries = 4;
int pid;
while ((pid = fork()) == -1) {
if (errno == EAGAIN) {
if (--retries >= 0) {
sleep(1);
continue;
} else {
msg("Cannot open the process now.");
return -1;
}
} else if (errno == ENOMEM) {
msg("Not enough memory.");
return -1;
} else {
msg("Errno = %u", errno);
return -1;
}
}
if (pid != 0) { /* Parent */
ret = waitpid(pid, &status, 0);
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (ret == -1) {
return -1;
}
return pid;
} else { /* Child */
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (execlp(cmd, cmd, "child", (char*) NULL) < 0) {
exit(1);
}
}
}
说明:
- 键入 "R" 以生成其自身的链式新实例。
- 键入“。” (点)退出并 return 到父进程。
- 在第一个(主)进程中,调整终端的大小。一切正常。
- 问题:打开大量实例(10 个或更多),调整终端大小。看它 refresh/redrawn 屏幕多次,可能每个实例一个 运行ning。尝试 return 到父级,似乎从这一点开始他们都在尝试读取 de stdin 并同时重绘其屏幕,真正的混乱发生了。如果运气好的话,您可以按
CTRL+C
或输入 pkill testprogram
.
- 使用 system() 函数尝试上述操作。问题没有发生。出于测试目的,第一行是#define,可以轻松地从 fork/exec/wait 切换到 system().
正如@KarstenKoop 的评论所建议的那样,阻止 SIGWINCH 解决了问题。
根据unix.stackexchange.com/q/85364/114939查看进程监听的信号,发现没有进程阻塞SIGWINCH信号,即使是system()启动的进程也是如此。
但是,有了这个在 fork/exec/wait
之前阻止 SIGWINCH 的解决方案,我们可以确认它确实在 /proc/{pid}/status 中被标记为被阻止。就像 SigBlk:0000000008010000,其中 0x8 是被阻止的 SIGWINCH。
虽然它解决了问题,但我不明白 system()
在不阻塞 SIGWINCH 的情况下启动进程的作用,即使如此一切正常。
有一些基于 curses 的程序被启动,使用 fork/exec/wait
在它们之间创建一个进程链。
当 xterm
调整大小并且只有第一个程序 运行ning 时一切正常。但是,当第二个(或第三个)程序是 运行ning:
savetty{};
endwin();
// forking
if (fork() != 0) {
// at parent
wait(&status);
} else {
// child
if (execlp(cmd, cmd, (char*) NULL) < 0) {
exit(1);
}
}
// child exited: parent refresh its screen
resetty(); refresh();
所有程序似乎都尝试同时刷新屏幕。从这一点开始,屏幕变得一团糟。
每个程序在 exec
下一个程序之前需要做什么,以 "freezes" 诅咒直到 wait
returns ?
编辑:
当我将 fork/exec/wait
替换为 system()
时,一切正常。但我不能保留它(这只是一个测试),它是一个很大的遗留系统,强烈依赖于 fork/exec/wait
系统调用。
我也试过运行execlp("/bin/sh", "-c", cmd)
也是一样的问题
编辑 2:从头开始的完整示例代码:
// Uncomment to switch from fork/exec/wait to system() function
//#define USE_SYSTEM
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <curses.h>
#include <term.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
void mybox(int y, int x, int height, int width);
void msg(char* format, ...);
void draw();
int myexec(char* cmd);
int spawn_new_instance(char* cmd);
char* argid = NULL;
int main(int argc, char* argv[]) {
initscr();
cbreak();
keypad(stdscr, TRUE);
noecho();
draw();
if (argc > 1) {
argid=argv[1];
msg("That is a child process");
}
int pid;
int key;
do {
key = getch();
switch(key) {
case KEY_RESIZE:
clear(); draw();
msg("KEY_RESIZE");
break;
case 'r':
case 'R':
pid = spawn_new_instance(argv[0]);
if (argid) {
#ifdef USE_SYSTEM
msg("Came back from system()");
#else
msg("Came back from pid %d", pid);
#endif
} else {
msg("Came back from pid %d - THAT IS THE ROOT PROCESS", pid);
}
break;
default:
msg("Unsupported key '%d'. Type '.' (dot) to exit", key);
}
} while (key != '.');
endwin();
}
void fullbox(void) {
mybox(0, 0, LINES, COLS);
}
void mybox(int y, int x, int height, int width) {
int x2 = x + width - 1;
int y2 = y + height - 1;
for (int ct = x; ct < x2; ct++) {
mvaddch(y, ct, ACS_HLINE);
mvaddch(y2, ct, ACS_HLINE);
}
for (int ct = y; ct < y2; ct++) {
mvaddch(ct, x, ACS_VLINE);
mvaddch(ct, x2, ACS_VLINE);
}
mvaddch(y, x, ACS_ULCORNER);
mvaddch(y, x2, ACS_URCORNER);
mvaddch(y2, x, ACS_LLCORNER);
mvaddch(y2, x2, ACS_LRCORNER);
refresh();
}
void msg(char* format, ...) {
for (int ct = 2; ct < COLS - 2; ct++) {
mvaddch(LINES-3, ct, ACS_CKBOARD);
}
char buffer[512];
va_list argptr;
va_start(argptr, format);
vsprintf(buffer, format, argptr);
int msglen = strlen(buffer) + 2;
int msgx = (COLS - msglen)/2;
mvprintw(LINES-3, msgx, " %s ", buffer);
}
void draw() {
mybox(0, 0, LINES, COLS);
char sbuf[128];
sprintf(sbuf, "PID: %d, LINES: %d, COLS: %d", getpid(), LINES, COLS);
int msglen = strlen(sbuf);
int msgy = (LINES - 1) / 2;
int msgx = (COLS - msglen)/2;
mvprintw(msgy, msgx, "%s", sbuf);
mybox(msgy-2, msgx-2, 1 + 4, msglen + 4);
mybox((LINES - LINES/3)/2, (COLS - COLS/3)/2, LINES/3, COLS/3);
mybox(LINES-4, 1, 3, COLS-2);
msg("Resize the terminal emulator, or type R to chain new process instance");
refresh();
}
int spawn_new_instance(char* cmd) {
savetty();
endwin();
int pid;
#ifdef USE_SYSTEM
char buf[512];
sprintf(buf, "%s child", cmd);
system(buf);
// we haven't pid using system()
pid=0;
#else
pid = myexec(cmd);
#endif
resetty();
refresh();
return pid;
}
int myexec(char* cmd) {
sigset_t blockSigchld;
sigset_t previousBlock;
sigaddset(&blockSigchld, SIGCHLD);
sigprocmask(SIG_BLOCK, &blockSigchld, &previousBlock);
int ret = 0, status = 0;
int retries = 4;
int pid;
while ((pid = fork()) == -1) {
if (errno == EAGAIN) {
if (--retries >= 0) {
sleep(1);
continue;
} else {
msg("Cannot open the process now.");
return -1;
}
} else if (errno == ENOMEM) {
msg("Not enough memory.");
return -1;
} else {
msg("Errno = %u", errno);
return -1;
}
}
if (pid != 0) { /* Parent */
ret = waitpid(pid, &status, 0);
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (ret == -1) {
return -1;
}
return pid;
} else { /* Child */
sigprocmask(SIG_SETMASK, &previousBlock, (sigset_t *) 0);
if (execlp(cmd, cmd, "child", (char*) NULL) < 0) {
exit(1);
}
}
}
说明:
- 键入 "R" 以生成其自身的链式新实例。
- 键入“。” (点)退出并 return 到父进程。
- 在第一个(主)进程中,调整终端的大小。一切正常。
- 问题:打开大量实例(10 个或更多),调整终端大小。看它 refresh/redrawn 屏幕多次,可能每个实例一个 运行ning。尝试 return 到父级,似乎从这一点开始他们都在尝试读取 de stdin 并同时重绘其屏幕,真正的混乱发生了。如果运气好的话,您可以按
CTRL+C
或输入pkill testprogram
. - 使用 system() 函数尝试上述操作。问题没有发生。出于测试目的,第一行是#define,可以轻松地从 fork/exec/wait 切换到 system().
正如@KarstenKoop 的评论所建议的那样,阻止 SIGWINCH 解决了问题。
根据unix.stackexchange.com/q/85364/114939查看进程监听的信号,发现没有进程阻塞SIGWINCH信号,即使是system()启动的进程也是如此。
但是,有了这个在 fork/exec/wait
之前阻止 SIGWINCH 的解决方案,我们可以确认它确实在 /proc/{pid}/status 中被标记为被阻止。就像 SigBlk:0000000008010000,其中 0x8 是被阻止的 SIGWINCH。
虽然它解决了问题,但我不明白 system()
在不阻塞 SIGWINCH 的情况下启动进程的作用,即使如此一切正常。