ncurses прерывает системный вызов при изменении размера терминала
У меня проблема с ncurses, и я не смог найти решения в Интернете, поэтому я написал следующую небольшую программу, чтобы продемонстрировать эту проблему.
Вы можете скомпилировать его через:
sudo aptitude install ncurses-dev
g++ -lncurses -o resize resize.cpp
Он отображает целочисленный счетчик, увеличиваемый каждую секунду путем разветвления в процесс таймера, который периодически отправляет один байт родительскому процессу через пару сокетов. Вы можете выйти из него, нажав CTRL+C.
При изменении размера терминала вы должны получить сообщение об ошибке "Прерванный системный вызов". Таким образом, вызов чтения прерывается SIGWINCH при изменении размера. Но как я могу избежать этого? Или часто системный вызов прерывается? Но как бы я обработал прерванный системный вызов, чтобы продолжить увеличивать счетчик, так как дескриптор файла кажется мертвым после прерывания.
Если вы используете неблокирующие сокеты, вы получите вместо этого "Ресурс временно недоступен".
Я использую стабильный Debian Wheezy, поэтому версия ncurses 5.9-10, а версия libstdC++ 4.7.2-5.
#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>
//Define a second.
timespec span = {1, 0};
//Handles both, SIGWINCH and SIGINT
void handle(int signal) {
switch (signal) {
case SIGWINCH:
//Reinitialize ncurses to get new size
endwin();
refresh();
printw("Catched SIGWINCH and handled it.\n");
refresh();
break;
case SIGINT:
//Catched CTRL+C and quit
endwin();
exit(0);
break;
}
}
//This registers above signal handler function
void set_handler_for(int signal) {
struct sigaction action;
action.sa_handler = handle;
action.sa_flags = 0;
if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
throw "Cannot set signal handler";
}
main() {
int fd[2];
//In this try block we fork into the timer process
try {
set_handler_for(SIGINT);
set_handler_for(SIGWINCH);
//Creating a socketpair to communicate between timer and parent process
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
throw "Cannot create socketpair";
pid_t pid;
//Doing the fork
if (-1 == (pid = fork()))
throw "Cannot fork process";
if (!pid) {
//We are the timer, so closing the other end of the socketpair
close(fd[0]);
//We send one byte every second to the parent process
while (true) {
char byte;
ssize_t bytes = write(fd[1], &byte, sizeof byte);
if (0 >= bytes)
throw "Cannot write";
nanosleep(&span, 0);
}
//Here the timer process ends
exit(0);
}
//We are the parent process, so closing the other end of the socketpair
close(fd[1]);
}
catch (const char*& what) {
std::cerr << what << std::endl;
exit(1);
}
//Parent process - Initializing ncurses
initscr();
noecho();
curs_set(0);
nodelay(stdscr, TRUE);
//In this try block we read (blocking) the byte from the timer process every second
try {
int tick = 0;
while (true) {
char byte;
ssize_t bytes = read(fd[0], &byte, sizeof byte);
if (0 >= bytes)
throw "Cannot read";
//Clear screen and print increased counter
clear();
mvprintw(0, 0, "Tick: %d - Resize terminal and press CTRL+C to quit.\n", ++tick);
//Catch special key KEY_RESIZE and reinitialize ncurses to get new size (actually not necassary)
int key;
while ((key = getch()) != ERR) {
if (key == KEY_RESIZE) {
endwin();
refresh();
printw("Got KEY_RESIZE and handled it.\n");
}
}
//Update the screen
refresh();
}
}
catch (const char*& what) {
//We got an error - print it but don't quit in order to have time to read it
std::string error(what);
if (errno) {
error.append(": ");
error.append(strerror(errno));
}
error = "Catched exception: "+error+"\n";
printw(error.c_str());
refresh();
//Waiting for CTRL+C to quit
while (true)
nanosleep(&span, 0);
}
}
Спасибо!
С уважением
2 ответа
Ладно, у меня все получилось, используя только входящие функции в обработчиках сигналов. Теперь пара сокетов все еще работает после EINTR или EAGAIN.
Спасибо!
#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>
// Define a second.
timespec base = {1, 0};
// Holds raised SIGINTs.
size_t raised_SIGINT = 0;
// Holds raised SIGWINCHs.
size_t raised_SIGWINCH = 0;
// Handle SIGWINCH
void handle_SIGWINCH(int) {
++raised_SIGWINCH;
}
// Handle SIGINT
void handle_SIGINT(int) {
++raised_SIGINT;
}
// Registers signal handlers.
void assign(int signal, void (*handler)(int)) {
struct sigaction action;
action.sa_handler = handler;
action.sa_flags = 0;
if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
throw "Cannot set signal handler";
}
// Prints ticks alive and usage information.
inline void print(size_t ticks) {
mvprintw(0, 0, "%ds alive. Resize terminal and press CTRL+C to quit.\n\n", ticks);
}
int main() {
// Holds the two socketpair file descriptors.
int fd[2];
// Fork into the timer process.
try {
// Register both signals.
assign(SIGINT, handle_SIGINT);
assign(SIGWINCH, handle_SIGWINCH);
// Create a socketpair to communicate between timer and parent process.
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
throw "Cannot create socketpair";
// Doing the fork.
pid_t pid;
if (-1 == (pid = fork()))
throw "Cannot fork process";
if (!pid) {
// We are the timer, so closing the parent end of the socketpair.
close(fd[0]);
// We send one byte every second to the parent process.
while (true) {
timespec less = base;
int ret;
// Continue sleeping after SIGWINCH but only for the time left.
while (-1 == (ret = nanosleep(&less, &less)) and errno == EINTR and raised_SIGWINCH);
// Maybe quit by user.
if (raised_SIGINT)
return 0;
// If something went wrong, terminate.
if (-1 == ret)
throw "Cannot sleep";
// Repeated writing if interrupted by SIGWINCH.
char byte;
ssize_t bytes;
do {
// Doing the write.
bytes = write(fd[1], &byte, sizeof byte);
}
while (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);
// Maybe quit by user.
if (raised_SIGINT)
return 0;
// If something went wrong, terminate.
if (0 >= bytes)
throw "Cannot write";
}
// Here the timer process ends.
return 0;
}
// We are the parent process, so closing the timer end of the socketpair.
close(fd[1]);
}
catch (const char*& what) {
// Print fatal error and terminate timer process causing parent process to terminate, too.
std::cerr << what << std::endl;
return 1;
}
// Initializing ncurses for the parent process.
initscr();
// Disable typing.
noecho();
// Disable cursor.
curs_set(0);
// Make reading characters non-blocking.
nodelay(stdscr, TRUE);
// Catch fatal errors.
try {
// Holds ticks alive.
size_t ticks = 0;
// Blockingly read the byte from the timer process awaiking us every second.
while (true) {
// Print ticks alive before incrementing them.
print(ticks++);
// Holds typed keys.
std::string keys;
// Read typed keys.
for (int key = getch(); key != ERR; key = getch())
if (key != KEY_RESIZE)
keys += key;
// Format typed keys string.
if (keys.size())
printw("You've typed: ");
else
keys += "\n";
keys += "\n\n";
// Print typed keys string.
printw(keys.c_str());
// Doing the prints.
refresh();
// Repeated reading if interrupted by SIGWINCH.
ssize_t bytes = 0;
bool again = false;
do {
// Doing the read.
char byte;
bytes = read(fd[0], &byte, sizeof byte);
again = (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);
// Print how often we got interrupted by SIGWINCH per time base.
if (again) {
// Next two calls are the common way to handle a SIGWINCH.
endwin();
refresh();
// For simpicity clear everything.
clear();
// Re-print ticks.
print(ticks);
// Print the interruption counter.
printw("%dx catched SIGWINCH per time base.\n\n", raised_SIGWINCH);
// Doing the prints.
refresh();
}
}
while (again);
// Reset SIGWINCH raises per time base.
raised_SIGWINCH = 0;
// Maybe quit by user.
if (raised_SIGINT) {
endwin();
return 0;
}
// If something went wrong, terminate.
if (0 >= bytes)
throw "Cannot read";
}
}
catch (const char*& what) {
// We got an error, appending errno if set.
std::string error(what);
if (errno) {
error.append(": ");
error.append(strerror(errno));
}
error = "Catched exception: "+error+"\n";
// Print the fatal error.
printw(error.c_str());
//Doing the print.
refresh();
// Waiting for CTRL+C to quit.
while (true)
nanosleep(&base, 0);
// Quit by user.
endwin();
return 0;
}
}
Большинство (если не все) системные вызовы имеют прерванный код ошибки (errno == EINTR), это нормально.
Я бы проверил EINTR на чтение из канала и проигнорировал его, просто прочитал снова.
Я бы не стал вызывать какие-либо функции ncurses в обработчике сигналов, некоторые являются входящими, но я сомневаюсь, что printw есть. Просто сделайте проверку KEY_RESIZE.