【问题标题】:Prevent read() systemcall returing with 0 when run as background process当作为后台进程运行时,防止 read() 系统调用返回 0
【发布时间】:2019-07-31 09:20:44
【问题描述】:

我有一个软件能够从标准输入读取命令以在单独的线程中进行调试。当我的软件作为前台进程运行时read 的行为与预期一样,它会阻塞并等待用户输入,即线程休眠。 当软件作为后台进程运行时,read 不断返回0(可能检测到 EOF?)。

这里的问题是,这个特定的读取是在while(true) 循环中。它尽可能快地运行,并在我的嵌入式设备上窃取宝贵的 CPU 负载。

我尝试将/dev/null 重定向到进程,但行为是相同的。我在 ARM Cortex A5 板上运行我的自定义 Linux。

有问题的代码如下并在其自己的线程中运行:

char bufferUserInput[256];
const int sizeOfBuffer = SIZE_OF_ARRAY(bufferUserInput);

while (1)
{

    int n = read(0, bufferUserInput, sizeOfBuffer); //filedes = 0 equals to reading from stdin
    printf("n is: %d\n", n);
    printf("Errno: %s",strerror(errno));

    if (n == 1)
    {
        continue;
    }

    if ((1 < n)
            && (n < sizeOfBuffer)
            && ('\n' == bufferUserInput[n - 1]))
    {
        printf("\r\n");
        bufferUserInput[n - 1] = '\0';
        ProcessUserInput(&bufferUserInput[0]);
    } else
    {
        n = 0;
    }
}

我正在寻找一种方法来防止read 在后台运行时不断返回并等待用户输入(当然永远不会出现)。

【问题讨论】:

  • 如果使用文件作为输入调用发布的代码,也将无法正确处理输入,例如app &lt; input.txt。在这种情况下,read() 将完全填充bufferUserInput,然后您的代码将丢弃该输入,因为n 将等于sizeOfBuffer。此外,read() 返回 ssize_t,而不是 int
  • 一个文件永远不会是输入,它只是用于读取值的简短调试命令。你对返回值的权利。

标签: c linux background-process


【解决方案1】:

如果您从 shell脚本 在“后台”(如 ./program &amp;)中启动程序,它的标准输入将从 /dev/null 重定向(带有一些 exceptions)。

尝试从 /dev/null 读取将始终返回 0 (EOF)。

示例(在 linux 上):

sh -c 'ls -l /proc/self/fd/0 & wait'
... -> /dev/null

sh -c 'dd & wait'
... ->  0 bytes copied, etc

上面链接中的修复也应该对你有用:

#! /bin/sh
...
exec 3<&0
./your_program <&3 &
...

【讨论】:

    【解决方案2】:

    stdin 不是终端时,read 将返回 0,因为您位于文件末尾。 read 仅在读取所有可用输入后才阻塞,可能 将来会有更多输入,这被认为对于终端、管道、套接字等是可能的,但对于常规文件和 @ 都不是987654325@。 (是的,另一个进程可以使常规文件变大,但read 的规范中没有考虑这种可能性。)

    忽略其他人指出的读取循环的各种问题(您应该修复无论如何,因为这将使从用户读取调试命令更加可靠)对您的代码进行最简单的更改这将解决您现在遇到的问题:在启动时检查 stdin 是否是终端,如果不是,则不要启动调试线程。您可以使用在 unistd.h 中声明的 isatty 函数来做到这一点。

    #include <stdio.h>
    #include <unistd.h>
    // ...
    
    int main(void)
    {
        if (isatty(fileno(stdin)))
          start_debug_thread();
        // ...
    }
    

    (根据您的使用上下文,当stdin 是管道或套接字时运行调试线程也可能有意义,但我个人不会打扰,我会依靠ssh 来提供远程(必要时伪)终端。)

    【讨论】:

    • 谢谢,这解决了我的一般问题,但我仍然想知道为什么 read 会返回 0。我测试了@PSkocik 代码 sn-p 并得到了相同的结果。
    • read 返回 0,因为您位于文件末尾。 read 仅在读取所有可用输入后才阻塞,当将来可能有更多输入时,这对于终端、管道、套接字等是可能的,但对于文件和 /dev/null 都不是。
    【解决方案3】:

    read() 在后台进程中从终端读取时不返回 0。 它要么继续阻塞,同时导致将SIGTTIN 发送到进程(这可能会破坏阻塞并导致retval=-1,errno=EINTR 被返回,或者如果SIGTTIN 被忽略,则会导致retval=-1, errno EIO

    下面的 sn-p 演示了这一点:

    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    int main()
    {
        char c[256];
        ssize_t nr;
    
        signal(SIGTTIN,SIG_IGN);
        nr = read(0,&c,sizeof(c));
        printf("%zd\n", nr);
        if(0>nr) perror(0);
        fflush(stdout);
    }
    

    您显示的代码 sn-p 不可能测试显示 0-return,因为您从不测试返回值是否为零。

    【讨论】:

    • read 之后,我在快速调试版本中检查nerrno 的值。我得到 n 是:0 和 Errno:成功。我在我的代码 sn-p 中添加了两个调试行
    • @J.Panek 我让我的 sn-p 读取 256 个字节,就像你所做的一样(更改对行为没有影响)。当我编译并执行./a.out &amp; 时,如果SIG_IGN 行被注释掉,我会得到Stopped,否则会得到Input/output error。如果输入是终端并且进程是后台的,我永远不会得到返回值0。如果这样做,请发布有关如何重现此类行为的具体步骤。
    • 嵌入式设备上的软件通过/etc/init.d中的启动脚本启动,它将stdoutstderr重定向到两个日志文件中。我在读取块周围使用while(1) 测试了您的代码 sn-p 并获得了相同的行为,即我的日志文件充满了0,并且 CPU 负载为 100%
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2021-11-29
    • 2015-06-11
    • 1970-01-01
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    相关资源
    最近更新 更多