防止输出重定向目标消失

防止输出重定向目标消失

我有一个在嵌入式 Linux 设备上运行的守护进程,其输出重定向到串行控制台:

my_daemon > /dev/ttyS0

但是现在,如果用户exit在串行接口上​​运行 shell,那么串行设备将被重新创建,这会导致串行设备暂时消失,从而导致我的守护进程崩溃。

是否有一些(直接的)方法来防止这种情况发生?也许中间有一些包装器(或与进程结合的命名管道),一旦管道目标消失,就会重新尝试重新连接到管道目标?不需要缓冲“离线”时间。

答案1

使用以下 shell 函数:

relay () (
#!/bin/sh
sink="${1:-/dev/ttyS0}"
exec 4<&0 2>/dev/null
while :; do
   cat 3<"$sink" >/proc/self/fd/3
   <&4 cat >/dev/null & sleep 2; kill -s PIPE "$!" || exit
done
)

像这样:

my_daemon | relay

看起来像 shebang ( #!/bin/sh) 的东西在函数内部没有任何意义。它只是表明代码适用于什么 shell。如果您想构建脚本而不是函数,请将函数体保存在可执行文件中,然后 shebang 就会起作用。

有几个技巧:

  1. 使用cat >/dev/ttyS0或类似重定向的解决方案可能存在缺陷。我假设你的话“重新创建串行设备”意味着存在一个/dev/ttyS0不存在的时间窗口。我还假设运行代码的用户(可能是 root)可以在/dev/.如果>/dev/ttyS0没有这样的文件,则会创建一个常规文件。您可以像这样进行测试! [ -e /dev/ttyS0 ],但文件可能会在测试之后和之前消失>/dev/ttyS0

    因此我使用cat 3<"$sink" >/proc/self/fd/3,默认情况下$sink/dev/ttyS0。第一个重定向尝试打开文件进行读取;第二个重定向尝试将 stdout 重定向到同一文件。诀窍是第二次重定向永远不会创建新文件。

    另一种方法是进行安排,以便用户可以访问/dev/ttyS0,但无法在 中创建文件/dev/。在这种情况下,不需要这个技巧,并且使用的解决方案>/dev/ttyS0应该是安全的。如果您出于某种原因无法使用,这可能是一种有效的方法/proc/self/fd/3。第一个cat就是:

    cat >"$sink"
    
  2. 第一个猫的目的是将数据发送到接收器。重定向可能会失败或者接收器最终可能会消失。第二个来了cat。第二个的目的cat是在没有接收器时丢弃数据。如果/dev/ttyS0应该立即重新创建,我们就不需要第二个cat。但我不确定路径名是否保证在您的情况下很快重新出现。我想当没有的时候/dev/ttyS0,你会想my_daemon继续而不是阻止。第二个cat让它继续。

    简单的cat >/dev/null不是一个好主意,即使在/dev/ttyS0重新创建后它也可能无限期地运行。诀窍是异步运行它并在几秒钟后终止它。然后代码循环,第一个cat尝试再次打开水槽。

    <&4因为有技巧所以需要。当作业控制被禁用时(默认情况下,它位于我们的函数所在的子 shell 中,或在脚本中),命令终止时&会将其标准输入重定向到/dev/null或等效文件。多亏了 previousexec 4<&0和 this <&4,第二个cat无论如何都可以从函数的标准输入中读取。

  3. kill如果第二个cat在 后不再存在,则(很可能)会失败sleep 2。如果my_daemon退出就会发生这种情况。kill … || exit是检测 EOF 条件的技巧。例如date | relay应该终止,尽管由于sleep.如果没有这个技巧,代码将无限循环。

    在 EOF 的情况下,第二个 PID 可能cat会被重用并kill定位到错误的进程。 AFAIK Linux 中的 PID 是按顺序分配的;序列不太可能在大约两秒内回绕并获得相同的 PID。该技巧假设如果第二个cat由于 EOF 提前退出,kill则会看到no such process并失败,因此exit会发生。

默认值为$sink/dev/ttyS0您仍然可以通过将其指定为第一个命令行参数来使用另一个路径名(例如… | relay foo)。如果您想测试常规文件,请记住在文件打开时将其删除不破坏它。在我的测试中,我常常set -x看看会发生什么,然后Ctrl+d cat根据需要轻松终止(第一个)

我的测试平台:Linux 内核 5.15.0,sh( ash) 来自 Busybox 1.30.1。

答案2

也许是这样的:

缓冲区粘贴程序

#!/bin/sh

while true;do
  cat buffer_file > /dev/ttyS0
done

然后运行这个:

my_daemon > buffer_file
./bufferpaster.sh

答案3

好的,在没有信息的情况下strace,我们可以检查/等待有效的设备文件。假设守护进程输出以换行符结尾的行(对于read),那么可能是这样的:

tty=/dev/ttyS0
my_daemon | ( 
    # Start with a read of 1 line, to catch when the daemon exits, and then "cat" the rest
    while read line; do 
        # check/wait for device file to exist and be writable
        until [ -w $tty ]; do sleep 1; done
        echo "$line"  >> $tty
        cat >> $tty
    done
)

附加>>是为了防止您想先测试文件,而不是测试ttyS0

答案4

将 stdout 写入日志文件,然后让 initsystem inittab/systemd 将日志文件 tail -F 发送到串行端口。 Init 将负责确保 tail 正在串行端口上运行,您可以专注于编写守护进程。您将需要进行日志轮换,但是您可以使用一些实用程序(例如 Piper、Multilog),这里有关于如何通过管道 stdout 创建日志文件的完整讨论:https://superuser.com/questions/291368/log-rotation-of-stdout

相关内容