Bash: nieskończony sen (nieskończone blokowanie)

158

Używam startxdo uruchamiania X, który oceni mój .xinitrc. W moim .xinitrcuruchamiam menedżera okien za pomocą /usr/bin/mywm. Teraz, jeśli zabiję mojego WM (aby np. Przetestować inny WM), X również zakończy działanie, ponieważ .xinitrcskrypt osiągnął EOF. Więc dodałem to na końcu mojego .xinitrc:

while true; do sleep 10000; done

W ten sposób X nie wyłączy się, jeśli zabiję mojego WM. Teraz moje pytanie: jak mogę spać w nieskończoność zamiast snu w pętli? Czy jest jakaś komenda, która trochę zamrozi skrypt?

Z poważaniem

watain
źródło

Odpowiedzi:

330

sleep infinity robi dokładnie to, co sugeruje i działa bez znęcania się nad kotami.

Donarsson
źródło
16
Chłodny. Niestety mój busybox nie rozumie.
not-a-user
12
BSD (lub przynajmniej OS X) też nie rozumie sleep infinity, chociaż fajnie było się o tym dowiedzieć dla Linuksa. Jednak while true; do sleep 86400; donepowinno być substytutem.
Ivan X
16
W związku z tym przeprowadziłem pewne badania, które udokumentowałem w osobnej odpowiedzi. Podsumowując: infinityjest konwertowane w C z „string” do a double. Następnie doublejest to obcinane do maksymalnych dozwolonych wartości timespec, co oznacza bardzo dużą liczbę sekund (zależnych od architektury), ale w teorii skończoną.
jp48
72

tail nie blokuje

Jak zawsze: na wszystko jest odpowiedź, która jest krótka, łatwa do zrozumienia, łatwa do zrozumienia i całkowicie błędna. Tutaj tail -f /dev/nullnależy do tej kategorii;)

Jeśli spojrzysz na to razem strace tail -f /dev/null, zauważysz, że to rozwiązanie jest dalekie od blokowania! Prawdopodobnie jest jeszcze gorsze niż sleeprozwiązanie w pytaniu, ponieważ wykorzystuje (pod Linuksem) cenne zasoby, takie jak inotifysystem. Również inne procesy, które piszą, aby /dev/nullzrobić tailpętlę. (Na moim Ubuntu64 16.10 dodaje to kilka 10 wywołań systemowych na sekundę w już zajętym systemie).

Pytanie dotyczyło polecenia blokującego

Niestety nie ma czegoś takiego.

Przeczytaj: Nie znam sposobu, aby zarchiwizować to bezpośrednio w powłoce.

Wszystko (nawet sleep infinity) może zostać przerwane jakimś sygnałem. Więc jeśli chcesz być naprawdę pewien, że nie zwróci się wyjątkowo, musi działać w pętli, tak jak już to zrobiłeś dla twojego sleep. Należy pamiętać, że (w systemie Linux) /bin/sleepnajwyraźniej jest ograniczony do 24 dni (spójrz strace sleep infinity), dlatego najlepsze, co możesz zrobić, to prawdopodobnie:

while :; do sleep 2073600; done

(Zauważ, że uważam, że sleeppętle są wewnętrznie pętle dla wartości wyższych niż 24 dni, ale to oznacza: nie blokuje, tylko bardzo powoli się zapętla. Dlaczego więc nie przenieść tej pętli na zewnątrz?)

.. ale możesz podejść całkiem blisko z nienazwanym fifo

Możesz stworzyć coś, co naprawdę blokuje, o ile nie ma sygnałów wysyłanych do procesu. Następujące zastosowania bash 4, 2 PID i 1 fifo:

bash -c 'coproc { exec >&-; read; }; eval exec "${COPROC[0]}<&-"; wait'

Możesz sprawdzić, czy to naprawdę blokuje, stracejeśli chcesz:

strace -ff bash -c '..see above..'

Jak to zostało skonstruowane

readblokuje się, jeśli nie ma danych wejściowych (zobacz inne odpowiedzi). Jednak tty(aka. stdin) Zwykle nie jest dobrym źródłem, ponieważ jest zamykane, gdy użytkownik się wylogowuje. Może również ukraść część danych wejściowych z tty. Niemiły.

Aby utworzyć readblok, musimy poczekać na coś takiego, fifoco nigdy nie zwróci niczego. W bash 4nie jest poleceniem, które mogą dostarczyć nam dokładnie z taką fifo: coproc. Jeśli również poczekamy na blokowanie read(które jest nasze coproc), skończymy. Niestety wymaga to otwarcia dwóch identyfikatorów PID i pliku fifo.

Wariant z nazwanym fifo

Jeśli nie zawracasz sobie głowy używaniem nazwanego fifo, możesz to zrobić w następujący sposób:

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

Nieużywanie pętli przy odczycie jest trochę niechlujne, ale możesz użyć tego ponownie fifotak często, jak chcesz i sprawić, że reads terminat zostanie użyty touch "$HOME/.pause.fifo"(jeśli czeka więcej niż jeden odczyt, wszystkie są przerywane jednocześnie).

Lub użyj pause()wywołania systemowego Linux

W przypadku nieskończonego blokowania istnieje wywołanie jądra Linuksa, o nazwie pause(), które robi to, co chcemy: Czekaj wiecznie (aż nadejdzie sygnał). Jednak nie ma do tego (jeszcze) programu przestrzeni użytkownika.

do

Stworzenie takiego programu jest łatwe. Oto fragment stworzyć bardzo mały program o nazwie Linux pause, który wstrzymuje na czas nieokreślony (potrzeb diet, gccetc.):

printf '#include <unistd.h>\nint main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause

python

Jeśli nie chcesz samemu kompilować czegoś, ale masz już pythonzainstalowany, możesz użyć tego pod Linuksem:

python -c 'while 1: import ctypes; ctypes.CDLL(None).pause()'

(Uwaga: użyj exec python -c ...do zastąpienia bieżącej powłoki, zwalnia to jeden PID. Rozwiązanie można również ulepszyć za pomocą niektórych przekierowań we / wy, zwalniając nieużywane FD. To zależy od Ciebie.)

Jak to działa (chyba): ctypes.CDLL(None)ładuje standardową bibliotekę C i uruchamia pause()w niej funkcję w ramach dodatkowej pętli. Mniej wydajna niż wersja C, ale działa.

Moja rekomendacja dla Ciebie:

Pozostań w zapętlonym śnie. Jest łatwy do zrozumienia, bardzo przenośny i przez większość czasu blokuje.

Tino
źródło
1
@Andrew Normalnie nie potrzebujesz trap(które modyfikuje zachowanie powłoki na sygnały) ani tła (które pozwala powłoce przechwytywać sygnały z terminala, jak Strg + C). Więc sleep infinitywystarczy (zachowuje się tak, exec sleep infinityjakby to była ostatnia instrukcja. Aby zobaczyć różnicę użyj strace -ffDI4 bash -c 'YOURCODEHERE'). Zapętlony sen jest lepszy, ponieważ sleepw pewnych okolicznościach może powrócić. Na przykład nie chcesz, aby X11 nagle się wyłączył killall sleep, tylko dlatego, że zamiast pętli uśpienia .xstartupkończy sleep infinitysię.
Tino
Może być trochę niejasne, ale s6-pausejest to polecenie użytkownika do uruchomienia pause(), opcjonalnie ignorujące różne sygnały.
Patrick,
@Tino /bin/sleepnie jest ograniczone do 24 dni, jak mówisz. Byłoby miło, gdybyś mógł to zaktualizować. Obecnie w systemie Linux ten kod jest aktywny. Ogranicza pojedyncze nanosleep()wywołania systemowe do 24 dni, ale wywołuje je w pętli. Więc sleep infinitynie powinien wychodzić po 24 dniach. doubleDodatni nieskończoność zostaje przekształca się w struct timespec. Patrząc na rpl_nanosleepGDB, infinityzostaje przekonwertowany { tv_sec = 9223372036854775807, tv_nsec = 999999999 }na Ubuntu 16.04.
nh2
@ nh2 Wspomniano już w tekście, że sen prawdopodobnie zapętla się, zamiast być całkowicie blokującym. Teraz trochę go zredagowałem, aby mieć nadzieję, że ten fakt jest nieco bardziej jasny. Zwróć uwagę na to „ prawdopodobnie ”, ponieważ stracesam nie mogę udowodnić, że naprawdę jest wkompilowany kod zapętlający sleep, i nie chcę czekać 24 dni tylko na przetestowanie tego (lub dekompilację /bin/sleep). Zawsze lepiej jest programować defensywnie, jeśli nie ma twardego matematycznego dowodu, że coś jest naprawdę takie, jakim się wydaje. Nigdy też niczego nie ufaj:killall -9 sleep
Tino
Opcję pause () można całkiem łatwo zrobić za pomocą perl: perl -MPOSIX -e 'pause ()'
tgoodhart
70

Może wydaje się to brzydkie, ale dlaczego nie po prostu biegać cati czekać na wejście w nieskończoność?

Michał Trybus
źródło
4
To nie działa, jeśli nie masz wiszącej rury, z której można czytać. Proszę doradź.
Matt Joiner
2
@Matt, może zrobić fajkę i catto? mkfifo pipe && cat pipe
Michał Trybus
Co mówi @twalberg, ale dodatkowo możesz od razu ponownie przypisać do 3 i odłączyć go, jak pokazano tutaj: superuser.com/a/633185/762481
jp48
32

TL; DR: sleep infinityfaktycznie przesypia maksymalny dozwolony czas, który jest skończony.

Zastanawiając się, dlaczego nie jest to nigdzie udokumentowane, zadałem sobie trud przeczytania źródeł z coreutils GNU i stwierdziłem, że wykonuje z grubsza to, co następuje:

  1. Użyj strtodz C stdlib na pierwszym argumencie, aby przekonwertować „nieskończoność” na podwójną precyzję. Tak więc, zakładając podwójną precyzję IEEE 754, 64-bitowa dodatnia wartość nieskończoności jest przechowywana w secondszmiennej.
  2. Invoke xnanosleep(seconds)( znajdujący się w gnulib ), to z kolei wywołuje dtotimespec(seconds)( również w gnulib ) do konwersji z doublena struct timespec.
  3. struct timespecto po prostu para liczb: część całkowita (w sekundach) i część ułamkowa (w nanosekundach). Naiwna zamiana dodatniej nieskończoności na liczbę całkowitą spowodowałaby niezdefiniowane zachowanie (patrz §6.3.1.4 standardu C), więc zamiast tego skraca się do TYPE_MAXIMUM (time_t).
  4. Rzeczywista wartość TYPE_MAXIMUM (time_t)nie jest ustawiona w standardzie (nawet sizeof(time_t)nie jest); więc dla przykładu wybierzmy x86-64 z ostatniego jądra Linuksa.

To jest TIME_T_MAXw jądrze Linuksa, które jest zdefiniowane ( time.h) jako:

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

Zauważ, że time_tjest __kernel_time_ti time_tjest long; używany jest model danych LP64, więc sizeof(long)8 (64 bity).

Skutkiem czego jest: TIME_T_MAX = 9223372036854775807.

To znaczy: sleep infinitedaje w wyniku rzeczywisty czas snu 9223372036854775807 sekund (10 ^ 11 lat). A dla 32-bitowych systemów linuxowych ( sizeof(long)to 4 (32 bity)): 2147483647 sekund (68 lat; zobacz także problem z rokiem 2038 ).


Edycja : najwyraźniej nanosecondswywołana funkcja nie jest bezpośrednio wywołaniem syscall, ale opakowaniem zależnym od systemu operacyjnego (również zdefiniowanym w gnulib ).

Jest to dodatkowy krok w wyniku: na niektórych systemach, gdzie HAVE_BUG_BIG_NANOSLEEPjest truesen jest obcięty do 24 dni, a następnie wezwał w pętli. Dzieje się tak w przypadku niektórych (lub wszystkich?) Dystrybucji Linuksa. Zauważ, że to opakowanie może nie być używane, jeśli test w czasie konfiguracji zakończy się pomyślnie ( źródło ).

W szczególności byłoby to 24 * 24 * 60 * 60 = 2073600 seconds(plus 999999999 nanosekund); ale jest to wywoływane w pętli w celu uwzględnienia określonego całkowitego czasu snu. Dlatego poprzednie wnioski pozostają aktualne.


Podsumowując, wynikający z tego czas snu nie jest nieskończony, ale wystarczająco długi dla wszystkich praktycznych celów , nawet jeśli wynikający z tego faktyczny upływ czasu nie jest przenośny; to zależy od systemu operacyjnego i architektury.

Aby odpowiedzieć na pierwotne pytanie, jest to oczywiście wystarczająco dobre, ale jeśli z jakiegoś powodu (system o bardzo ograniczonych zasobach) naprawdę chcesz uniknąć bezużytecznego dodatkowego licznika czasu, wydaje mi się, że najbardziej poprawną alternatywą jest użycie catmetody opisanej w innych odpowiedziach .

jp48
źródło
1
W następnych coreutils sleep infinitybędzie teraz spać wiecznie bez zapętlania: lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
Vladimir Panteleev
8

sleep infinitywygląda najbardziej elegancko, ale czasami z jakiegoś powodu nie działa. W takim przypadku można spróbować innych poleceń blokowania takich jak cat, read, tail -f /dev/null, grep aitd

Hui Zheng
źródło
1
tail -f /dev/nullbyło również działającym rozwiązaniem dla mnie na platformie SaaS
schmunk
2
tail -f /dev/nullma również tę zaletę, że nie zużywa stdin. Użyłem go z tego powodu.
Sudo Bash
Osoby rozważające tę opcję powinny przeczytać tę odpowiedź, aby poznać konsekwencje tej opcji.
Shadow
6

A co z wysłaniem SIGSTOP- a do siebie?

Powinno to wstrzymać proces do momentu odebrania SIGCONT. Tak jest w twoim przypadku: nigdy.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;
michuelnik
źródło
6
Sygnały są asynchroniczne. Może się więc zdarzyć co następuje: a) wywołanie powłoki kill b) polecenie kill mówi jądru, że powłoka powinna odebrać sygnał STOP c) funkcja kill kończy i powraca do powłoki d) powłoka kontynuuje działanie (może się kończy, ponieważ kończy się skrypt) e) jądro w końcu znajduje czas na dostarczenie sygnał STOP do powłoki
not-a-user
1
@temple Świetny wgląd, nie myśleliśmy o asynchronicznej naturze sygnałów. Dzięki!
michuelnik
4

Pozwólcie, że wyjaśnię, dlaczego sleep infinitydziała, chociaż nie jest to udokumentowane. Odpowiedź jp48 jest również przydatna.

Najważniejsze: określając influb infinity(bez rozróżniania wielkości liter) możesz spać najdłużej, na co pozwala implementacja (tj. Mniejsza wartość HUGE_VALi TYPE_MAXIMUM(time_t)).

Przejdźmy teraz do szczegółów. Kod źródłowy sleeppolecenia można odczytać z coreutils / src / sleep.c . Zasadniczo funkcja robi to:

double s; //seconds
xstrtod (argv[i], &p, &s, cl_strtod); //`p` is not essential (just used for error check).
xnanosleep (s);

Zrozumienie xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Zgodnie z gnulib / lib / xstrtod.c , wywołanie funkcji xstrtod()konwertuje ciąg znaków argv[i]na wartość zmiennoprzecinkową i zapisuje ją do niej *sprzy użyciu funkcji konwertującej cl_strtod().

cl_strtod()

Jak widać z coreutils / lib / cl-strtod.c , cl_strtod()konwertuje ciąg znaków na wartość zmiennoprzecinkową, używając strtod().

strtod()

Według man 3 strtod, strtod()konwertuje ciąg znaków do wartości typu double. Strona podręcznika mówi

Oczekiwaną formą (początkowej części) ciągu jest ... lub (iii) nieskończoność lub ...

a nieskończoność definiuje się jako

Nieskończoność to „INF” lub „INFINITY”, pomijając przypadek.

Chociaż dokument mówi

Jeśli poprawna wartość spowodowałaby przepełnienie, zwracany jest znak plus lub minus HUGE_VAL( HUGE_VALF, HUGE_VALL)

, nie jest jasne, jak traktuje się nieskończoność. Zobaczmy więc kod źródłowy gnulib / lib / strtod.c . To, co chcemy przeczytać, to

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

Dlatego INFi INFINITY(bez rozróżniania wielkości liter) są traktowane jako HUGE_VAL.

HUGE_VAL rodzina

Użyjmy N1570 jako standardu C. HUGE_VAL, HUGE_VALFa HUGE_VALLmakra są zdefiniowane w §7.12-3

Makro
    HUGE_VAL
rozwija się do dodatniego podwójnego wyrażenia stałego, niekoniecznie reprezentowanego jako liczba zmiennoprzecinkowa. Makra
    HUGE_VALF
    HUGE_VALL
są odpowiednio zmiennoprzecinkowymi i długimi podwójnymi odpowiednikami HUGE_VAL.

HUGE_VAL, HUGE_VALFi HUGE_VALLmogą być dodatnimi nieskończonościami w implementacji obsługującej nieskończoności.

oraz w §7.12.1-5

Jeśli pływający wynik ocieka i domyślne zaokrąglenie jest w istocie, to funkcja zwraca wartość makra HUGE_VAL, HUGE_VALFlub HUGE_VALLw zależności od rodzaju powrotnej

Zrozumienie xnanosleep (s)

Teraz rozumiemy całą istotę xstrtod(). Z powyższych wyjaśnień jasno wynika xnanosleep(s), że widzieliśmy najpierw faktycznie oznacza xnanosleep(HUGE_VALL).

xnanosleep()

Zgodnie z kodem źródłowym gnulib / lib / xnanosleep.c , xnanosleep(s)zasadniczo robi to:

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

dtotimespec()

Ta funkcja konwertuje argument typu doublena obiekt typu struct timespec. Ponieważ jest to bardzo proste, zacytuję kod źródłowy gnulib / lib / dtotimespec.c . Wszystkie komentarze są dodawane przeze mnie.

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

Ponieważ time_tjest zdefiniowany jako typ całkowity (patrz §7.27.1-3), jest naturalne, że zakładamy, że maksymalna wartość typu time_tjest mniejsza niż HUGE_VAL(typu double), co oznacza, że ​​wchodzimy w przypadek przepełnienia. (W rzeczywistości założenie to nie jest potrzebne, ponieważ we wszystkich przypadkach procedura jest zasadniczo taka sama).

make_timespec()

Ostatnia ściana, na którą musimy się wspiąć, to make_timespec(). Na szczęście jest to tak proste, że wystarczy przytoczyć kod źródłowy 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;
}
ynn
źródło
2

Niedawno musiałem to zrobić. Wymyśliłem następującą funkcję, która pozwoli bashowi spać wiecznie bez wywoływania żadnego zewnętrznego programu:

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

UWAGA: Wcześniej opublikowałem wersję tego, która otwierała i zamykała deskryptor pliku za każdym razem, ale odkryłem, że w niektórych systemach zrobienie tego setki razy na sekundę w końcu blokowało się. W ten sposób nowe rozwiązanie zachowuje deskryptor pliku między wywołaniami funkcji. Bash i tak posprząta przy wyjściu.

Można to nazwać podobnie jak / bin / sleep i będzie spać przez żądany czas. Nazywany bez parametrów, zawiesi się na zawsze.

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

Na moim blogu jest napis z nadmierną ilością szczegółów

śruba
źródło
1

Takie podejście nie pochłonie żadnych zasobów do podtrzymania procesu.

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

Awaria

  • while :; do sleep 1; done & Tworzy fikcyjny proces w tle
  • kill -STOP $! Zatrzymuje proces w tle
  • wait $! Poczekaj na proces w tle, będzie to blokować na zawsze, ponieważ proces w tle został wcześniej zatrzymany
qoomon
źródło
0

Zamiast zabijać menedżera okien, spróbuj uruchomić nowy z --replacelub, -replacejeśli jest dostępny.

Wstrzymano do odwołania.
źródło
1
Jeśli używam --replace, zawsze otrzymuję ostrzeżenie typu another window manager is already running. To nie ma dla mnie większego sensu.
watain
-2
while :; do read; done

bez czekania na sen dziecka.

shuaiming
źródło
1
To zjada, stdinjeśli nadal jest podłączony do tty. Jeśli uruchomisz go za pomocą < /dev/nullzajętych pętli. Może się to przydać w pewnych sytuacjach, więc nie głosuję przeciw.
Tino
1
To bardzo zły pomysł, po prostu pochłonie całą masę procesora.
Mohammed Noureldin