2015-11-19 53 views
1

我在C編寫一個基本的unix shell,我想在shell中捕獲Cntrl-C信號,並將它們僅傳遞給前臺進程,但不傳遞給後臺進程。 shell本身應該繼續運行(而且它會),後臺進程應該忽略一個Cntrl-C並且只能通過專門發送給它們的kill信號來殺死,可能通過命令行「kill pid」。但是,前臺和後臺進程都應該使用SIGCHLD觸發處理程序。然而,現在,外殼捕捉到Cntrl-C信號,並且似乎正確地識別出沒有前臺進程傳遞信號,但後臺進程仍然死亡。C shell,父類捕獲的信號仍然進入子進程。

我試着將後臺進程的組ID設置爲別的,並且解決了這個問題,但是它創建了一個新問題。當我這樣做時,當後臺進程完成時,我的信號處理程序不再捕獲信號。

到目前爲止,我已經看過SIGINT的手冊頁,我已經閱讀了20個左右的答案,我試着將孩子的組ID設置爲與父組不同的東西(解決問題,但現在孩子不能再發送SIGCHLD給父母了),並且我在運行後臺進程時檢查了childid!= foreground process和foregroundProcess == 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); 
      }  
     } 
    } 
+0

在一個不相關的音符,如果你還沒有改變的'stdout'你不需要,只要你最終所有的輸出與換行符明確地刷新它的緩衝模式。 'stdout'默認是行緩衝的。 –

+0

感謝Joachim。由於printf的存在是爲了調試目的,所以我對緩衝區進行了額外的細心處理。我不希望某些打印線實際上被打印到不打印,因爲另一個打印線在此期間做了一些奇怪的事情。無論如何,所有這些printf可能都會在最終產品中消失。 – Fish314

回答

2

找到一個答案我自己的問題。我已經嘗試使用setpid(0,0);更改後臺子進程的組標識,並且此方法可行,但創建了另一個問題。在那次電話會議之後,我不再從父母的孩子那裏接收到SIGCHLD信號。這是因爲一旦孩子的進程組發生了變化,它就基本上不再爲了信號傳遞而連接到父進程。這解決了後臺進程從父節點捕獲Cntrl-C(SIGINT)信號的問題(不希望的行爲),但阻止後臺進程在完成時向父節點發送信號。解決了一個問題,只能創建另一個問題。

相反,解決辦法是檢測兒童是否是關於作爲前臺或後臺進程被創建,如果背景,告訴它忽略SIGINT信號:signal(SIGINT, SIG_IGN);

+0

我認爲像Bash這樣的交互式shell會在用戶編輯提示時通過將tty置於原始模式來處理它。 http://stackoverflow.com/questions/31907212/will-ctrlc-send-sigint-signals-to-both-parent-and-child-processes-in-linux。所以如果你願意的話,你仍然可以'kill -INT'後臺進程。如果你是後臺進程,它仍然可以被編輯。您的方法對於不會實現作業控制(bg/fg)或花哨行編輯的玩具殼來說是合理的。 –

+0

這就是我正在建造的東西。一個基本的,寫自己的shell,並沒有太多的特性作爲類的一部分。謝謝你的信息。我將不得不做更多的研究。 – Fish314

0

因爲它可能是第一個進程在tty上,內核會在發送內核啓動的SIGINT,SIGHUP或SIGQUIT時將信號發送給其所有子節點。有關更多詳細信息,請參閱Terminate sudo python script when the terminal closes,以及有關追蹤/調試所發生事件的想法,以確保您的工作正確。


起始BG子的替代處理出忽略SIGINT(和SIGQUIT,也許SIGHUP?)是避免具有內核提供這些信號到外殼。

我忘了,但我認爲內核只向當前的前臺進程傳送SIGINT。如果在前臺運行cat或其他東西,你的shell就不會得到它。所以你只需要擔心shell在前臺時會發生什麼。 (儘管如此,我只有大約80%的可信度,如果shell(以及它的所有孩子)總是收到SIGINT,這個想法就毫無用處。)

如果在用戶編輯命令提示符時禁用tty的信號發送,則可以避免讓shell收到SIGINT。也許最好的辦法是做的stty -isig等效,使用tcsetattr(3)

// 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()以及shell禁用終端中斷之間留下一個小小的時間窗口。

IIRC,我讀了一些關於bash難以跟蹤何時在raw(用於行編輯)和cooked(用於要運行的命令)之間交換終端的信息,但我認爲那只是因爲作業控制。 (^ Z/FG)