Bash:无限睡眠(无限阻塞)
- 2024-10-28 08:37:00
- admin 原创
- 68
问题描述:
我使用startx
来启动 X,它将评估我的.xinitrc
。在我的 中,.xinitrc
我使用 来启动我的窗口管理器/usr/bin/mywm
。现在,如果我终止我的 WM(为了测试其他 WM),X 也会终止,因为脚本.xinitrc
已到达 EOF。因此,我在 的末尾添加了以下内容.xinitrc
:
while true; do sleep 10000; done
这样,如果我关闭 WM,X 就不会终止。现在我的问题是:如何实现无限睡眠而不是循环睡眠?是否有类似冻结脚本的命令?
解决方案 1:
sleep infinity
如果实施,将永远休眠或休眠最长休眠长度,具体取决于实施情况。(请参阅该问题的其他答案和评论,其中提到了一些变化)
解决方案 2:
tail
不阻塞
一如既往:对于所有事情,都有一个简短、易懂、易于遵循且完全错误的答案。这里tail -f /dev/null
属于这一类 ;)
如果您使用 来查看它strace tail -f /dev/null
,您会发现这个解决方案远非阻塞!它可能比sleep
问题中的解决方案更糟糕,因为它使用(在 Linux 下)inotify
系统等宝贵资源。还有其他写入/dev/null
maketail
循环的进程。(在我的 Ubuntu64 16.10 上,这会在已经很繁忙的系统上每秒增加数十个系统调用。)
问题是关于阻止命令
不幸的是,没有这样的事情......
阅读:我不知道有什么方法可以直接用 shell 实现这一点。
一切(甚至sleep infinity
)都可能被某个信号中断。因此,如果您真的想确保它不会异常返回,它必须循环运行,就像您已经为 所做的那样sleep
。请注意,(在 Linux 上)/bin/sleep
显然上限为 24 天(请查看strace sleep infinity
),因此您能做的最好的事情可能是:
while :; do sleep 2073600; done
(请注意,我相信sleep
内部循环的值高于 24 天,但这意味着:它不是阻塞,而是循环非常缓慢。那么为什么不将这个循环移到外面呢?)
...但你可以用一个不知名的fifo
只要没有向进程发送信号,您就可以创建真正阻塞的东西。以下使用bash 4
、2 个 PID 和 1 fifo
:
bash -c 'coproc { exec >&-; read; }; eval exec "${COPROC[0]}<&-"; wait'
如果你愿意的话,你可以检查这是否真的阻塞strace
:
strace -ff bash -c '..see above..'
这是如何构建的
read
如果没有输入数据,则会阻塞(请参阅其他一些答案)。但是,tty
(又名stdin
)通常不是一个好的来源,因为它会在用户注销时关闭。此外,它还可能从窃取一些输入tty
。不太好。
要进行read
阻塞,我们需要等待诸如 之类的东西fifo
,它永远不会返回任何内容。在 中bash 4
有一个命令可以为我们提供这样的fifo
: coproc
。如果我们还等待阻塞read
(即我们的coproc
),我们就完成了。遗憾的是,这需要保持两个 PID 和一个 的打开状态fifo
。
具有命名的变体fifo
如果您不想使用命名fifo
,您可以按如下方式执行此操作:
mkfifo "$HOME/.pause.fifo" 2>/dev/null; read <"$HOME/.pause.fifo"
在读取时不使用循环有点草率,但是您可以fifo
根据需要重复使用它,并read
使用终止touch "$HOME/.pause.fifo"
(如果有多个读取等待,则所有读取都会立即终止)。
或者使用 Linuxpause()
系统调用
对于无限阻塞,有一个名为的 Linux 系统调用pause()
可以实现我们的要求:永远等待(直到信号到达)。但是,目前还没有针对此问题的用户空间程序。
碳
创建这样的程序很容易。下面是创建一个名为 的非常小的 Linux 程序的代码片段pause
,该程序会无限期暂停(需要 C 编译器,例如gcc
,并使用diet
等来生成小的二进制文件):
printf '#include <unistd.h>
int main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause
python
如果你不想自己编译一些东西,但是已经安装python
了,你可以在 Linux 下使用这个:
python -c 'while 1: import ctypes; ctypes.CDLL(None).pause()'
(注意:用来exec python -c ...
替换当前 shell,这会释放一个 PID。该解决方案还可以通过一些 IO 重定向进行改进,释放未使用的 FD。这取决于你。)
工作原理:ctypes.CDLL(None)
加载“主程序”(包括 C 库)并运行pause()
其中的函数,全部在循环内完成。效率低于 C 版本,但可以工作。
我的建议是:
保持循环睡眠。它很容易理解,非常便携,并且大多数时间都会阻塞。
解决方案 3:
这可能看起来很丑陋,但为什么不直接运行cat
并让它永远等待输入呢?
解决方案 4:
TL;DR:自 GNU coreutils 版本 9 以来,它sleep infinity
在 Linux 系统上做了正确的事情。以前(以及其他系统中)的实现实际上是休眠允许的最大时间,这是有限的。
我想知道为什么这没有在任何地方记录,我费心阅读了GNU coreutils 的源代码,我发现它大致执行如下操作:
使用
strtod
C stdlib 中的第一个参数将“无穷大”转换为双精度值。因此,假设 IEEE 754 双精度,64 位正无穷大值存储在seconds
变量中。调用
xnanosleep(seconds)
(在 gnulib 中找到),这反过来又调用dtotimespec(seconds)
(也在 gnulib 中)从 转换double
为struct timespec
。struct timespec
只是一对数字:整数部分(以秒为单位)和小数部分(以纳秒为单位)。将正无穷大简单转换为整数会导致未定义的行为(参见C 标准中的 §6.3.1.4),因此它会截断为TYPE_MAXIMUM(time_t)
。的实际值
TYPE_MAXIMUM(time_t)
未在标准中设置(甚至sizeof(time_t)
没有设置);因此,为了举例说明,我们从最近的 Linux 内核中选择 x86-64。
这是TIME_T_MAX
在 Linux 内核中,其定义time.h
为( ):
(time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
请注意,time_t
是__kernel_time_t
且time_t
是long
;使用 LP64 数据模型,因此sizeof(long)
是 8 (64 位)。
其结果是:TIME_T_MAX = 9223372036854775807
。
即:sleep infinite
实际睡眠时间为 9223372036854775807 秒(10^11 年)。对于 32 位 Linux 系统(sizeof(long)
为 4(32 位)):2147483647 秒(68 年;另请参阅2038 年问题)。
编辑:显然nanoseconds
调用的函数不是直接的系统调用,而是依赖于操作系统的包装器(也在gnulib 中定义)。
因此,需要额外的一步:在某些系统中,睡眠HAVE_BUG_BIG_NANOSLEEP
时间会被截断为 24 天,然后在循环中调用。某些(或所有?)Linux 发行版都是这种情况。请注意,如果配置true
时测试成功,则可能不会使用此包装器(来源)。
具体来说,应该是24 * 24 * 60 * 60 = 2073600 seconds
(加上 999999999 纳秒);但这是在循环中调用的,以遵守指定的总睡眠时间。因此,先前的结论仍然有效。
总之,产生的睡眠时间不是无限的,但对于所有实际用途来说已经足够高了,即使产生的实际时间流逝是不可移植的;这取决于操作系统和架构。
回答原始问题,这显然已经足够好了,但如果由于某种原因(资源非常受限的系统)你真的想避免无用的额外倒计时器,我想最正确的选择是使用cat
其他答案中描述的方法。
编辑:最近的 GNU coreutils 版本将尝试使用pause
系统调用(如果可用)而不是循环。当针对 Linux(可能还有 BSD)中的这些较新版本时,先前的参数不再有效。
可移植性
这是一个重要且合理的关注点:
sleep infinity
是POSIX未考虑的 GNU coreutils 扩展。GNU 的实现还支持时间持续时间的“花哨”语法,例如sleep 1h 5.2s
POSIX 仅允许正整数(例如不允许sleep 0.5
)。一些兼容的实现:GNU coreutils、FreeBSD(至少从8.2版本开始?)、Busybox(需要使用选项
FANCY_SLEEP
和进行编译FLOAT_DURATION
)。该
strtod
行为与 C 和 POSIX 兼容(即,strtod("infinity", 0)
在符合 C99 的实现中始终有效,参见 §7.20.1.3)。
解决方案 5:
sleep infinity
看起来非常优雅,但有时由于某种原因它不起作用。在这种情况下,您可以尝试其他阻止命令,例如cat
,,,等。read
`tail -f /dev/null`grep a
解决方案 6:
让我解释一下为什么它sleep infinity
有效,尽管它没有记录。jp48的答案也很有用。
最重要的是:通过指定inf
或infinity
(均不区分大小写),您可以休眠您的实现允许的最长时间(即HUGE_VAL
和的较小值TYPE_MAXIMUM(time_t)
)。
现在让我们深入了解细节。可以从coreutils/src/sleep.csleep
中读取命令的源代码。本质上,该函数执行以下操作:
double s; //seconds
xstrtod (argv[i], &p, &s, cl_strtod); //`p` is not essential (just used for error check).
xnanosleep (s);
理解xstrtod (argv[i], &p, &s, cl_strtod)
xstrtod()
根据gnulib/lib/xstrtod.c,调用xstrtod()
将字符串转换argv[i]
为浮点值并将其存储到*s
,使用转换函数cl_strtod()
。
cl_strtod()
从coreutils/lib/cl-strtod.c可以看出,cl_strtod()
使用 将字符串转换为浮点值strtod()
。
strtod()
根据man 3 strtod
,strtod()
将字符串转换为类型的值double
。手册页说
字符串(的初始部分)的预期形式是......或(iii)无穷大,或......
无穷大定义为
无穷大要么是“INF”,要么是“INFINITY”,不考虑大小写。
尽管文件说
如果正确值会导致溢出,则返回正数或负数
HUGE_VAL
(HUGE_VALF
, )HUGE_VALL
,不清楚无穷大是如何处理的。所以让我们看看源代码gnulib/lib/strtod.c。我们想要阅读的是
else if (c_tolower (*s) == 'i'
&& c_tolower (s[1]) == 'n'
&& c_tolower (s[2]) == 'f')
{
s += 3;
if (c_tolower (*s) == 'i'
&& c_tolower (s[1]) == 'n'
&& c_tolower (s[2]) == 'i'
&& c_tolower (s[3]) == 't'
&& c_tolower (s[4]) == 'y')
s += 5;
num = HUGE_VAL;
errno = saved_errno;
}
因此,INF
和INFINITY
(不区分大小写)被视为HUGE_VAL
。
HUGE_VAL
家庭
让我们使用N1570作为 C 标准。HUGE_VAL
,HUGE_VALF
并且HUGE_VALL
宏在§7.12-3 中定义
该宏
HUGE_VAL
扩展为正双精度常量表达式,不一定能表示为浮点数。这两个宏
HUGE_VALF
HUGE_VALL
分别是 的浮点数和长双精度模拟
HUGE_VAL
。
HUGE_VAL
、HUGE_VALF
和HUGE_VALL
在支持无穷大的实现中可以是正无穷大。
以及 §7.12.1-5
如果浮点结果溢出且默认舍入生效,则函数根据返回类型返回宏的值
HUGE_VAL
、HUGE_VALF
或HUGE_VALL
理解xnanosleep (s)
现在我们了解了 的所有本质xstrtod()
。从上面的解释中,我们清楚地知道xnanosleep(s)
我们首先看到的实际上意味着xnanosleep(HUGE_VALL)
。
xnanosleep()
根据源代码gnulib/lib/xnanosleep.c,xnanosleep(s)
本质上执行以下操作:
struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);
dtotimespec()
此函数将类型的参数转换double
为类型的对象struct timespec
。由于它非常简单,我引用源代码gnulib/lib/dtotimespec.c。所有注释都是我添加的。
struct timespec
dtotimespec (double sec)
{
if (! (TYPE_MINIMUM (time_t) < sec)) //underflow case
return make_timespec (TYPE_MINIMUM (time_t), 0);
else if (! (sec < 1.0 + TYPE_MAXIMUM (time_t))) //overflow case
return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
else //normal case (looks complex but does nothing technical)
{
time_t s = sec;
double frac = TIMESPEC_HZ * (sec - s);
long ns = frac;
ns += ns < frac;
s += ns / TIMESPEC_HZ;
ns %= TIMESPEC_HZ;
if (ns < 0)
{
s--;
ns += TIMESPEC_HZ;
}
return make_timespec (s, ns);
}
}
由于time_t
被定义为整数类型(参见 §7.27.1-3),我们自然会假设类型的最大值time_t
小于HUGE_VAL
(类型double
),这意味着我们进入了溢出情况。(实际上这个假设是不必要的,因为在所有情况下,过程本质上都是相同的。)
make_timespec()
我们要翻越的最后一堵墙是make_timespec()
。非常幸运的是,它非常简单,引用源代码gnulib/lib/timespec.h就足够了。
_GL_TIMESPEC_INLINE struct timespec
make_timespec (time_t s, long int ns)
{
struct timespec r;
r.tv_sec = s;
r.tv_nsec = ns;
return r;
}
解决方案 7:
那么向自己发送SIGSTOP怎么样?
这应该会暂停进程,直到收到 SIGCONT。对于你的情况,永远不会。
kill -STOP "$$";
# grace time for signal delivery
sleep 60;
解决方案 8:
我最近有这个需求。我想出了以下函数,它可以让 bash 永远处于休眠状态,而无需调用任何外部程序:
snore()
{
local IFS
[[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
{
# workaround for MacOS and similar systems
local fifo
fifo=$(mktemp -u)
mkfifo -m 700 "$fifo"
exec {_snore_fd}<>"$fifo"
rm "$fifo"
}
read ${1:+-t "$1"} -u $_snore_fd || :
}
注意:我之前发布过一个版本,每次都会打开和关闭文件描述符,但我发现在某些系统上,每秒执行数百次此操作最终会锁定。因此,新的解决方案在调用函数之间保留文件描述符。无论如何,Bash 都会在退出时清理它。
可以像 /bin/sleep 一样调用它,它会休眠请求的时间。如果不带参数调用,它会永远挂起。
snore 0.1 # sleeps for 0.1 seconds
snore 10 # sleeps for 10 seconds
snore # sleeps forever
我的博客上有一篇非常详细的文章
解决方案 9:
这种方法不会消耗任何资源来维持进程活动。
while :; do :; done & kill -STOP $! && wait
分解
while :; do :; done &
在后台创建一个虚拟进程kill -STOP $!
停止后台进程wait
等待后台进程,这将永远阻塞,因为后台进程之前已经停止了
笔记
仅在脚本文件内有效。
解决方案 10:
jp48 1 和ynn 2的精彩答案深入研究了sleep infinity
的源代码来解释其以前的行为,同时仅仅注意到它最近发生了变化“做了正确的事情”。
以下是我对当前行为改变的深入分析:
该变化并未发生在实用程序所在的coreutils上,
sleep
而是发生在其使用的GNULib函数上xnanosleep()
。它始于2020-02-10 在 GNULib 错误列表上的一场讨论,引用了 StackOverflow 上的这个问题(这太酷了!)关于尾部方法的缺点和同事@Vladimir Panteleev提出的补丁,提出了一种更好的方法。
2020 年 2 月 16 日,该补丁作为提交提交到存储库,并在变更日志中提及
coreutils
它于 2020-02-25被添加到源代码树中,当时他们将子模块引用更新gnulib
为前一天所做的提交。因此,它于 2020-03-05 在Coreutilsv8.32中发布,这是 8.x 系列的最后一个版本。
变化本身非常简单:
/* Sleep until the time (call it WAKE_UP_TIME) specified as
SECONDS seconds after the time this function is called.
*/
int
xnanosleep (double seconds)
{
#if HAVE_PAUSE
if (1.0 + TYPE_MAXIMUM (time_t) <= seconds)
{
do
pause ();
while (errno == EINTR);
/* pause failed (!); fall back on repeated nanosleep calls. */
}
#endif
通过使用pause()
系统调用,现在sleep infinity
实际上会永远休眠直到收到信号,这可以说是绝对最小的开销。
解决方案 11:
不要终止窗口管理器,而是尝试使用--replace
或(-replace
如果可用)运行新的窗口管理器。
解决方案 12:
sleep inf
可以节省一些输入,它与sleep infinity
解决方案 13:
我们可以使用flock来永远休眠。这就是死锁机制。
flock -sF /tmp flock -eF /tmp true
并且它还可以在指定的时间休眠。
flock -sF /tmp flock -w seconds -eF /tmp true
解决方案 14:
while :; do read; done
无需等待孩子睡觉过程。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件