簡體   English   中英

C shell,被父捕獲的信號仍進入子進程。

[英]C shell, signal caught by parent still goes to child process.

我正在用C語言編寫一個基本的unix外殼程序,我想在外殼程序中捕獲Cntrl-C信號並將其僅傳遞給前台進程,而不傳遞給后台進程。 外殼程序本身應繼續運行(並且確實如此),並且后台進程應忽略Cntrl-C,並且只能通過專門發送給它們的終止信號(可能是通過命令行“ kill pid ”) 終止 但是,前台和后台進程都應使用SIGCHLD觸發處理程序。 但是,現在,外殼捕獲了Cntrl-C信號, 似乎可以正確地識別出沒有將信號傳遞到的前台進程,但是后台進程仍然消失了。

我嘗試將后台進程的組ID設置為其他名稱,這可以解決問題,但會產生一個新問題。 當我這樣做時,后台進程完成時,我的信號處理程序將不再捕獲信號。

到目前為止,我已經查看了SIGINT的手冊頁,已經閱讀了約20個SO答案,我嘗試將孩子的組ID設置為與父組不同的名稱(解決了問題,但是現在孩子不能再將SIGCHLD發送給父進程),而在運行后台進程時,我已經檢查了childid!=前台進程和frontendProcess == 0。 但是,后台進程仍然被殺死。 有任何想法嗎?

我認為我的問題出在我的信號處理程序中,但是不確定:

在主要方面:

  struct sigaction sa;
  sa.sa_handler = &handleSignal; /*passing function ref. to handler */
  sa.sa_flags = SA_RESTART;  
  sigfillset(&sa.sa_mask); /*block all other signals while handling sigs */

  sigaction(SIGUSR1, &sa, NULL);
  sigaction(SIGINT, &sa, NULL);
  sigaction(SIGCHLD, &sa, NULL);
  sigaction(SIGTERM, &sa, NULL);

handleSignal看起來像這樣:

 void handleSignal(int signal){
  int childid;
  switch (signal) { 
        /*if the signal came from a child*/
       case SIGCHLD:  
        /*get the child's id and status*/  
        childid = waitpid(-1,&childStatus,0);

        /*No action for foreground processes that exit w/status 0 */
        /*otherwise show pid & showStatus */
          if ((childid != foregroundProcess)){
            printf("pid %i:",childid);
            showStatus(childStatus);
            fflush(stdout);
          }
          break;

        /* if signal came from somewhere else, pass it to foreground child */
        /* if one exists. */
        default:
          printf("Caught signal: %i and passing it", signal);
          printf(" to child w/pid: %i\n\n:", foregroundProcess);
          fflush(stdout);

          /*If there is a child, send signal to it. */
          if (foregroundProcess){
            printf("trying to kill foreground.\n");
            fflush(stdout);
            kill(foregroundProcess, signal);
          }     
      }
    }

找到了我自己問題的答案。 我已經嘗試過使用setpid(0,0);更改后台子進程的組ID setpid(0,0); 這行得通,但產生了另一個問題。 通話之后,我不再從父級的子級捕獲SIGCHLD信號。 這是因為一旦更改了子進程組,出於信令目的,它實際上不再連接到父進程組。 這解決了后台進程從父進程捕獲Cntrl-C(SIGINT)信號的問題(不良行為),但阻止了后台進程在完成時向父進程發信號。 解決了一個問題,只解決了另一個問題。

相反,解決方案是檢測將要創建一個子進程作為前台進程還是后台進程,如果是后台進程,則告訴它忽略SIGINT信號: signal(SIGINT, SIG_IGN);

由於這可能是tty上的第一個進程,因此內核在發送內核啟動的SIGINT,SIGHUP或SIGQUIT時,會將信號發送給其所有子進程。 有關終端的更多詳細信息,請參閱在終端關閉時終止sudo python腳本 ,以及跟蹤/調試正在發生的事情以確保正確使用的想法。


啟動bg子進程忽略SIGINT(和SIGQUIT,也許還有SIGHUP?)的一種替代方法是避免讓內核將這些信號傳遞給Shell。

我忘記了,但是我認為內核僅將SIGINT傳遞給當前的前台進程。 如果cat或其他物體在前台運行,則外殼將無法獲得它。 因此,您只需要擔心外殼在前台時會發生什么情況。 (不過,我對此只有80%的把握,如果外殼(及其所有子容器)始終收到SIGINT,則此想法毫無用處。)

如果在用戶編輯命令提示符時禁用tty的信號發送,則可以避免讓外殼接收SIGINT。 最好的方法可能是使用tcsetattr(3)做與stty -isig等效的操作

// disable interrupts
struct termios term_settings;
tcgetattr(fd, &term_settings);     // TODO: check errors
term_settings.c_lflag &= ~ISIG;   // clear the interactive signals bit
tcsetattr(fd, TCSANOW, &term_settings);

// do the reverse (settings |= ISIG) after forking, before exec

如果您strace ,你會只看到ioctl系統調用,因為這是系統調用的termios庫函數的基礎上實現的。

我猜這在子進程結束與wait()返回與外殼禁用終端中斷之間留出了很小的時間窗口。

IIRC,我讀到一些有關bash難以跟蹤何時在raw(用於行編輯)和Cooked(用於運行命令)之間交換終端的技巧,但是我認為這僅是由於作業控制。 (^ z / fg)


暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM