bash - `ulimit -t` 在 shell 和操作系统之间完全不可移植?

标签 bash shell signals zsh ulimit

更新:这不再是一个问题,而更像是一个总结。哦,好吧...

bash、dash 和 zsh 都带有内置命令 ulimit。每个都有一个选项-t,它接受一个数字作为参数,可以理解为进程可能消耗的CPU时间(以秒为单位)。此后,他们将收到一个信号。事情已经很清楚了。

不过,还有很多不清楚的地方。我发现其中一些相当出乎意料。特别是,您获得的行为取决于 shell 和底层操作系统。我创建了一个表格来总结变异性的程度。我还包含了用于自动获取这些结果的脚本的代码。最后一个测试需要 root 权限,如果您注释掉 test_shell_sudo $shell,则可以阻止其运行。

|                                              | Darwin/zsh | Darwin/bash | FreeBSD/zsh | FreeBSD/bash | FreeBSD/dash | Linux/zsh  | Linux/bash  | Linux/dash  |
| ulimit -t sets                               | soft limit | both limits | soft limit  | both limits  | both limits  | soft limit | both limits | both limits |
| ulimit -t gets                               | soft limit | soft limit  | soft limit  | soft limit   | soft limit   | soft limit | soft limit  | soft limit  |
| Hard limits can be set below the soft limit  | yes        | no          | yes         | yes          | yes          | yes        | no          | no          |
| Soft limits can be set above the hard limit  | yes        | no          | yes         | no           | no           | yes        | no          | no          |
| Hard limits can be raised without privileges | yes        | no          | yes         | no           | no           | yes        | no          | no          |
| soft signal                                  | SIGXCPU    | SIGXCPU     | SIGXCPU     | SIGXCPU      | SIGXCPU      | SIGXCPU    | SIGXCPU     | SIGXCPU     |
| hard signal                                  | SIGXCPU    | SIGXCPU     | SIGKILL     | SIGKILL      | SIGKILL      | SIGKILL    | SIGKILL     | SIGKILL     |
| Number of SIGXCPUs sent                      | one        | one         | one         | one          | one          | multiple   | multiple    | multiple    |
| Raising soft beyond hard limit raises it     | yes        | impossible* | yes         | no           | no           | yes        | impossible* | impossible* |

* even as root
#!/usr/bin/env bash

get_sigcode() {
    /bin/kill -l |
        tr '\n[a-z]' ' [A-Z]' |
        awk -v name=$1 '
            { for (i=1; i<=NF; ++i) if ($i == name) print i }'
}

create_runner() {
    cat > sig.c <<'EOF'
#include <stdlib.h>
#include <stdio.h>

int
main()
{
  int runs = 0;
  double x = 0.0;
  for (;;runs++) {
    x += (double)rand() / RAND_MAX;
    if (x >= 1e7) {
      printf("Took %d iterations to reach 1000.\n", runs);
      x = 0.0;
      runs = 0;
    }
  }
  return 0;
}
EOF
    cc sig.c -o sig
    rm -f sig.c
    echo Successfully compiled sig.c
}

create_counter() {
    cat > sigcnt.c <<'EOF'
#include <stdatomic.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

sig_atomic_t sig_received;
void handle_signal(int signum) {
  sig_received = signum;
}

int
main()
{
  signal(SIGXCPU, handle_signal);

  int sigxcpu_cnt = 0;
  time_t start, now;
  time(&start);

  int runs = 0;
  double x = 1;
  for (;;) {
    if (sig_received == SIGXCPU) {
      sigxcpu_cnt++;
      sig_received = 0;
    }
    time(&now);
    if (now - start > 5) {
      switch (sigxcpu_cnt) {
      case 0:
        fprintf(stderr, "none\n");
        exit(0);
      case 1:
        fprintf(stderr, "one\n");
        exit(0);
      default:
        fprintf(stderr, "multiple\n");
        exit(0);
      }
    }

    // Do something random that eats CPU (sleeping is not an option)
    x += (double)rand() / RAND_MAX;
    if (x >= 1e7) {
      printf("Took %d iterations to reach 1000.\n", runs);
      x = 0.0;
      runs = 0;
    }
  }
}
EOF
    cc sigcnt.c -o sigcnt
    rm -f sigcnt.c
    echo Successfully compiled sigcnt.c
}

echo_underscored() {
    out1=$1
    out2=''
    for ((i=0; i < ${#out1}; ++i)); do
        out2+='='
    done
    echo $out1
    echo $out2
}


test_shell() {
    shell=$1
    echo_underscored "Testing shell: $shell"

    f() {
        $shell -c 'ulimit -St 3; ulimit -t 2; ulimit -Ht; ulimit -St' | tr -d '\n'
    }
    case `f` in
        22)
            t_sets='both limits';;
        unlimited2)
            t_sets='soft limit';;
        *)
            echo UNEXPECTED;;
    esac
    echo "ulimit -t sets: ${t_sets}"

    f() {
        $shell -c 'ulimit -St 3; ulimit -Ht 4; ulimit -St 3; ulimit -t'
    }
    case `f` in
        3)
            t_gets='soft limit';;
        *)
            echo UNEXPECTED;;
    esac
    echo "ulimit -t gets: ${t_gets}"

    f() {
        $shell -c 'ulimit -St 2; ulimit -Ht 1' >/dev/null 2>&1 &&
            echo yes || echo no
    }
    ht_can_set_below_soft=`f`
    echo "Hard limits can be set below the soft limit: ${ht_can_set_below_soft}"

    f() {
        $shell -c 'ulimit -St 1; ulimit -Ht 2; ulimit -St 3' >/dev/null 2>&1 &&
            echo yes || echo no
    }
    st_can_set_above_hard=`f`
    echo "Soft limits can be set above the hard limit: ${st_can_set_above_hard}"

    f() {
        $shell -c 'ulimit -St 1; ulimit -Ht 2; ulimit -Ht 3' >/dev/null 2>&1 &&
            echo yes || echo no
    }
    hard_can_be_raised=`f`
    echo "Hard limits can be raised without privileges: ${hard_can_be_raised}"

    f() {
        $shell -c 'ulimit -St 1; ./sig' >/dev/null 2>&1
        echo $?
    }
    case $((`f` - 128)) in
        ${sigxcpu})
            soft_signal=SIGXCPU;;
        ${sigkill})
            soft_signal=SIGKILL;;
        *)
            echo UNEXPECTED;
    esac
    echo "soft signal: ${soft_signal}"

    f() {
        $shell -c 'ulimit -St 1; ulimit -Ht 1; ./sig' >/dev/null 2>&1
        echo $?
    }
    case $((`f` - 128)) in
        ${sigxcpu})
            hard_signal=SIGXCPU;;
        ${sigkill})
            hard_signal=SIGKILL;;
        *)
            echo UNEXPECTED;;
    esac
    echo "hard signal: ${hard_signal}"

    f() {
        $shell -c 'ulimit -St 1; ./sigcnt 2>&1 >/dev/null'
    }
    sigxcpus_sent=`f`
    echo "Number of SIGXCPUs sent: ${sigxcpus_sent}"
}

test_shell_sudo() {
    shell=$1
    echo_underscored "Testing shell with sudo: $shell"

    f() {
        sudo $shell -c 'ulimit -St 1; ulimit -Ht 1; ulimit -St 2 && ulimit -Ht' \
            2>/dev/null;
    }
    out=`f`; ret=$?;
    if [[ $ret == 0 ]]; then
        case $out in
            1)
                raising_soft_beyond_hard='no';;
            2)
                raising_soft_beyond_hard='yes';;
            *)
                echo UNEXPECTED;;
        esac
    else
        raising_soft_beyond_hard='impossible'
    fi
    echo "Raising soft beyond hard limit raises it: ${raising_soft_beyond_hard}"
}

main() {
    echo "Testing on platform: $(uname)"

    sigxcpu=$(get_sigcode XCPU)
    sigkill=$(get_sigcode KILL)
    echo Number of signal SIGXCPU: ${sigxcpu}
    echo Number of signal SIGKILL: ${sigkill}

    create_runner
    create_counter
    echo

    for shell in zsh bash dash; do
        which $shell >/dev/null || continue;
        test_shell $shell
        echo
    done

    for shell in zsh bash dash; do
        which $shell >/dev/null || continue;
        test_shell_sudo $shell
        echo
    done
}

main

corresponding gist还配有一张更好的 table 。

最佳答案

首先,这是 ulimit 的绝对规则,包括 shell 在内的所有进程都受到限制:

  • 任何人都可以降低自己的硬性限制。
  • 提高硬性限制需要特殊权限。
  • 只要小于硬限制,软限制就可以上下提高。

考虑到这一点:

  1. Should I be able to raise the limit set by an earlier call to ulimit again?

软限制,是的。硬限制,否。

bash appears to think no whereas zsh thinks yes.

Bash 默认设置硬限制。 Zsh 默认设置软限制。

Zsh 记录了这一点,但 bash 没有。无论如何,strace 告诉一切:

$ strace -e setrlimit zsh -c 'ulimit -t 1'
setrlimit(RLIMIT_CPU, {rlim_cur=1, rlim_max=RLIM64_INFINITY}) = 0

$ strace -e setrlimit bash -c 'ulimit -t 1'
setrlimit(RLIMIT_CPU, {rlim_cur=1, rlim_max=1}) = 0
  1. What signals will I be sent?

如果超出 CPU 软限制,您 will receive a SIGXCPU 。之后发生的事情在 POSIX 中是未定义的。根据其手册页,Linux 将每秒重新发送 SIGXCPU,直到达到硬限制,此时您将收到 SIGKILL。

Do I get a grace period?

您可以通过设置软限制来选择自己的宽限期。

警告:

zsh 上,设置硬限制而不同时设置软限制将导致该限制应用于子级而不是 shell:

zsh% ulimit -H -t 1
zsh% ( while true; do true; done )   # is a child, soon killed
zsh% while true; do true; done       # not a child, never dies

如果您同时设置这两个限制,它们将应用于当前 shell,如 bash 中所示:

zsh% ulimit -SH -t 1
zsh% while true; do true; done       # will now die, just like bash

我不知道这背后的理由是什么。

关于bash - `ulimit -t` 在 shell 和操作系统之间完全不可移植?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37817366/

相关文章:

ruby-on-rails - Rails 4.1 环境变量不重新加载

c++信号处理程序可以唤醒线程吗?

linux - 自动复制文件

linux - Linux Shell 中的 `{} +` 在 Windows 中相当于什么?

java - Tomcat启动和停止使用shell脚本?

linux - 取消注释文件中的特定行

c - C 中的 SIGINT 是否会自动刷新和关闭打开的流?

python - 使异步事件循环响应 Windows(和 Unix)上的 KeyboardInterrupt

bash - Grep 并打印返回引用

linux - 在 Linux 上重命名多个文件同时保持相同的扩展名