3

我现在拥有的代码将提示发送到标准输出,然后从标准输入读取一行。在任何时候接收SIGINT都会中断执行并退出程序。我不确定我应该在哪里捕获SIGINT,并且我知道当我当前的代码收到信号时我无法启动新的提示。是否有适当的方法来实现这一点(最终目标是让它像大多数 shell 一样工作(SIGINT 取消当前提示并开始一个新提示))?

此代码将在 Linux 上运行,但平台独立性越低越好。

get_line从标准输入读取一行到缓冲区并生成一个 char[],分配给 line。
split_args取一行并将其转换为 char[] 数组,在空白处拆分。
is_command_valid确定用户是否键入了已知的内部命令。无法执行外部程序。

static int run_interactive(char *user)
{
    int done = 0;

    do
    {
        char *line, **args;
        int (*fn)(char *, char **);

        fprintf(stderr, "gitorium (%s)> ", user);
        get_line(&line);

        if (line[0] == '\0')
        {
            free(line);
            break;
        }

        split_args(&args, line);

        if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") ||
            !strcmp(args[0], "logout") || !strcmp(args[0], "bye"))
            done = 1;
        else if (NULL == args[0] ||
            (!strcmp("help", args[0]) && NULL == args[1]))
            interactive_help();
        else if ((fn = is_command_valid(args)) != NULL)
            (*fn)(user, args);
        else
            error("The command does not exist.");

        free(line);
        free(args);
    }
    while (!done);

    return 0;
}

这是两个最重要的辅助函数

static int split_args(char ***args, char *str)
{
    char **res = NULL, *p = strtok(str, " ");
    int n_spaces = 0, i = 0;

    while (p)
    {
        res = realloc(res, sizeof (char*) * ++n_spaces);

        if (res == NULL)
            return GITORIUM_MEM_ALLOC;

        res[n_spaces-1] = p;
        i++;
        p = strtok(NULL, " ");
    }

    res = realloc(res, sizeof(char*) * (n_spaces+1));
    if (res == NULL)
        return GITORIUM_MEM_ALLOC;

    res[n_spaces] = 0;
    *args = res;

    return i;
}

static int get_line(char **linep)
{
    char *line = malloc(LINE_BUFFER_SIZE);
    int len = LINE_BUFFER_SIZE, c;
    *linep = line;

    if(line == NULL)
        return GITORIUM_MEM_ALLOC;

    for(;;)
    {
        c = fgetc(stdin);
        if(c == EOF || c == '\n')
            break;

        if(--len == 0)
        {
            char *linen = realloc(*linep, sizeof *linep + LINE_BUFFER_SIZE);
            if(linen == NULL)
                return GITORIUM_MEM_ALLOC;

            len = LINE_BUFFER_SIZE;
            line = linen + (line - *linep);
            *linep = linen;
        }

        *line++ = c;
    }

    *line = 0;
    return 0;
}
4

1 回答 1

2

如果我对您的理解正确,您想知道如何处理信号以及收到信号后该怎么做。

建立信号处理程序的方式是使用sigaction(). 您没有说明您所在的平台,所以我假设 Linux,尽管sigaction()它是由 POSIX 标准定义的,并且应该可以在大多数其他平台上使用。

有多种方法可以做到这一点。一种方法是建立一个信号处理程序,它简单地将一个全局变量设置为 1,表示信号已被捕获。然后在你的getline()函数中建立一个检查是否SIGINT被捕获,如果它被返回NULL并允许run_interactive()再次运行。

以下是捕捉信号的方法:

#include <signal.h>

static int sigint_caught = 0;

static void sigint_handler(int sig) {
  sigint_caught = 1;
}

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // or SA_RESTART if you want to automatically restart system calls interrupted by the signal
sa.sa_handler = sigint_handler;

if (sigaction(SIGINT, &sa, NULL) == -1) {
  printf("could not establish handler\n");
  exit(-1); // or something
}

然后也许在getline()无限循环中,您将建立检查以查看是否SIGINT已被捕获:

for (;;) {
  if (sigint_caught) {
    return NULL;
  }

  // ...

然后在您的run_interactive()通话中,您可以通过检查检查返回值查看是否SIGINT被捕获:

// ...

get_line(&line);

if (line == NULL && sigint_caught) {
  sigint_caught = 0; // allow it to be caught again
  free(line);
  continue; // or something; basically go to the next iteration of this loop
} else if (line[0] == '\0') {
  free(line);
  break;
} else {
  // rest of code

没有测试它,所以我不能保证它会起作用,因为你的问题非常广泛(必须查看更多的代码等),但希望它能让你对你能做什么有足够的了解你在你的情况。这可能是一个非常幼稚的解决方案,但它可能会满足您的需求。对于更强大的东西,也许可以查看流行 shell 的源代码,如 bash 或 zsh。

例如,可能发生的一件事是fgetc()由于标准输入中没有新数据而可能会阻塞,并且可能是在发送信号时。fgetc()会被打断errnoEINTR所以你可以在下面添加一个检查getline()

c = fgetc(stdin);

// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
  return NULL;

仅当您不设置sa_flags为 时才会发生这种情况SA_RESTART。如果你这样做了,那么fgetc应该自动重新启动并继续阻塞,直到收到新的输入,这可能是也可能不是你想要的。

于 2013-04-08T23:36:35.303 回答