我有一个在嵌入式 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 就会起作用。
有几个技巧:
使用
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"
第一个猫的目的是将数据发送到接收器。重定向可能会失败或者接收器最终可能会消失。第二个来了
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
无论如何都可以从函数的标准输入中读取。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)