2015-11-19 71 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)


相关问题