【问题标题】:How to handle Control-C signal while designing a shell?设计外壳时如何处理 Control-C 信号?
【发布时间】:2019-12-20 04:39:13
【问题描述】:

我正在尝试实现 shell 中存在的一些功能,包括仅在用户输入 quit 而不是 Ctrl+C 时退出。下面是我尝试过的代码的简化版本。

代码 1:没有在信号处理程序中调用 loop()。

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

代码1的输出:

从第三个输入可以看出,我转到一个新行并从我离开循环的地方继续。我想重新开始循环。因此,我通过在信号处理程序本身中调用循环进行了以下修改。

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
    loop();
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

代码 2 的输出:

可以看出,当我第一次单击 Ctrl+C(在输入第 3 行上)时,它可以正常工作,我可以继续。但是当我第二次单击 Ctrl+C 时,我没有转到新行,我必须按 enter 才能执行程序。

我处理了this 的问题,但它似乎不适用于我的情况。这是我第一次使用信号和系统调用,所以我的问题可能很愚蠢。但是,如果有人可以帮助我找到实现信号的正确方法,那将是一个很大的帮助。谢谢。

【问题讨论】:

  • 不要使用signal()——使用sigaction()(见What is the difference between sigaction() and signal()?)。不要在信号处理程序中使用printf();它可能导致未定义的行为(请参阅How to avoid using printf() in a signal handler)。不要在信号处理程序中使用loop()——这是完全不支持的。
  • @J.Panek:signal() 的某些变体重置信号处理程序并要求信号处理程序恢复信号处理。在sigaction() 中查找SA_RESETHAND,这在很大程度上允许使用sigaction() 模拟古董signal() 实现。标准 C 中signal() 的语义是最小的; POSIX 中signal() 的语义反映了在 POSIX 标准化之前实际实现的分歧。 POSIX 推荐sigaction() 有很多原因。
  • 当您在信号处理程序中有loop() 调用时,您可能会发现中断信号仍然被阻塞,因为您还没有从原始信号处理程序返回。此外,在信号处理程序中调用loop() 意味着您正在递归调用loop()(尽管是间接递归)。对loop() 的原始调用也没有结束。您的loop() 没有错误检查;我希望那是因为您创建了一个 MCVE (Minimal, Complete, Verifiable Example?)(或 MRE 或 SO 现在使用的任何名称)。
  • 你应该注意scanf()的返回值。如果它被中断,它可能会返回一个错误条件——如果它被中断,你不应该使用a中的值。
  • 看看scanf()返回了什么。如果它显示1,则可以使用a 的内容。如果它显示0EOF,您还有更多工作要做。此后,您可能需要使用 clearerr(stdin) 从终端获取任何有意义的信息。然后您可以返回并再次提示。

标签: c shell operating-system signals


【解决方案1】:

Jonathan Leffler 提供了有用的提示(尽管还不够,至少在某些流行的操作系统上是这样):

  • 看看scanf()返回什么。
  • 不要在信号处理程序中使用loop()
  • 不要使用signal()——使用sigaction()。在您的情况下,这有两个优点:
    • 可以避免在调用信号处理程序时将信号操作恢复到默认状态,因此您不必在处理程序中再次更改操作。
    • 可以避免重新启动read() 系统调用(在scanf() 内),以便scanf() 返回,您可以在第一时间对中断做出反应。

所以,信号处理程序可以只是

void sigintHandler(int sig_num)
{
    printf("\n");   // or perhaps better write(1, "\n", 1)
}

main() 中的signal(SIGINT, sigintHandler); 可以替换为

  sigaction(SIGINT, &(struct sigaction){ .sa_handler = sigintHandler }, NULL);

scanf("%s",a); 一起

      if (scanf("%s", a) == EOF)
      {
          if (errno == EINTR) continue; // read operation interrupted by signal
          return;
      }

EOF 在中断和文件结束时都会返回,因此需要通过errno 来区分这些情况。此外,如果您的程序提供了退出它的方法,那就太好了,例如return

【讨论】:

  • 好吧,我缺少的是errno 的支票。好消息是它在我的原始代码中适用于getc。我一定会记住您的建议。谢谢。
猜你喜欢
  • 1970-01-01
  • 2021-12-16
  • 1970-01-01
  • 2020-04-01
  • 2021-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多