8

我一直在研究 Linux 中的信号。我已经做了一个测试程序来捕获 SIGINT。

#include <unistd.h>
#include <signal.h>
#include <iostream>
void signal_handler(int signal_no);
int main() {
  signal(SIGINT, signal_handler);
  for (int i = 0; i < 10; ++i) {
  std::cout << "I'm sleeping..." << std::endl;
  unsigned int one_ms = 1000;
  usleep(200* one_ms);
  }
  return 0;
}
void signal_handler(int signal_no) {
  if (signal_no == SIGINT)
    std::cout << "Oops, you pressed Ctrl+C!\n";
  return;
}

虽然输出如下所示:

I'm sleeping...
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
I'm sleeping...
I'm sleeping...

我知道当按下 Ctrl+C 时,前台进程组中的进程都会收到一个 SIGINT(如果没有进程选择忽略它)。

那么shell(bash)和上述程序的实例是否都收到了信号?每个“Oops”之前的“^C”来自哪里?

操作系统是 CentOS,外壳是 bash。

4

2 回答 2

10

拦截 ^C 并将其转换为发送到附加进程(即外壳)的信号的是终端(驱动程序),stty intr ^B它将指示终端驱动程序拦截 ^B。也是终端驱动程序将 ^C 回显到终端。

shell 只是一个位于线路另一端的进程,它通过终端驱动程序(例如 /dev/ttyX)从终端接收它的标准输入,并且它的标准输出(和标准错误)也连接到同一个 tty .

请注意(如果启用了回显)终端将击键发送到进程(组)并返回到终端。stty 命令只是 ioctl()s 的包装,用于“控制”tty 进程的 tty 驱动程序。

更新:为了证明不涉及shell,我创建了以下小程序。它应该由其父 shell 执行exec ./a.out(无论如何,交互式 shell 会派生一个子 shell)程序将生成 SIGINTR 的密钥设置为 ^B,关闭回显,然后等待来自标准输入的输入。

#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

int thesignum = 0;
void handler(int signum);

void handler(int signum)
{ thesignum = signum;}

#define THE_KEY 2 /* ^B */

int main(void)
{
int rc;
struct termios mytermios;

rc = tcgetattr(0 , &mytermios);
printf("tcgetattr=%d\n", rc );

mytermios.c_cc[VINTR] = THE_KEY; /* set intr to ^B */
mytermios.c_lflag &= ~ECHO ; /* Dont echo */
rc = tcsetattr(0 , TCSANOW, &mytermios);
printf("tcsetattr(intr,%d) =%d\n", THE_KEY, rc );

printf("Setting handler()\n" );
signal(SIGINT, handler);

printf("entering pause()\n... type something followed by ^%c\n", '@'+THE_KEY );
rc = pause();
printf("Rc=%d: %d(%s), signum=%d\n", rc, errno , strerror(errno), thesignum );

// mytermios.c_cc[VINTR] = 3; /* reset intr to ^C */
mytermios.c_lflag |= ECHO ; /* Do echo */
rc = tcsetattr(0 , TCSANOW, &mytermios);
printf("tcsetattr(intr,%d) =%d\n", THE_KEY, rc );

return 0;
}

intr.sh:

#!/bin/sh
echo $$
exec ./a.out
echo I am back.
于 2012-05-26T12:47:53.990 回答
6

shell 会回显您键入的所有内容,因此当您键入^C时,也会回显(并且在您的情况下会被信号处理程序拦截)。stty -echo根据您的需要/限制,该命令可能对您有用也可能没用,有关更多信息,请参阅stty 的手册页。

当然,在较低级别上还有很多事情要做,只要您通过外围设备驱动程序(例如用于生成 ^C 信号的键盘驱动程序和显示所有内容的终端驱动程序)与系统进行通信。您可以在汇编/机器语言、寄存器、查找表等方面进行更深入的研究。如果您想要更详细、更深入的理解,以下书籍是一个不错的起点:

Unix OS 的设计是这类事情的一个很好的参考。还有两篇经典参考:Unix Programming EnvironmentAdvanced Programming in the UNIX Environment

在这个 SO 问题中很好的总结如何 Ctrl-C 终止子进程?

“当你运行一个程序时,例如find,shell:

  • 壳叉本身
  • 并为孩子设置默认信号处理
  • 用给定的命令替换孩子(例如用 find )
  • 当您按下 CTRL-C 时,父 shell 会处理此信号,但子 shell 会收到它 - 使用默认操作 - 终止。(孩子也可以实现信号处理)”
于 2012-05-26T12:03:10.303 回答