`ulimit -t` совершенно непереносим между оболочками и ОС?

Обновление: это становится не столько вопросом, сколько резюме. Ну что ж...

bash, dash и zsh поставляются со встроенной командой ulimit, У каждого есть опция -t который принимает число в качестве аргумента, чтобы понимать его как процессорное время в секундах, которое могут потреблять процессы. После этого им будет отправлен сигнал. Так много ясно.

Хотя есть много неясного. И я нахожу некоторые из них довольно неожиданными. В частности, ваше поведение зависит как от оболочки, так и от базовой операционной системы. Я создал таблицу, которая суммирует степень изменчивости. Я также включил код для скрипта, который я использовал для автоматического получения этих результатов. Последний тест нуждается в привилегиях суперпользователя и может быть остановлен, если вы закомментируете test_shell_sudo $shell,

| | Дарвин / зш | Дарвин / Баш | FreeBSD/zsh | FreeBSD/bash | FreeBSD/dash | Linux/ Zsh | Linux / Bash | Linux / Dash  |
| 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

Соответствующая сущность также поставляется с более приятным столом.

1 ответ

Решение

Во-первых, вот абсолютные правила ограничения, которыми ограничены все процессы, включая оболочки:

  • Любой может снизить собственный жесткий лимит.
  • Это требует специальных привилегий, чтобы поднять жесткий лимит.
  • Мягкий предел можно увеличивать и уменьшать, если он меньше жесткого.

Имея это в виду:

  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. Какие сигналы мне отправят?

Если вы превысите мягкое ограничение ЦП, вы получите 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

Если вы одновременно установите оба ограничения, они применяются к текущей оболочке, как в bash:

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

Я понятия не имею, в чем причина этого.

Другие вопросы по тегам