重击:无限睡眠(无限阻塞)


158

startx通常用X来开始评估我的.xinitrc。在我中,.xinitrc我使用启动我的窗口管理器/usr/bin/mywm。现在,如果我杀死了我的WM(为了测试其他WM),由于.xinitrc脚本到达EOF ,X也将终止。所以我在我的末尾添加了这个.xinitrc

while true; do sleep 10000; done

这样,如果我杀死WM,X不会终止。现在我的问题是:如何进行无限睡眠而不是循环睡眠?是否有一个类似于冻结脚本的命令?

最好的祝福

Answers:


330

sleep infinity 完全按照建议进行操作,并且不会虐待猫。


16
凉。不幸的是,我的busybox无法理解。
非用户

12
sleep infinity尽管对于Linux而言,BSD 是一件很酷的事情,但BSD(或至少是OS X)也不了解。但是,while true; do sleep 86400; done应该是一个适当的替代品。
伊万X

16
关于这一点,我做了一些单独记录在案的研究。总结一下:infinity在C中从“字符串”转换为double。然后将double其截断为允许的最大值timespec,这意味着非常多的秒数(取决于体系结构),但从理论上讲是有限的。
jp48-11

72

tail 不阻塞

一如既往:对于所有问题,答案都是简短,易于理解,易于遵循且完全错误。这里tail -f /dev/null属于这一类;)

如果您一起看它,strace tail -f /dev/null您会发现,该解决方案远非阻塞!它可能比问题中的sleep解决方案还要糟糕,因为它使用(在Linux下)inotify系统之类的宝贵资源。还有其他写入/dev/nullmake tail循环的进程。(在我的Ubuntu64 16.10上,这在已经很忙的系统上每秒增加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(aka。stdin)通常不是一个很好的来源,因为当用户注销时它是关闭的。也可能会窃取tty。不太好。

要进行read阻止,我们需要等待诸如a之类的东西fifo,它将永远不会返回任何东西。其中bash 4有一个命令可以准确地为我们提供这样的命令fifocoproc。如果我们也等待阻塞read(这是我们的coproc),那么我们就完成了。可悲的是,这需要保持打开两个PID和一个fifo

带有命名的变体 fifo

如果您不打扰使用named fifo,则可以执行以下操作:

mkfifo "$HOME/.pause.fifo" 2>/dev/null; read <"$HOME/.pause.fifo"

读取时不使用循环会有点草率,但是您可以根据需要多次重用它fifo,并使read使用来 s终端touch "$HOME/.pause.fifo"(如果有多个读取等待,则立即终止所有连接)。

或使用Linux pause() syscall

对于无限阻塞,有一个Linux内核调用,称为 pause()它可以满足我们的要求:永远等待(直到信号到达)。但是,目前尚无用户空间程序。

C

创建这样的程序很容易。下面是一个片段,以创建一个名为一个非常小的Linux程序pause,其无限期暂停(需求dietgcc等):

printf '#include <unistd.h>\nint 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 ...用于替换当前外壳,这将释放一个PID。还可以通过一些IO重​​定向来改进该解决方案,从而释放未使用的FD。这取决于您。)

这是如何工作的(我认为):ctypes.CDLL(None)加载标准C库并pause()在其他一些循环中运行该函数。比C版本效率低,但可以。

我对您的建议:

保持循环睡眠。这很容易理解,非常便携,并且在大多数情况下会阻塞。


1
@Andrew通常,您不需要trap(将shell修改为信号的行为)或背景(允许shell截获来自终端的信号,例如Strg + C)。这样sleep infinity就足够了(就像exec sleep infinity是最后一条语句一样。请看一下区别用法strace -ffDI4 bash -c 'YOURCODEHERE')。循环睡眠会更好,因为sleep在某些情况下可能会返回。例如,您不希望X11在上突然关闭killall sleep,仅因为.xstartup结束sleep infinity而不是睡眠循环。
Tino

可能有点晦涩,但它s6-pause是一个userland命令,可以运行pause(),可以忽略各种信号。
Patrick

/bin/sleep正如您所说,@ Tino 的上限为24天。如果您可以进行更新,那就太好了。目前在Linux上,此代码处于活动状态。它将单个nanosleep()系统调用的上限设置为24天,但会循环调用它们。因此sleep infinity24天后不应该退出。在double正无穷大被转变成struct timespecrpl_nanosleep在GDB中查看,infinity将其转换为{ tv_sec = 9223372036854775807, tv_nsec = 999999999 }在Ubuntu 16.04上。
nh2

@ nh2在文本中已经提到过,sleep 可能会循环而不是被完全阻塞。我现在对其进行了稍微的编辑,希望可以使这一事实更加清楚。请注意此“ 可能 ”,因为strace单凭我无法证明在中确实编译了一些循环代码这一事实sleep,并且我不想等待24天就进行测试(或反编译/bin/sleep)。如果没有确凿的数学证明,那么似乎最好是防御性编程。也不要信任任何东西:killall -9 sleep
Tino

使用perl可以很容易地完成pause()选项:perl -MPOSIX -e'pause()'
tgoodhart


32

TL; DR: sleep infinity实际上休眠了允许的最大时间,这是有限的。

想知道为什么在任何地方都没有记录,我费心地阅读了GNU coreutils资源。发现它的执行大致如下:

  1. strtod在第一个参数上使用C stdlib中的命令将'infinity'转换为double precision。因此,假设IEEE 754双精度,则将64位正无穷大值存储在seconds变量中。
  2. 调用xnanosleep(seconds)在gnulib中找到),这又调用dtotimespec(seconds)也在gnulib中)将转换doublestruct timespec
  3. struct timespec只是一对数字:整数部分(以秒为单位)和小数部分(以纳秒为单位)。幼稚地转换正无穷大为整数会导致不确定的行为(请参见C标准的6.3.1.4节),因此它将截断为TYPE_MAXIMUM (time_t)
  4. 的实际值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_ttime_tlong ; 使用LP64数据模型,因此使用sizeof(long)8(64位)。

结果是: TIME_T_MAX = 9223372036854775807

也就是说:sleep infinite导致实际睡眠时间为9223372036836854775807秒(10 ^ 11年)。对于32位linux系统(sizeof(long)为4(32位)):2147483647秒(68年;另请参见2038年问题)。


编辑:显然,nanoseconds调用的函数不是直接进行syscall,而是与OS相关的包装器(也在gnulib中定义)。

这里有一个额外的步骤,其结果是:对于一些系统中HAVE_BUG_BIG_NANOSLEEPtrue睡眠被截断至24天,然后在循环中调用。一些(或全部?)Linux发行版就是这种情况。请注意,如果configure -time测试成功(),。

尤其是24 * 24 * 60 * 60 = 2073600 seconds(加上999999999纳秒);但这是为了遵守指定的总睡眠时间而循环调用的。因此,先前的结论仍然有效。


总之,最终的睡眠时间 即使产生的实际经过时间不是便携式的也不是无限的,而是足以满足所有实际目的。这取决于操作系统和体系结构。

要回答原始问题,这显然已经足够好了,但是如果由于某种原因(一个资源非常有限的系统)您确实想要避免一个无用的额外倒数计时器,我想最正确的选择是使用cat其他答案中描述的方法。


1
在下一个coreutils中,sleep infinity现在将永久地永久休眠而不会循环:lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
弗拉基米尔·潘捷列夫

8

sleep infinity看起来最优雅,但有时​​由于某些原因无法正常工作。在这种情况下,你可以尝试其他的拦截命令,如catreadtail -f /dev/nullgrep a等。


1
tail -f /dev/null也是在SaaS平台上对我来说
可行的

2
tail -f /dev/null还具有不消耗标准输入的优点。因此,我使用了它。
Sudo Bash

那些考虑此选项的人应该阅读此答案以了解此选项的后果。
Shadow

6

如何发送SIGSTOP向自己怎么办?

这应该暂停该过程,直到收到SIGCONT。在您的情况下:永远不会。

kill -STOP "$$";
# grace time for signal delivery
sleep 60;

6
信号是异步的。因此可能发生以下情况:a)shell调用kill b)kill告诉内核shell将接收到STOP信号c)kill终止并返回shell d)shell继续(可能因为脚本结束而终止)e)内核终于找到了交付时间停止信号到外壳
非用户

1
@temple很有见识,没有考虑信号的异步性质。谢谢!
michuelnik

4

让我解释一下为什么sleep infinity有效,尽管没有记录。jp48的答案也很有用。

最重要的事情是:通过指定infinfinity(都不区分大小写),您可以在实现允许的最长时间内睡眠(即HUGE_VALand 的较小值TYPE_MAXIMUM(time_t))。

现在让我们深入研究细节。sleep可以从coreutils / src / sleep.c中读取命令的源代码。本质上,该函数执行以下操作:

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]为浮点值并将其存储到。*scl_strtod()

cl_strtod()

coreutils / lib / cl-strtod.c中可以看出,使用cl_strtod()将字符串转换为浮点值strtod()

strtod()

根据man 3 strtodstrtod()将字符串转换为type的值double。手册页说

(字符串的初始部分)的预期形式为...或(iii)无穷大,或...

无限定义为

忽略大小写,无穷大是“ INF”或“ INFINITY”。

虽然文件告诉

如果正确的值将导致溢出,则返回加号或减号HUGE_VALHUGE_VALFHUGE_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;
  }

因此,INFINFINITY(都不区分大小写)被视为HUGE_VAL

HUGE_VAL 家庭

让我们使用N1570作为C标准。HUGE_VALHUGE_VALF并且HUGE_VALL宏在第7.12-3节中定义


    HUGE_VAL
扩展为正的双常量表达式,不一定表示为浮点数。宏
    HUGE_VALF
    HUGE_VALL
分别是的float和long double类似物HUGE_VAL

HUGE_VALHUGE_VALFHUGE_VALL可以是支持无限性的实现中的肯定无限性。

以及第7.12.1-5节

如果浮动结果溢出和默认的舍入有效时,则该函数返回的宏的值HUGE_VALHUGE_VALFHUGE_VALL根据返回类型

理解 xnanosleep (s)

现在我们了解了的所有本质xstrtod()。从上面的解释中可以很清楚地看出,xnanosleep(s)我们首先看到的实际上是xnanosleep(HUGE_VALL)

xnanosleep()

根据源代码gnulib / lib / xnanosleep.cxnanosleep(s)基本上是这样做的:

struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);

dtotimespec()

此函数将type的参数转换为type 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节),因此我们自然假设type的最大值time_t小于HUGE_VAL(of type 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;
}

2

我最近需要这样做。我想出了以下函数,它将使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

我的博客上有一篇写着过多细节的文章


1

这种方法不会消耗任何资源来使进程保持活动状态。

while :; do sleep 1; done & kill -STOP $! && wait $!

分解

  • while :; do sleep 1; done & 在后台创建虚拟进程
  • kill -STOP $! 停止后台进程
  • wait $! 等待后台进程,这将永远阻塞,导致后台进程在停止之前

0

而不是终止窗口管理器,而尝试使用--replace或运行新的窗口管理器-replace


1
如果我使用,--replace我总是会收到类似的警告another window manager is already running。对我来说,这没有多大意义。
10年

-2
while :; do read; done

没有等待孩子睡觉的过程。


1
stdin如果仍然碰巧连接到,则会吃掉它tty。如果您将其与< /dev/null忙循环一起运行。在某些情况下可能有一定用处,所以我不反对。
Tino

1
这是一个非常糟糕的主意,它只会消耗所有CPU。
Mohammed Noureldin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.