为什么Linux按下Ctrl+C时总是输出“^C”?

问题描述 投票:0回答:2

我一直在研究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,shell是bash。

linux bash signals sigint
2个回答
13
投票

终端(驱动程序)拦截 ^C 并将其转换为发送到附加进程(即 shell)的信号

stty intr ^B
将指示终端驱动程序拦截 ^B。它也是将 ^C 回显到终端的终端驱动程序。

shell 只是一个位于线路另一端的进程,通过终端驱动程序(例如 /dev/ttyX)从终端接收它的 stdin,并且它的 stdout(和 stderr)也附加到同一个进程。蒂。

请注意(如果启用回显)终端会将击键发送到“两个”进程(组)并返回到终端。 stty 命令只是 tty 驱动程序的 ioctl() 的包装,用于“控制”tty 的进程。 更新:为了证明不涉及shell,我创建了以下小程序。它应该由其父 shell 通过

exec ./a.out

执行(无论如何,交互式 shell 都会分叉子 shell)程序将生成 SIGINTR 的密钥设置为 ^B,关闭 echo,然后等待来自 stdin 的输入.


#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.



5
投票
^C

时,也会回显(在您的情况下会被信号处理程序拦截)。命令

stty -echo
对您可能有用也可能没用,具体取决于您的需求/限制,请参阅 stty
 的手册页了解更多信息。 
当然,在较低级别上还会发生更多事情,随时

,您通过外围设备驱动程序与系统进行通信(例如用于生成 ^C 信号的键盘驱动程序,以及显示所有内容的终端驱动程序) 。您可以在汇编/机器语言、寄存器、查找表等层面进行更深入的挖掘。如果您想要更详细、更深入的理解,下面的书籍是一个很好的起点:

The Design of the Unix OS

对于这类事情来说是一个很好的参考。另外两本经典参考书:

Unix编程环境UNIX 环境中的高级编程 这个问题的总结很好Ctrl-C 如何终止子进程?

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

,shell:

壳叉本身

    并为子进程设置默认信号处理
  • 用给定的命令替换子项(例如,使用 find)
  • 当您按 CTRL-C 时,父 shell 会处理此信号,但子 shell 将收到它 - 使用默认操作 - 终止。 (孩子也可以实现信号处理)”
© www.soinside.com 2019 - 2024. All rights reserved.