如何在linux c中的CTRL-C之后清除stdout

时间:2016-03-31 07:10:43

标签: c linux stdout sigint

我们不希望在用户通过CTRL-C中断后打印任何内容。我们已尝试在sigInt信号处理程序中添加__fpurge以及fflush,但它无效。

如何立即清除缓冲的stdout值?我遇到过几个类似的线程,但没有找到可行的解决方案。

其他信息很少: 在sigInt信号处理程序内部,即使添加了exit(0),缓冲区内容也会被打印,但处理器被杀死了。

添加退出(0)以缩小问题范围,我不想杀死处理器

我知道上面是预期的行为,不知道如何避免它。

4 个答案:

答案 0 :(得分:1)

考虑这个编辑过的例子 - 编辑过;这个没有退出过程:

#define  _POSIX_C_SOURCE 200809L /* For nanosleep() */
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>

static void exit_handler(int signum)
{
    int fd, result;

    /* If the standard streams are connected to a tty,
     * tell the kernel to discard already buffered data.
     * (That is, in kernel buffers. Not C library buffers.)
    */
    if (isatty(STDIN_FILENO))
        tcflush(STDIN_FILENO, TCIOFLUSH);
    if (isatty(STDOUT_FILENO))
        tcflush(STDOUT_FILENO, TCIOFLUSH);
    if (isatty(STDERR_FILENO))
        tcflush(STDERR_FILENO, TCIOFLUSH);

    /* Redirect standard streams to /dev/null,
     * so that nothing further is output.
     * This is a nasty thing to do, and a code analysis program
     * may complain about this; it is suspicious behaviour.
    */
    do {
        fd = open("/dev/null", O_RDWR);
    } while (fd == -1 && errno == EINTR);
    if (fd != -1) {
        if (fd != STDIN_FILENO)
            do {
                result = dup2(fd, STDIN_FILENO);
            } while (result == -1 && (errno == EINTR || errno == EBUSY));
        if (fd != STDOUT_FILENO)
            do {
                result = dup2(fd, STDOUT_FILENO);
            } while (result == -1 && (errno == EINTR || errno == EBUSY));
        if (fd != STDERR_FILENO)
            do {
                result = dup2(fd, STDERR_FILENO);
            } while (result == -1 && (errno == EINTR || errno == EBUSY));
        if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
            close(fd);
    }
}

static int install_exit_handler(const int signum)
{
    struct sigaction act;
    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = exit_handler;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;
    return 0;
}

int main(void)
{
    if (install_exit_handler(SIGINT)) {
        fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    while (1) {
        struct timespec t = { .tv_sec = 0, .tv_nsec = 200000000L };

        printf("Output\n");
        fflush(stdout);

        nanosleep(&t, NULL);
    }

    /* Never reached. */
    return EXIT_SUCCESS;
}

当进程收到SIGINT信号时,它将首先刷新内核终端缓冲区中的任何内容,然后将标准流重定向到/dev/null(即无处)。

请注意,您需要通过向其发送TERM或KILL信号(即另一个终端中的killall ./yourprogname)来终止该过程。

当您通过远程连接运行详细过程时,很多信息可能始终在飞行中。本地机器和运行该进程的远程机器都将使它们的套接字缓冲区几乎已满,因此latency可能比通常大得多 - 在这种情况下我甚至在快速(GbE)上看到了几个第二次延迟本地网络。

这意味着将信号从本地机器传播到远程机器将花费可测量的时间;在最坏的情况下,大约几秒钟。只有这样,远程进程才会停止输出数据。所有待处理的数据仍然必须从远程机器传输到本地机器,这可能需要相当长的时间。 (通常,瓶颈是终端本身;在大多数情况下,更快来最小化终端,因此它不会尝试呈现它接收的任何文本,只在内部缓冲它。)< / p>

这就是为什么 Ctrl + C 不会,也不能立即停止远程输出。

在大多数情况下,您将使用SSH连接到远程计算机。该协议也没有“清除”功能,这可能对此有所帮助。包括我自己在内的很多人都考虑过这个问题 - 至少我的香肠手指不小心将 tab 放到了可执行文件而不是同名的输出文件中,而且不仅让终端充满了垃圾,而且二进制文件中的特殊字符有时会将终端状态(例如xterm control sequencesANSI escape codes)设置为不可恢复的内容(即 Ctrl + Z 通过reset 输入不会将终端重置回工作状态;如果是,kill -KILL %- ; fg将停止Bash中的错误命令,并让你的终端返回),并且您需要断开连接,这也将终止从在后台远程运行的同一终端启动的所有进程。

这里的解决方案是使用终端多路复用器,如GNU screen,它允许您连接到远程机器或从远程机器断开连接,而不会中断现有的终端连接。 (换句话说,screen是远程机器上的终端头像。)

答案 1 :(得分:0)

问题是,Posix和Linux库都没有声明fpurge__fpurge在信号处理函数中是安全的。正如DevSolar所解释的那样,C语言itsel并没有为标准库声明许多安全函数(至少_Exit,但Posix明确允许closewrite。所以,你总是可以关闭底层文件描述符应为1:

void handler(int sig) {
    static char msg[] = "Interrupted";
    write(2, msg, sizeof(msg) - 1);  // carefully use stderr here
    close(1);  // foo is displayed if this line is commented out
    _Exit(1);
}
int main() {
    signal(SIGINT, handler);
    printf("bar");
    sleep(15);
    return 0;
}

当我在睡眠期间输入Ctrl-C时,它会按预期显示:

$ ./foo
^CInterrupted with 2
$

close系统调用应该足够了,因为它会关闭底层文件描述符。因此,即使稍后尝试刷新stdout缓冲区,它们也会在封闭的文件描述符上写入,因此根本没有效果。缺点是stdout已被重定向,程序应将基础文件描述符的新值存储在全局变量中。

答案 2 :(得分:0)

首先,引用C11标准,强调我的:

  

7.14.1.1 signal功能

     

5如果信号的出现不是调用abortraise函数的结果,如果信号处理程序调用了函数中的任何函数,则行为是未定义的。标准库除了abort函数,_Exit函数,quick_exit函数或signal函数,第一个参数等于对应的信号编号导致调用处理程序的信号。

这意味着调用fflush 未定义的行为

查看可能调用的函数,abort_Exit都会使缓冲区实现定义,quick_exit调用_Exit因为我无法找到关于Linux行为的实现定义,所以就标准而言,你运气不好。 (惊喜。不。)

唯一的其他&#34;终止&#34; function,exit 刷新缓冲区,你可能不会首先从处理程序中调用它。

所以你必须看看特定于Linux的功能。 _exit的手册页没有对缓冲区做出任何声明。 close手册页警告不要关闭可能被其他线程的系统调用使用的文件描述符,并指出&#34;文件系统在流关闭时刷新缓冲区并不常见#34 ;,意思是它可以发生(即close不保证实际丢弃未写入的缓冲区内容。)

此时,如果我是你,我会问自己#34;毕竟这是一个好主意&#34; ......

答案 3 :(得分:0)

如果你在信号处理程序中执行kill(getpid(), SIGKILL);(这是异步安全的),你会立即被操作系统杀死(因为你想要退出(0))。不再期待进一步的产出。

唯一的问题:您将无法正常清理,之后在主线程中正常退出。如果你负担得起......