c - 在 C/UNIX 中使用 SIGINT 信号时遇到问题

标签 c unix signals

我有一个运行 UNIX 基本 shell 的 C 程序,我希望做一些特定的事情。本质上,每当我按 CTRL+C 时,我都会杀死所有子进程,但保持父进程处于事件状态,以便循环返回下一行输入。下面的代码似乎可以按照日期、whoami 等基本命令的预期工作。但是,当我发出以下命令进行测试时,shell 不会循环返回以获取更多输入,并且之后输入的任何文本都不执行任何操作。

$sleep 100 
$CTRL+C

为了停止 shell,我必须使用 CTRL+Z 强制程序停止。我需要 SIGINT 信号来中断子进程的 sleep 命令,并强制父进程在杀死子进程后返回提示用户,但显然我做错了一些事情。这是解释我的意思的输出。

$ ./a3shell2
--------myShell > date
 Input command is: date
Sat Nov  9 15:38:08 CST 2019
--------myShell > whoami
 Input command is: whoami
klm46
--------myShell > ^C
Caught SIGINT!
killing children
date
 Input command is: date
Sat Nov  9 15:38:20 CST 2019
--------myShell > sleep 100
 Input command is: sleep 100
^C
Caught SIGINT!
killing children
date
date
whoami
^Z
[2]+  Stopped                 ./a3shell2

Shell 程序:

/* ----------------------------------------------------------------- */
/* PROGRAM  simple shell.c progam                                    */
/* ----------------------------------------------------------------- */
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>

pid_t pid = 0;

void sig_int(int signo) 
{
    printf("\nCaught SIGINT!\n");
  printf("killing children\n"); 
  if (pid != 0) 
  {
    kill(pid, SIGKILL); 
    pid = 0; 
  }
}

char *getinput(char *buffer, size_t buflen) 
{
    printf("--------myShell > ");
    return fgets(buffer, buflen, stdin);
}

/* ----------------------------------------------------------------- */
/* FUNCTION  parse:                                                  */
/* ----------------------------------------------------------------- */

void  parse(char *line, char **argv)
{
     while (*line != '\0') {       /* if not the end of line ....... */ 
          while (*line == ' ' || *line == '\t' || *line == '\n')
               *line++ = '\0';     /* replace white spaces with 0    */
          *argv++ = line;          /* save the argument position     */
          while (*line != '\0' && *line != ' ' && 
                 *line != '\t' && *line != '\n') 
               line++;             /* skip the argument until ...    */
     }
     *argv = '\0';                 /* mark the end of argument list  */
}

/* ----------------------------------------------------------------- */
/* FUNCTION execute:                                                 */
/* ----------------------------------------------------------------- */

void execute(char **argv)
{
     //pid_t  pid;
     int    status;

     if ((pid = fork()) < 0) {     /* fork a child process           */
          printf("*** ERROR: forking child process failed\n");
          exit(1);
     }
     else if (pid == 0) {          /* for the child process:         */
          if (execvp(*argv, argv) < 0) {     /* execute the command  */
               printf("*** ERROR: exec failed\n");
               exit(1);
          }
     }
     else {                                  /* for the parent:      */
          while (wait(&status) != pid)       /* wait for completion  */
               ;
     }
}

/* ----------------------------------------------------------------- */
/*                  The main program starts here                     */
/* ----------------------------------------------------------------- */

void  main(void)
{

     char  line[1024];             /* the input line                 */
     char  *argv[64];              /* the command line argument      */
     size_t linelen;

if (signal(SIGINT, sig_int) == SIG_ERR) 
  {
        fprintf(stderr, "signal error: %s\n", strerror(errno));
        exit(1);
    }

     while (1) 
     {                   /* repeat until done ....         */
          getinput(line, sizeof(line));
          line[strlen(line) - 1] = '\0';
          printf(" Input command is: %s \n", line);

          parse(line, argv);       /*   parse the line               */

          if (strcmp(argv[0], "exit") == 0)  /* is it an "exit"?     */
               exit(0);            /*   exit if it is                */

          execute(argv);           /* otherwise, execute the command */
     }
}

最佳答案

当您 CTRL+C 时,您的 shell 陷入无限的 wait(&status) != pid 循环。

SIGINT 中断 wait(),并且您的信号处理程序将全局 pid 变量设置为零。当控制权返回到循环时,它永远不会结束:pid 为零,并且 wait() can only return PIDs and -1 ,永远不会为零。

(您不会在 datewhoami 中看到此行为,因为在您发出 CTRL+ 时这些子进程已经终止C。在这种情况下,您可能正在中断 fgets(),而不是 wait(),并向已获取的 PID 发送 SIGKILL。那就是也不好,因为当您发送 SIGKILL 时 PID 可能会被重新使用。)

顺便说一句,目前尚不清楚当 SIGINT 中断 wait() 调用时实际会发生什么,因为 signal() 在不同平台上的行为历来有所不同。调用可能会被中断,返回 -1 (EINTR),或者可能会恢复 wait(),返回被kill() 处理的子进程的 PID。在这种情况下,这是没有意义的——这两个都不符合您当时正在寻找的零——但这是 use sigaction() and not signal() 的一个很好的理由。 .

关于c - 在 C/UNIX 中使用 SIGINT 信号时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58783794/

相关文章:

c - 此 C 代码片段背后的安全考虑

linux - 如果 CPU 使用率持续高于一定数量,则发送警报电子邮件

c - 帮助在 C 中实现 GNU Readline

c++ - boost::signals2 的包装器,具有通用插槽的生命周期管理

c - 如何在 OpenGL 中获取矩阵堆栈的当前大小?

ios - 在不知道它们是 IPv4 还是 IPv6 的情况下预创建套接字

c - 将 char 变量传递给数组中的下一个值 - C

linux - 如何使用 tasket 将 cpu 核心分配给带有参数的程序的多个实例

c - Linux - 为什么阻塞和忽略的信号处于待处理状态?

python - 如何从单元测试模拟终端 CTRL + C 事件?