Bash: нескінченний сон (нескінченне блокування)


158

Я використовую startxдля запуску X, який оцінить моє .xinitrc. У моєму .xinitrcзапуску віконного менеджера використовується /usr/bin/mywm. Тепер, якщо я вб'ю свій WM (для того, щоб перевірити якийсь інший WM), X теж припиниться, оскільки .xinitrcсценарій досяг EOF. Тому я додав це наприкінці свого .xinitrc:

while true; do sleep 10000; done

Таким чином X не припиняється, якщо я вб'ю свій WM. Тепер моє запитання: як я можу зробити нескінченний сон замість петлі? Чи є така команда, яка начебто заморозить сценарій?

З найкращими побажаннями

Відповіді:


330

sleep infinity робить саме те, що пропонує, і працює без зловживань з котами.


16
Класно. На жаль, мій зайнятий не розуміє.
не-користувач

12
BSD (або принаймні OS X) теж не розуміє sleep infinity, хоча дізнатися про Linux було здорово. Однак, це while true; do sleep 86400; doneповинно бути адекватною заміною.
Іван X

16
З цього приводу я зробив деякі дослідження, які я задокументував окремою відповіддю. Підводячи підсумок: infinityперетворюється в C з "рядка" в a double. Тоді doubleце усічене до максимально допустимих значень timespec, що означає дуже велику кількість секунд (залежно від архітектури), але, теоретично, кінцеве.
jp48

72

tail не блокує

Як завжди: На все є відповідь, який короткий, легкий для розуміння, простий у дотриманні та зовсім неправильний. Тут tail -f /dev/nullпідпадає під цю категорію;)

Якщо ви подивитесь на це, strace tail -f /dev/nullви помітите, що це рішення далеко не блокування! Це, мабуть, навіть гірше, ніж sleepвирішення питання, оскільки воно використовує (під Linux) дорогоцінні ресурси, як inotifyсистема. Також інші процеси, які пишуть для /dev/nullстворення tailциклу. (У моєму Ubuntu64 16.10 це додає кілька 10 системних дзвінків в секунду у вже зайнятій системі.)

Питання полягало у блокуванні команди

На жаль, такого немає ..

Читайте: я не знаю жодного способу безпосередньо це архівувати разом із оболонкою.

Все (навіть 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є команда , яка може точно надати нам такі fifo: coproc. Якщо ми також будемо чекати блокування read(яке є нашим coproc), ми закінчили. На жаль, для цього потрібно залишати відкритими два PID та a fifo.

Варіант із названим fifo

Якщо ви не переймаєтесь іменем fifo, ви можете зробити це наступним чином:

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

Не використовувати цикл для читання є трохи неохайним, але ви можете використовувати це повторно fifoтак часто, як вам подобається, і зробити readтермінал s за допомогою touch "$HOME/.pause.fifo"(якщо очікується більше одного очікування читання, всі припиняються одразу).

Або скористайтеся системою pause()виклику Linux

Для нескінченного блокування існує дзвінок ядра Linux, який називається pause(), який робить те, що ми хочемо: зачекайте вічно (поки не надійде сигнал). Однак для цього немає програми для простору користувачів.

С

Створити таку програму досить просто. Ось фрагмент коду , щоб створити дуже невелику програму Linux під назвою , pauseяка робить паузу на невизначений термін (потреби diet, і gccт.д.):

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 ...для заміни поточної оболонки, це звільняє один PID. Рішення можна вдосконалити також за допомогою перенаправлення вводу-виводу, звільнивши невикористані диски. Це залежить від вас.)

Як це працює (я думаю): ctypes.CDLL(None)завантажує стандартну бібліотеку C і виконує pause()функцію в ній в якомусь додатковому циклі. Менш ефективний, ніж версія С, але працює.

Моя рекомендація для вас:

Залишайтеся на петельному сні. Це легко зрозуміти, дуже портативний і блокує більшу частину часу.


1
@Andrew Зазвичай вам не потрібен trap(що модифікує поведінку оболонки на сигнали), а також фон (що дозволяє оболонці перехоплювати сигнали з терміналу, як-от Strg + C). Так sleep infinityдостатньо (поводиться так, exec sleep infinityякби це останнє твердження. Щоб побачити різницю використання strace -ffDI4 bash -c 'YOURCODEHERE'). Цикл сну краще, тому що sleepможе повернутися за певних обставин. Наприклад, ви не хочете, щоб X11 раптово вимикався на a killall sleep, тільки тому, що .xstartupзакінчується sleep infinityзамість циклу сну.
Тіно

Може бути трохи незрозумілим, але s6-pauseце команда userland для запуску pause(), необов'язково ігнорування різних сигналів.
Патрік

@Tino /bin/sleepне обмежується на 24 дні, як ви говорите. Було б добре, якби ви могли це оновити. Зараз у Linux цей код активний. Він обмежує окремі nanosleep()системні дзвінки до 24 днів, але викликає їх у циклі. Тому sleep infinityне слід виходити через 24 дні. doubleПозитивна нескінченність перетвориться до struct timespec. Дивлячись на rpl_nanosleepGDB, infinityперетворюється { tv_sec = 9223372036854775807, tv_nsec = 999999999 }на Ubuntu 16.04.
nh2

@ nh2 У тексті вже згадувалося, що сон, ймовірно, циклічно замість того, щоб повністю блокувати. Я трохи відредагував це зараз, сподіваюся, зробити цей факт трохи більш зрозумілим. Зверніть увагу на це " ймовірно ", тому що straceя не можу довести той факт, що дійсно є якийсь цикл циклу, зібраний в ньому sleep, і я не хочу чекати 24 дні, щоб перевірити це (або декомпілювати /bin/sleep). Завжди краще програмувати оборонно, якщо немає важких математичних доказів, що щось є насправді, як здається. Також ніколи нічого не довіряйте:killall -9 sleep
Тіно

Параметр pause () можна виконати досить легко за допомогою perl: perl -MPOSIX -e 'pause ()'
tgoodhart

70

Можливо, це здається некрасивим, але чому б просто не запустити catі не дати йому чекати введення назавжди?


4
Це не працює, якщо у вас немає підвісної труби, з якої можна читати. Порадьте, будь ласка.
Метт Столяр

2
@Matt, може, зроби трубу і catце? mkfifo pipe && cat pipe
Michał Trybus

Що говорить @twalberg, але додатково ви можете негайно перепризначити 3 та від’єднати його, як показано тут: superuser.com/a/633185/762481
jp48

32

TL; DR: sleep infinityнасправді спить максимально допустимий час, який є кінцевим.

Замислюючись, чому це ніде не зафіксовано, я потрудився читати джерела з GNU coreutils, і я виявив, що воно виконує приблизно те, що випливає:

  1. Використовуйте strtodз C stdlib на першому аргументі, щоб перетворити "нескінченність" на подвійну точність. Отже, якщо припустити, що IEEE 754 з подвоєною точністю, 64-бітове значення позитивної нескінченності зберігається у secondsзмінній.
  2. Invoke xnanosleep(seconds)( знайдено в gnulib ), це в свою чергу , викликає dtotimespec(seconds)( також в gnulib ) для перетворення doubleв struct timespec.
  3. struct timespecце лише пара чисел: ціла частина (у секундах) і дробова частина (у наносекундах). Наївно перетворення позитивної нескінченності в ціле число може привести до непередбачуваного поведінки (див §6.3.1.4 від стандарту C), так що замість цього він відріже до TYPE_MAXIMUM (time_t).
  4. Фактичне значення TYPE_MAXIMUM (time_t)не встановлено у стандарті (навіть sizeof(time_t)не є); тож для прикладу виберемо x86-64 з недавнього ядра Linux.

Це TIME_T_MAXв ядрі Linux, яке визначається ( time.h) як:

(time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)

Зауважте, що time_tє __kernel_time_tі time_tє long; Використовується модель даних LP64, так sizeof(long)це 8 (64 біт).

Які результати в: TIME_T_MAX = 9223372036854775807.

Тобто: sleep infiniteфактичний час сну становить 9223372036854775807 секунд (10 ^ 11 років). А для 32-бітових систем Linux ( sizeof(long)це 4 (32 біта)): 2147483647 секунд (68 років; див. Також проблему 2038 року ).


Редагувати : мабуть, nanosecondsфункція викликається не безпосередньо системою виклику, а обгорткою, залежною від ОС (також визначеною в gnulib ).

Там це додатковий крок в результаті: для деяких систем , в яких HAVE_BUG_BIG_NANOSLEEPє trueсон скорочується до 24 днів , а потім викликається в циклі. Це стосується деяких (або всіх?) Дистрибутивів Linux. Зауважте, що ця обгортка не може використовуватися, якщо тест -time- час конфігурації завершився ( джерело ).

Зокрема, це було б 24 * 24 * 60 * 60 = 2073600 seconds(плюс 999999999 наносекунд); але це викликається циклом, щоб дотримуватися вказаний загальний час сну. Тому попередні висновки залишаються в силі.


На закінчення, отриманий час сну не є нескінченним, але достатньо високим для всіх практичних цілей , навіть якщо отриманий фактичний проміжок часу не є портативним; це залежить від ОС та архітектури.

Щоб відповісти на початкове запитання, це, очевидно, досить добре, але якщо з якихось причин ( дуже обмежена ресурсами система) ви дійсно хочете уникати непотрібного додаткового таймера зворотного відліку, я думаю, найбільш правильною альтернативою є використання catметоду, описаного в інших відповідях .


1
У наступних coreutils sleep infinityтепер фактично будуть спати вічно, не зациклившись
Володимир Пантелеев,

8

sleep infinityвиглядає найелегантніше, але іноді це не спрацьовує чомусь. У цьому випадку, ви можете спробувати інші команди блокування , такі як cat, read, tail -f /dev/null, і grep aт.д.


1
tail -f /dev/nullтакож було робочим рішенням для мене на платформі SaaS
schmunk

2
tail -f /dev/nullтакож має перевагу не споживання stdin. Я використовував це з цієї причини.
Судо Баш

Ті, хто розглядає цей варіант, повинні прочитати цю відповідь, щоб дізнатися про наслідки цього варіанту.
Тінь

6

А як же надсилати SIGSTOP до себе?

Це має призупиняти процес до отримання SIGCONT. Що у вашому випадку: ніколи.

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

6
Сигнали асинхронні. Отже, може статися таке: а) виклики оболонки kill b) kill повідомляє ядро, що оболонка отримає сигнал STOP; c) вбивство закінчується і повертається до оболонки; d) оболонка продовжується (можливо, припиняється, оскільки сценарій закінчується); сигнал СТОП на оболонку
не користувач

1
@temple Прекрасне розуміння, не думав про асинхронний характер сигналів. Дякую!
michuelnik

4

Дозвольте мені пояснити, чому sleep infinityпрацює, хоча це не документально підтверджено. Відповідь jp48 також корисна.

Найголовніше: Вказавши infабо infinity(не залежно від регістру), ви можете спати довше, ніж дозволяє ваша реалізація (тобто менша величина HUGE_VALта TYPE_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], &p, &s, cl_strtod)

xstrtod()

Відповідно до gnulib / lib / xstrtod.c , виклик xstrtod()перетворює рядок argv[i]у значення з плаваючою комою та зберігає його *s, використовуючи функцію перетворення cl_strtod().

cl_strtod()

Як видно з coreutils / lib / cl-strtod.c , cl_strtod()перетворює рядок у значення з плаваючою точкою, використовуючи strtod().

strtod()

Відповідно man 3 strtod, strtod()перетворює рядок у значення типу double. На сторінці написано

Очікувана форма (початкової частини рядка) - це ... або (iii) нескінченність, або ...

і нескінченність визначається як

Нескінченність є або "INF", або "INFINITY", ігноруючи випадок.

Хоча документ розповідає

Якщо правильне значення призведе до переповнення, повертається плюс або мінус HUGE_VAL( HUGE_VALF, HUGE_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;
  }

Таким чином, INFі INFINITY(як нечутливі до регістру) розглядаються як HUGE_VAL.

HUGE_VAL сім'я

Давайте використовувати N1570 як стандарт С. HUGE_VAL, HUGE_VALFа HUGE_VALLмакроси визначені в §7.12-3

Макрос
    HUGE_VAL
розширюється до позитивного подвійного постійного виразу, не обов'язково представленого як поплавок. Макроси -
    HUGE_VALF
    HUGE_VALL
це відповідно плаваючі та довгі подвійні аналоги HUGE_VAL.

HUGE_VAL, HUGE_VALFі HUGE_VALLможе бути позитивною нескінченністю в реалізації, яка підтримує нескінченності.

та в §7.12.1-5

Якщо плаваючий результат переливається і по замовчуванням округлення в дійсності, то функція повертає значення макросу HUGE_VAL, HUGE_VALFабо в HUGE_VALLвідповідно до типу, що повертається значення

Розуміння xnanosleep (s)

Тепер ми зрозуміли всю суть xstrtod(). З вищезгаданих пояснень зрозуміло, що xnanosleep(s)ми побачили, що насправді спочатку означає xnanosleep(HUGE_VALL).

xnanosleep()

Відповідно до вихідного коду gnulib / lib / xnanosleep.c , xnanosleep(s)по суті це робить:

struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);

dtotimespec()

Ця функція перетворює аргумент типу doubleв об’єкт типу struct timespec. Оскільки це дуже просто, дозвольте мені навести вихідний код gnulib / lib / dtotimespec.c . Всі коментарі я додаю.

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_VAL(типу double), а це означає, що ми входимо у випадок переповнення. (Насправді це припущення не потрібне, оскільки в усіх випадках процедура по суті однакова.)

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

2

Нещодавно у мене була потреба це зробити. Я придумав таку функцію, яка дозволить 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 / сон, і він буде спати протягом потрібного часу. Називається без параметрів, він буде повісити назавжди.

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

У моєму блозі є запис із зайвими подробицями


1

Цей підхід не затребує жодних ресурсів для підтримання процесу в живих.

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

Зламатися

  • while :; do sleep 1; done & Створює фіктивний процес у фоновому режимі
  • kill -STOP $! Зупиняє фоновий процес
  • wait $! Зачекайте фонового процесу, він буде блокуватися назавжди, тому що фоновий процес був зупинений раніше

0

Замість того, щоб вбивати менеджера вікон, спробуйте запустити новий із --replaceабо -replaceза наявності.


1
Якщо я використовую, --replaceя завжди отримую попередження на кшталт another window manager is already running. Це не має особливого сенсу для мене.
watain

-2
while :; do read; done

відсутність очікування процесу сну дитини.


1
Це з'являється, stdinякщо це все-таки трапиться підключити до tty. Якщо запустити його за допомогою < /dev/nullциклів зайнятості. У певних ситуаціях це може бути корисно, тому я не виступаю проти.
Тіно

1
Це дуже погана ідея, вона просто споживатиме багато процесора.
Мухаммед Нурелдін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.