2013-10-13 5 views
1

У меня проблема с ncurses и не удалось найти решение в Интернете, поэтому я написал следующую небольшую программу, чтобы продемонстрировать проблему.ncurses прерывает системный вызов при изменении размера терминала

Вы можете скомпилировать его с помощью:

sudo aptitude install ncurses-dev 
g++ -lncurses -o resize resize.cpp 

Он отображает целочисленный счетчик увеличивается каждую секунду разветвление в процессе таймера, который периодически посылает один байт родительского процесса через socketpair. Вы можете выйти из нее, нажав 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); 
    } 
} 

Спасибо!

С уважением

ответ

0

Большинство (если не все) системные вызовы имеют прерванный код ошибки (ERRNO == EINTR), это нормально.

Я бы проверил EINTR на чтении из трубы и проигнорировал его, просто прочитал снова.

Я бы не назвал какие-либо функции ncurses в обработчике сигналов, некоторые из них являются повторителями, но я сомневаюсь, что это printw. Просто выполните проверку KEY_RESIZE.

0

Хорошо, я работал, только используя функции повторного входа в обработчики сигналов. Теперь socketpair все еще работает после 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; 
    } 
}