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を受け取ることを理解しています(プロセスがそれを無視することを選択しない場合)。

それで、シェル(bash)と上記のプログラムのインスタンスの両方がシグナルを受信したのですか?各「おっと」の前の「^C」はどこから来ていますか?

OSはCentOSで、シェルはbashです。

4

2 に答える 2

10

^ Cをインターセプトし、それを接続されたプロセス(シェル)に送信されるシグナルに変換するのはターミナル(ドライバー)であり、stty intr ^B代わりに^Bをインターセプトするようにターミナルドライバーに指示します。^Cをターミナルにエコーバックするのもターミナルドライバーです。

シェルは、回線のもう一方の端にあるプロセスであり、ターミナルドライバー(/ dev / ttyXなど)を介してターミナルからstdinを受け取り、stdout(およびstderr)も同じttyに接続されます。 。

(エコーが有効になっている場合)端末はキーストロークをプロセス(グループ)と端末の両方に送信することに注意してください。sttyコマンドは、ttyを「制御」するプロセスのttyドライバーのioctl()をラッパーするだけです。

更新:シェルが関与していないことを示すために、次の小さなプログラムを作成しました。親シェルが経由して実行する必要がありますexec ./a.out(とにかく、インタラクティブシェルはドーターシェルをフォークするようです)プログラムは、SIGINTRを生成するキーを^ Bに設定し、エコーをオフにして、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.
于 2012-05-26T12:47:53.990 に答える
6

シェルは入力したものすべてをエコーするため、入力する^Cと、それもエコーされます(この場合、シグナルハンドラーによってインターセプトされます)。このコマンドstty -echoは、ニーズや制約に応じて役立つ場合と役に立たない場合があります。詳細については、sttyのマニュアルページを参照してください。

もちろん、周辺機器を介してシステムと通信する場合は常に、より低いレベルでさらに多くのことが行われます。デバイスドライバー(^ C信号の生成に使用するキーボードドライバーや、すべてを表示するターミナルドライバーなど)が関係します。アセンブリ/機械語、レジスタ、ルックアップテーブルなどのレベルでさらに深く掘り下げることができます。より詳細で詳細なレベルの理解が必要な場合は、以下の本から始めることをお勧めします。

Unix OSの設計は、この種のものの良い参考資料です。さらに2つの古典的なリファレンス:Unixプログラミング環境UNIX環境での高度なプログラミング

このSOの質問のここにある素晴らしい要約Ctrl-Cはどのようにして子プロセスを終了しますか?

「たとえばfind、シェルなどのプログラムを実行すると、次のようになります。

  • シェルフォーク自体
  • 子の場合、デフォルトの信号処理を設定します
  • 子を指定されたコマンドに置き換えます(例:find)
  • CTRL-Cを押すと、親シェルがこのシグナルを処理しますが、子はそれを受け取ります-デフォルトのアクションで-終了します。(子供も信号処理を実装できます)」
于 2012-05-26T12:03:10.303 に答える