【问题标题】:ncurses interrupts system call when resizing terminal调整终端大小时,ncurses 中断系统调用
【发布时间】:2023-07-22 17:12:01
【问题描述】:

我遇到了 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);
    }
}

谢谢!

问候

【问题讨论】:

    标签: system-calls ncurses interruption socketpair


    【解决方案1】:

    大多数(如果不是全部)系统调用都有中断错误代码 (errno == EINTR),这是正常的。

    我会检查从管道读取的 EINTR 并忽略它,再次读取。

    我不会在信号处理程序中调用任何 ncurses 函数,有些是可重入的,但我怀疑 printw 是。只需进行 KEY_RESIZE 检查即可。

    【讨论】:

      【解决方案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;
          }
      }
      

      【讨论】: