【问题标题】:`ulimit -t` entirely unportable between shells and OSs?`ulimit -t` 在 shell 和操作系统之间完全不可移植?
【发布时间】:2016-06-14 16:21:50
【问题描述】:

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

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

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

| |达尔文/zsh |达尔文/bash | FreeBSD/zsh | FreeBSD/bash | FreeBSD/破折号 | Linux/zsh | Linux/bash | Linux/破折号 | | ulimit -t 设置 |软限制 |两个限制 |软限制 |两个限制 |两个限制 |软限制 |两个限制 |两个限制 | | ulimit -t 获取 |软限制 |软限制 |软限制 |软限制 |软限制 |软限制 |软限制 |软限制 | |硬限制可以设置在软限制以下 |是的 |没有 |是的 |是的 |是的 |是的 |没有 |没有 | |软限制可以设置在硬限制之上 |是的 |没有 |是的 |没有 |没有 |是的 |没有 |没有 | |可以在没有特权的情况下提高硬限制 |是的 |没有 |是的 |没有 |没有 |是的 |没有 |没有 | |软信号| SIGXCPU | SIGXCPU | SIGXCPU | SIGXCPU | SIGXCPU | SIGXCPU | SIGXCPU | SIGXCPU | |硬信号| SIGXCPU | SIGXCPU |杀戮 |杀戮 |杀戮 |杀戮 |杀戮 |杀戮 | |发送的 SIGXCPU 数量 |一个 |一个 |一个 |一个 |一个 |多个 |多个 |多个 | |超越硬极限提升软提升它|是的 |不可能* |是的 |没有 |没有 |是的 |不可能* |不可能* | * 即使是根
#!/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 还配有更好的桌子。

【问题讨论】:

  • 这些限制是对用户的警告。内核中(通常)还设置了软限制和硬限制,即它们不是由 shell 管理的。再次,软限制被认为是一个警告,限制可能很快就会达到,并且程序可以将其进程的限制扩展到硬限制,但不能进一步(除非 root)。有关底层 C API,请参阅 man 2 getrlimitman 2 setrlimit

标签: bash shell signals zsh ulimit


【解决方案1】:

首先,以下是包括 shell 在内的所有进程都受限于 ulimits 的绝对规则:

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

考虑到这一点:

  1. 我应该能够再次提高之前调用 ulimit 设置的限制吗?

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

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

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

我有宽限期吗?

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

警告:

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

我不知道这背后的原因是什么。

【讨论】:

  • 是的,我测试并确认了这一切。您能否提供一系列以非 root 身份运行的命令来演示您所看到的内容?
  • 使用 (a) 和 (b) 您尝试使用 while 循环,如本答案所示,而不是使用您自己的代码,对吗?
  • 对不起。我的测试一定有缺陷。你说的完全正确。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-08
  • 2016-02-16
  • 2010-10-07
  • 2015-06-17
  • 1970-01-01
相关资源
最近更新 更多