`ulimit -t`在shell和OS之间完全不可移植?

时间:2016-06-14 16:21:50

标签: 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还带有更好的表格。

1 个答案:

答案 0 :(得分:2)

首先,这里是ulimits的绝对规则,包括shell在内的所有进程都被限制为:

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

考虑到这一点:

  
      
  1. 我是否可以再次提前通过ulimit调用设置的限制?
  2.   

软限制,是的。硬限制,没有。

  

bash似乎认为不,而zsh认为是。

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. 我会收到什么信号?
  2.   

如果超过软CPU限制,则为will receive a SIGXCPU。之后发生的事情在POSIX中是未定义的。根据其手册页,Linux将每秒重新发送一次SI​​GXCPU,直到达到硬限制为止,此时你就是SIGKILL&#39。

  

我有宽限期吗?

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

警告:

zsh上,设置硬限制而不设置软限制将导致限制适用于儿童而不是外壳:

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

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