【问题标题】:Bash: infinite sleep (infinite blocking)Bash:无限睡眠(无限阻塞)
【发布时间】:2011-02-25 11:37:21
【问题描述】:

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

while true; do sleep 10000; done

这样,如果我杀死我的 WM,X 就不会终止。现在我的问题是:我怎样才能做到无限睡眠而不是循环睡眠?有没有类似冻结脚本的命令?

【问题讨论】:

    标签: linux bash sleep infinite


    【解决方案1】:

    也许这看起来很难看,但为什么不直接运行 cat 并让它永远等待输入?

    【讨论】:

    • 如果您没有可供阅读的悬挂管道,这将不起作用。请指教。
    • @Matt,也许做个烟斗然后cat呢? mkfifo pipe && cat pipe
    • @twalberg 所说的,但另外您可以立即重新分配给 3 并取消链接,如下所示:superuser.com/a/633185/762481
    【解决方案2】:

    不要关闭窗口管理器,而是尝试使用--replace-replace(如果可用)运行新的。

    【讨论】:

    • 如果我使用--replace,我总是会收到类似another window manager is already running 的警告。这对我来说没有多大意义。
    【解决方案3】:

    向自己发送SIGSTOP 怎么样?

    这应该暂停进程,直到收到 SIGCONT。你的情况是:从不。

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

    【讨论】:

    • 信号是异步的。因此可能会发生以下情况: a) shell 调用 kill b) kill 告诉内核 shell 将收到信号 STOP c) kill 终止并返回到 shell d) shell 继续(可能因为脚本结束而终止) e) 内核终于找到了交付的时间向 shell 发出 STOP 信号
    • @temple 很有见地,没有考虑信号的异步性质。谢谢!
    【解决方案4】:

    sleep infinity 完全按照它的建议行事,并且不会虐待猫。

    【讨论】:

    • 酷。不幸的是我的busybox听不懂。
    • BSD(或至少是 OS X)也不理解 sleep infinity,尽管对于 Linux 来说这是一件很酷的事情。但是,while true; do sleep 86400; done 应该是一个合适的替代品。
    • 对此,我进行了一些研究,并在单独的答案中进行了记录。总结一下:infinity 在 C 中从“字符串”转换为double。然后double 被截断为timespec 允许的最大值,这意味着非常多的秒数(取决于架构),但理论上是有限的。
    • 我在谷歌上搜索了“虐待猫”。不知道我期待找到什么。怎么样:“在执行此脚本时没有猫受到伤害”
    • FWIW,macOS Big Sur 现在可以理解“无限睡眠”,尽管 Mojave 没有。 (我跳过了卡特琳娜。)
    【解决方案5】:

    sleep infinity 看起来最优雅,但有时​​由于某种原因它不起作用。在这种情况下,您可以尝试其他阻塞命令,例如catreadtail -f /dev/nullgrep a 等。

    【讨论】:

    • tail -f /dev/null 也是我在 SaaS 平台上的可行解决方案
    • tail -f /dev/null 也有不消耗标准输入的优点。出于这个原因,我使用了它。
    • 那些考虑这个选项的人应该阅读this answer以了解这个选项的后果。
    【解决方案6】:
    while :; do read; done
    

    不等待子睡眠过程。

    【讨论】:

    • 如果它仍然碰巧连接到tty,这会吃掉stdin。如果你用< /dev/null 运行它,它会忙循环。在某些情况下它可能会有一些用处,所以我不反对。
    • 这是一个非常糟糕的主意,它只会消耗大量的 cpu。
    【解决方案7】:

    tail 不阻塞

    一如既往:对于任何事情都有一个简短、易于理解、易于理解且完全错误的答案。这里tail -f /dev/null 属于这一类;)

    如果您使用strace tail -f /dev/null 查看它,您会注意到,此解决方案远非阻塞!它可能比问题中的sleep 解决方案更糟糕,因为它使用(在Linux 下)像inotify 系统这样的宝贵资源。写入/dev/null 的其他进程也会使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(又名stdin)通常不是一个好的来源,因为它在用户注销时关闭。它也可能从tty 窃取一些输入。不好看。

    要使read 阻塞,我们需要等待类似fifo 的东西,它永远不会返回任何东西。在bash 4 中有一个命令可以准确地为我们提供这样的fifocoproc。如果我们还等待阻塞read(这是我们的coproc),我们就完成了。遗憾的是,这需要保持打开两个 PID 和一个 fifo

    具有命名fifo 的变体

    如果您不想使用已命名的fifo,您可以按以下方式进行:

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

    在读取时不使用循环有点草率,但您可以随意重复使用此fifo,并使用touch "$HOME/.pause.fifo" 使reads 终止(如果有多个读取等待, 全部立即终止)。

    或者使用 Linux pause() 系统调用

    对于无限阻塞,有一个名为 pause() 的 Linux 内核调用,它执行我们想要的操作:永远等待(直到信号到达)。但是(目前)还没有用户空间程序。

    C

    创建这样的程序很容易。这是一个 sn-p 来创建一个名为 pause 的非常小的 Linux 程序,它无限期地暂停(需要 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 ...替换当前的shell,这会释放一个PID。解决方案也可以通过一些IO重​​定向来改进,释放未使用的FD。这取决于你。)

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

    我给你的建议:

    保持循环睡眠。它易于理解,非常便携,并且大部分时间都会阻塞。

    【讨论】:

    • @Andrew 通常你不需要trap(它将shell的行为修改为信号)也不需要背景(它允许shell拦截来自终端的信号,如Strg+C) .所以sleep infinity 就足够了(如果它是最后一个语句,则行为类似于exec sleep infinity。要查看差异,请使用strace -ffDI4 bash -c 'YOURCODEHERE')。循环睡眠更好,因为sleep 在某些情况下可以返回。例如,您不希望 X11 在 killall sleep 上突然关闭,因为 .xstartupsleep infinity 结束而不是睡眠循环。
    • 可能有点晦涩,但s6-pause 是运行pause() 的用户级命令,可以选择忽略各种信号。
    • @Tino /bin/sleep 如您所说,没有 24 天的上限。如果你能更新它会很好。现在在 Linux 上,this code 处于活动状态。它将单个 nanosleep() 系统调用限制为 24 天,但在循环中调用它们。所以sleep infinity 不应该在 24 天后退出。 double 正无穷被转换为struct timespec。查看 GDB 中的 rpl_nanosleepinfinity 在 Ubuntu 16.04 上转换为 { tv_sec = 9223372036854775807, tv_nsec = 999999999 }
    • @nh2 文中已经提到 sleep 可能 循环而不是完全阻塞。我现在稍微编辑了一下,希望能让这个事实更清楚一点。请注意这个“可能”,因为仅从strace 我无法证明sleep 中确实有一些循环代码编译的事实,我不想等待24天只是为了测试这个(或反编译/bin/sleep)。如果没有硬性的数学证明,那么防御性地编程总是更好,因为它看起来确实如此。也永远不要相信任何东西:killall -9 sleep
    • 在下一个 coreutils 中,sleep infinity 现在实际上将永远休眠而不循环:lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
    【解决方案8】:

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

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

    1. 在第一个参数上使用 C 标准库中的 strtod 将“无穷大”转换为双精度值。因此,假设 IEEE 754 双精度,64 位 正无穷 值存储在 seconds 变量中。
    2. 调用xnanosleep(seconds) (found in gnulib),这反过来又调用dtotimespec(seconds) (also in gnulib) 将double 转换为struct timespec
    3. struct timespec 只是一对数字:整数部分(以秒为单位)和小数部分(以纳秒为单位)。 将 正无穷 天真地转换为整数会导致未定义的行为(请参阅 §6.3.1.4 from C standard),因此它会截断为 TYPE_MAXIMUM(time_t)
    4. 标准中没有设置TYPE_MAXIMUM(time_t)的实际值(甚至sizeof(time_t)也没有);因此,作为示例,让我们从最近的 Linux 内核中选择 x86-64。

    这是Linux内核中的TIME_T_MAX,定义为(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 导致实际睡眠时间为 9223372036854775807 秒(10^11 年)。而对于 32 位 linux 系统(sizeof(long) 是 4(32 位)):2147483647 秒(68 年;另见 year 2038 problem)。


    编辑:显然调用的nanoseconds 函数不是直接系统调用,而是依赖于操作系统的包装器(也是defined in gnulib)。

    因此有一个额外的步骤:对于某些HAVE_BUG_BIG_NANOSLEEPtrue 的系统,睡眠被截断为 24 天,然后在循环中调用。某些(或全部?)Linux 发行版就是这种情况。请注意,如果 configure-time 测试成功 (source),则可能不会使用此包装器。

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


    总之,产生的睡眠时间不是无限的,但对于所有实际用途来说足够高,即使产生的实际时间流逝是不可移植的;这取决于操作系统和架构。

    要回答最初的问题,这显然已经足够了,但是如果出于某种原因(非常资源受限的系统)您真的想避免无用的额外倒数计时器,我猜是最正确的替代方法是使用其他答案中描述的cat 方法。

    编辑:最近的 GNU coreutils 版本将尝试使用 pause 系统调用(如果可用)而不是循环。当针对 Linux(可能是 BSD)中的这些较新版本时,前面的论点不再有效。


    便携性

    这是一个重要的有效问题:

    • sleep infinity 是未考虑的 GNU coreutils 扩展 in POSIX。 GNU 的实现还支持持续时间的“花哨”语法,例如 sleep 1h 5.2s,而 POSIX 只允许正整数(例如,sleep 0.5不允许允许)。
    • 一些兼容的实现:GNU coreutils、FreeBSD(至少从版本8.2?)、Busybox(需要使用选项FANCY_SLEEPFLOAT_DURATION 编译)。
    • strtod 行为与 C 和 POSIX 兼容(即strtod("infinity", 0) 在符合 C99 的实现中始终有效,see §7.20.1.3)。

    【讨论】:

    【解决方案9】:

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

    There's a writeup with excessive details on my blog here

    【讨论】:

      【解决方案10】:

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

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

      故障

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

      注意事项

      • 只能在脚本文件中工作。

      【讨论】:

      • 在 MacOS 上会导致忙等待(100% CPU)。
      • @Miao1007 它只发生在 zsh 和 sh 这不会发生。我不知道为什么会在 zsh 中发生这种情况。
      • @Miao1007 我知道了,因为zsh中的wait会恢复给定的进程。
      • @Miao1007 我通过从wait 命令中删除$! 来修复我的命令。现在它也在zsh 中工作。
      【解决方案11】:

      让我解释一下为什么sleep infinity 有效,尽管它没有记录。 jp48's answer 也很有用。

      最重要的是:通过指定infinfinity(均不区分大小写),您可以在实现允许的最长时间内休眠(即HUGE_VALTYPE_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], &amp;p, &amp;s, cl_strtod)

      xstrtod()

      根据gnulib/lib/xstrtod.cxstrtod()的调用将字符串argv[i]转换为浮点值并存储到*s,使用转换函数cl_strtod()

      cl_strtod()

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

      strtod()

      根据man 3 strtodstrtod() 将字符串转换为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_VALFHUGE_VALL 宏在 §7.12-3

      中定义


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

      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()

      此函数将double 类型的参数转换为struct timespec 类型的对象。既然很简单,我就引用源代码gnulib/lib/dtotimespec.c。所有的cmets都是我加的。

      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_VALdouble 类型),这意味着我们进入溢出案例。 (实际上不需要这个假设,因为在所有情况下,过程基本相同。)

      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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-19
        • 2018-03-13
        • 1970-01-01
        • 2018-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多