Domyślny kod wyjścia po zakończeniu procesu?


54

Kiedy proces zostanie zabity sygnałem nadającym się do obsługi, takim jak SIGINTlub, SIGTERMale nie obsługuje on sygnału, jaki będzie kod zakończenia procesu?

Co z takimi nieobsługiwanymi sygnałami SIGKILL?

Z tego, co mogę powiedzieć, zabicie procesu z SIGINTprawdopodobnym wynikiem w kodzie wyjścia 130, ale czy różni się to w zależności od implementacji jądra lub powłoki?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

Nie jestem pewien, jak bym przetestował inne sygnały ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

1
twoje killall myScriptprace, stąd zwrócenie killall (a nie skryptu!) wynosi 0. Możesz umieścić kill -x $$[x będący liczbą sygnału, a $$ zwykle rozszerzane przez powłokę do PID tego skryptu (działa w sh, bash, ...)] w skrypcie, a następnie przetestuj, jaki był jego rdzeń wyjściowy.
Olivier Dulac,


komentarz na temat pytania częściowego: Nie umieszczaj myScript w tle. (pomiń &). Wyślij sygnał z innego procesu powłoki (w innym terminalu), a następnie możesz go użyć $?po zakończeniu działania myScript.
MattBianco

Odpowiedzi:


61

Procesy mogą wywoływać wywołanie _exit()systemowe (w systemie Linux, patrz także exit_group()) z argumentem liczby całkowitej, aby zgłosić kod wyjścia do ich rodzica. Chociaż jest to liczba całkowita, tylko 8 najmniej znaczących bitów jest dostępnych dla rodzica (wyjątek stanowi to podczas używania waitid()lub obsługi SIGCHLD w rodzicu w celu pobrania tego kodu , choć nie w Linuksie).

Rodzic zwykle robi a wait()lub, waitpid()aby uzyskać status swojego dziecka jako liczbę całkowitą (chociaż waitid()można użyć również nieco innej semantyki).

W systemie Linux i większości Uniksów, jeśli proces zakończy się normalnie, bity od 8 do 15 tego numeru statusu będą zawierały kod wyjścia, który został przekazany exit(). Jeśli nie, to 7 najmniej znaczących bitów (od 0 do 6) będzie zawierało numer sygnału, a bit 7 zostanie ustawiony, jeśli rdzeń został zrzucony.

perljest $?, na przykład, że zawiera szereg określonych przez waitpid():

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Powłoki podobne do Bourne'a również ustawiają status wyjścia ostatniego polecenia uruchomienia we własnej $?zmiennej. Jednak nie zawiera bezpośrednio liczby zwróconej przez waitpid(), ale transformację i różni się między powłokami.

Wspólne dla wszystkich powłok jest to, że $?zawiera najmniej 8 bitów kodu wyjścia (liczby przekazanej do exit()), jeśli proces zakończy się normalnie.

Różni się to, gdy proces kończy się sygnałem. We wszystkich przypadkach, a jest to wymagane przez POSIX, liczba będzie większa niż 128. POSIX nie określa, jaka może być wartość. W praktyce jednak we wszystkich znanych mi powłokach Bourne'a 7 najniższych bitów $?będzie zawierało liczbę sygnałów. Ale gdzie njest numer sygnału,

  • w ash, zsh, pdksh, bash, powłoka Bourne'a $?jest 128 + n. Co to znaczy, że w tych pocisków, jeśli masz $?z 129, nie wiem, czy to dlatego, że proces zakończył działanie exit(129), czy też został zabity sygnałem 1( HUPw większości systemów). Ale uzasadnieniem jest to, że powłoki, gdy same się kończą, domyślnie zwracają status wyjścia ostatnio zakończonej komendy. Upewniając się, że $?nigdy nie jest większy niż 255, pozwala to uzyskać spójny status wyjścia:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93, $?jest 256 + n. Oznacza to, że od wartości $?możesz rozróżnić proces zabity i nie zabity. Nowsze wersje ksh, przy wyjściu, jeśli $?były większe niż 255, zabijają się tym samym sygnałem, aby móc zgłosić ten sam status wyjścia rodzicowi. Chociaż wydaje się to dobrym pomysłem, oznacza to, że kshwygeneruje dodatkowy zrzut rdzenia (potencjalnie nadpisujący drugi), jeśli proces zostanie zabity przez sygnał generujący rdzeń:

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    Można nawet powiedzieć, że jest błąd, który ksh93zabija się, nawet jeśli $?pochodzi od funkcji return 257wykonanej przez:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yash. yashoferuje kompromis. Powraca 256 + 128 + n. Oznacza to, że możemy również odróżnić zabity proces od tego, który zakończył się prawidłowo. A po wyjściu zgłosi się 128 + nbez samobójstwa i skutków ubocznych, jakie może mieć.

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

Aby uzyskać sygnał z wartości $?, przenośnym sposobem jest użycie kill -l:

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(ze względu na przenośność nigdy nie należy używać numerów sygnałów, a jedynie nazw sygnałów)

Na frontach innych niż Bourne:

  • csh/ tcshi to fishsamo co powłoka Bourne'a, z tym wyjątkiem, że status jest $statuszamiast $?(należy pamiętać, że zshustawia się również $statusdla zgodności z csh(oprócz $?)).
  • rc: status wyjścia $statusrównież jest, ale gdy zabije go sygnał, zmienna ta zawiera nazwę sygnału (jak sigtermlub sigill+corejeśli rdzeń został wygenerowany) zamiast liczby, co jest kolejnym dowodem dobrego projektu tej powłoki .
  • es. status wyjścia nie jest zmienną. Jeśli zależy Ci na tym, uruchom polecenie jako:

    status = <={cmd}
    

    która zwróci liczbę sigtermlub sigsegv+corejak w rc.

Może pod względem kompletności, powinniśmy wspomnieć zsh„s $pipestatusi bash” s $PIPESTATUStablice, które zawierają kod zakończenia elementów ostatniej rurociągu.

A także dla kompletności, jeśli chodzi o funkcje powłoki i pliki źródłowe, domyślnie funkcje zwracają ze statusem zakończenia ostatniego uruchomienia polecenia, ale mogą również ustawić jawnie status powrotu za pomocą returnwbudowanego. Widzimy tutaj pewne różnice:

  • bashi mksh(od R41 regresja ^ Witch najwyraźniej wprowadzona umyślnie ) skróci liczbę (dodatnią lub ujemną) do 8 bitów. Na przykład return 1234ustawimy $?na 210, return -- -1ustawimy $?na 255.
  • zshoraz pdksh(i pochodne inne niż mksh) pozwalają na każdą 32-bitową liczbę całkowitą ze znakiem (-2 31 do 2 31 -1) (i skracają liczbę do 32 bitów ).
  • ashi yashzezwól na dowolną liczbę całkowitą dodatnią od 0 do 2 31 -1 i zwróć błąd dla dowolnej liczby z tego.
  • ksh93w return 0celu return 320ustawienia $?w takim stanie, ale nic innego, obcina do 8 bitów. Uważaj, jak już wspomniano, że zwrócenie liczby między 256 a 320 może spowodować kshsamobójstwo przy wyjściu.
  • rci eszezwalaj na zwracanie dowolnych nawet list

Należy również pamiętać, że niektóre powłoki również użyć specjalnych wartości $?/ $statuszgłosić pewne warunki błędów, które nie są kod zakończenia procesu, jak 127i 126na polecenie nie znaleziono lub nie wykonywalny (lub błąd składni w pliku) pochodzących ...


1
an exit code to their parenta to get the *status* of their child. podkreśliłeś „status”. Czy exit codei *status*to samo? Przypadek tak, skąd biorą się dwa nazwiska? Sprawa inna niż ta, czy możesz podać definicję / odniesienie statusu?
n611x007

2
Są tutaj 3 liczby. Kod wyjścia : numer przekazane exit(). Status wyjścia : liczby otrzymanej przez waitpid()który zawiera kod, numer wyjście sygnału i czy istnieje rdzeń dumpingowych. Liczba udostępniana przez niektóre powłoki w jednej ze specjalnych zmiennych ( $?, $status), która jest transformacją statusu wyjścia w taki sposób, że zawiera kod wyjścia w przypadku normalnego zakończenia, ale także przenosi informację o sygnale, jeśli proces został zabity (ten jest również ogólnie nazywany statusem wyjścia ). Wszystko to wyjaśniono w mojej odpowiedzi.
Stéphane Chazelas

1
Widzę, dziękuję! Zdecydowanie doceniam tę wyraźną nutę tego rozróżnienia. Te wyrażenia dotyczące wyjścia są używane zamiennie w niektórych miejscach, w których warto je wykonać. Czy wariant zmiennej powłoki ma nawet (ogólną) nazwę? Sugeruję więc, aby to wyjaśnić, zanim przejdę do szczegółów na temat powłok. Sugeruję wstawienie wyjaśnienia (z twojego komentarza) po pierwszym lub drugim akapicie.
n611x007

1
Czy możesz wskazać cytat POSIX, który mówi o pierwszych 7 bitach będących sygnałem? Jedyne, co mogłem znaleźć, to > 128część: „Status wyjścia polecenia, które zakończyło się, ponieważ otrzymał sygnał, będzie zgłaszane jako większe niż 128”. pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

1
@cuonglm, nie sądzę, że jest dostępny publicznie nigdzie indziej przez HTTP, nadal możesz go pobrać z gmane przez NNTP. Poszukaj identyfikatora wiadomości efe764d811849b34eef24bfb14106f61@austingroupbugs.net(od 2015-05-06) lubXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas

23

Kiedy proces kończy działanie, zwraca wartość całkowitą do systemu operacyjnego. W większości wariantów unixa ta wartość jest pobierana modulo 256: wszystko oprócz bitów niskiego rzędu jest ignorowane. Status procesu potomnego jest zwracany do jego rodzica przez 16-bitową liczbę całkowitą, w której

  • bity 0–6 (7 bitów niskiego rzędu) to liczba sygnałów, która została użyta do zabicia procesu lub 0, jeśli proces zakończył się normalnie;
  • bit 7 jest ustawiany, jeśli proces został zabity przez sygnał i zrzucony rdzeń;
  • bity 8–15 są kodem zakończenia procesu, jeśli proces zakończył się normalnie, lub 0, jeśli proces został zabity przez sygnał.

Status jest zwracany przez waitwywołanie systemowe lub jedno z jego rodzeństwa. POSIX nie określa dokładnego kodowania statusu wyjścia i numeru sygnału; zapewnia tylko

  • sposób stwierdzenia, czy status wyjścia odpowiada sygnałowi czy normalnemu wyjściu;
  • sposób dostępu do kodu wyjścia, jeśli proces zakończył się normalnie;
  • sposób na uzyskanie dostępu do numeru sygnału, jeśli proces został zabity przez sygnał.

Ściśle mówiąc, nie ma wyjścia Kod gdy proces zostaje zabity przez sygnał: co tam jest wyjście zamiast statusu .

W skrypcie powłoki status wyjścia polecenia jest zgłaszany przez specjalną zmienną $?. Ta zmienna koduje status wyjścia w niejednoznaczny sposób:

  • Jeśli proces zakończył się normalnie, $?oznacza to jego status zakończenia.
  • Jeśli proces został zabity przez sygnał, wówczas $?w większości systemów jest to 128 plus numer sygnału. POSIX upoważnia tylko $?w tym przypadku więcej niż 128; ksh93 dodaje 256 zamiast 128. Nigdy nie widziałem wariantu uniksowego, który zrobiłby coś innego niż dodanie stałej do liczby sygnału.

Dlatego w skrypcie powłoki nie można jednoznacznie stwierdzić, czy polecenie zostało zabite sygnałem, czy zakończyło się kodem stanu większym niż 128, z wyjątkiem ksh93. Bardzo rzadko programy wychodzą z kodami stanu większymi niż 128, częściowo dlatego, że programiści unikają tego z powodu $?niejasności.

SIGINT jest sygnałem 2 w większości wariantów unixa, więc $?wynosi 128 + 2 = 130 dla procesu, który został zabity przez SIGINT. Zobaczysz 129 dla SIGHUP, 137 dla SIGKILL itp.


O wiele lepiej sformułowane i bardziej rzeczowe niż moje, nawet jeśli w gruncie rzeczy mówi to samo. Możesz wyjaśnić, że dotyczy $?to tylko pocisków podobnych do Bourne'a. Zobacz także yashinne (ale wciąż POSIX) zachowanie. Również zgodnie z POSIX + XSI (Unix), a kill -2 "$pid"wyśle ​​SIGINT do procesu, ale rzeczywisty numer sygnału może nie wynosić 2, więc $? niekoniecznie będzie to 128 + 2 (lub 256 + 2 lub 384 + 2), ale kill -l "$?"wróci INT, dlatego radziłbym, aby przenośność nie odnosiła się do samych liczb.
Stéphane Chazelas,

8

To zależy od twojej powłoki. Ze strony podręcznika bash(1), sekcja SHELL GRAMMAR , podsekcja Proste polecenia :

Zwracana wartość prostego polecenia wynosi [...] 128+ n, jeśli polecenie zakończy się sygnałem n .

Ponieważ SIGINTw systemie znajduje się sygnał numer 2, zwracana wartość wynosi 130, gdy jest uruchamiany w trybie Bash.


1
Jak na świecie to znajdujesz, a nawet wiesz, gdzie szukać? Kłaniam się przed twoim geniuszem.
Cory Klein

1
@CoryKlein: Przeważnie. Och, i prawdopodobnie będziesz również chciał strony signal(7)man.
Ignacio Vazquez-Abrams,

fajne rzeczy; czy wiesz, czy przypadkiem dołączę pliki w C z tymi stałymi? +1
Rui F Ribeiro

@CoryKlein Dlaczego nie wybrałeś tego jako poprawnej odpowiedzi?
Rui F Ribeiro

3

Wydaje się, że jest to właściwe miejsce, aby wspomnieć, że SVr4 wprowadził waitid () w 1989 r., Ale jak dotąd żaden ważny program nie korzysta z niego. waitid () pozwala odzyskać pełne 32 bity z kodu exit ().

Około 2 miesiące temu przepisałem część oczekiwania / kontroli zadania powłoki Bourne Shell, aby użyć waitid () zamiast waitpid (). Dokonano tego w celu usunięcia ograniczenia, które maskuje kod wyjścia za pomocą 0xFF.

Interfejs waitid () jest znacznie bardziej przejrzysty niż poprzednie implementacje wait (), z wyjątkiem wywołania cwait () z UNOS z 1980 roku.

Możesz być zainteresowany przeczytaniem strony podręcznika:

http://schillix.sourceforge.net/man/man1/bosh.1.html

i sprawdź sekcję „Zastępowanie parametrów”, która aktualnie gapi się na str. 8.

Nowe zmienne .sh. * Zostały wprowadzone dla interfejsu waitid (). Ten interfejs nie ma już dwuznacznego znaczenia dla liczb znanych z $? i znacznie ułatwi interfejs.

Pamiętaj, że musisz mieć funkcję waitid () zgodną z POSIX, aby móc korzystać z tej funkcji, więc Mac OS X i Linux obecnie tego nie oferują, ale funkcja waitid () jest emulowana w wywołaniu waitpid (), więc W przypadku platformy innej niż POSIX nadal otrzymasz tylko 8 bitów z kodu wyjściowego.

W skrócie: .sh.status to numeryczny kod wyjścia, .sh.code to numeryczny powód wyjścia.

Dla lepszej przenośności istnieje: .sh.codename dla tekstowej wersji przyczyny wyjścia, np. „DUMPED” i .sh.termsig, nazwa pojedyncza sygnału, który zakończył proces.

Dla lepszego wykorzystania istnieją dwie niezwiązane z wyjściem wartości .sh.codename: „NOEXEC” i „NOTFOUND”, które są używane, gdy program w ogóle nie może zostać uruchomiony.

FreeBSD naprawił błąd kerlnel waitid () w ciągu 20 godzin po moim raporcie, Linux nie zaczął jeszcze z naprawą. Mam nadzieję, że 26 lat po wprowadzeniu tej funkcji, która jest teraz w POSIX, wszystkie systemy operacyjne wkrótce ją obsługują.


Powiązaną odpowiedzią jest unix.stackexchange.com/a/453432/5132 .
JdeBP,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.