Код виходу за замовчуванням, коли процес закінчується?


54

Коли процес вбивається за допомогою сигналу, керованого ручкою, SIGINTабо SIGTERMвін не обробляє сигнал, який буде вихідний код процесу?

А як щодо сигналів, що не працюють без обробки SIGKILL?

З того, що я можу сказати, вбивство процесу з SIGINTімовірними результатами виходу коду 130, але чи може це залежати від реалізації ядра чи оболонки?

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

Я не впевнений, як би я перевірив інші сигнали ...

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

1
ваші killall myScriptроботи, отже, повернення killall (а не сценарію!) дорівнює 0. Ви можете розмістити kill -x $$[x - номер сигналу, і $$ зазвичай розширюється оболонкою до PID цього сценарію (працює в sh, bash, ...)] всередині сценарію, а потім перевірити те, що було його вихідним ядром.
Олів'є Дулак


коментар до напівпитання: Не ставте myScript на задній план. (опустити &). Надішліть сигнал з іншого процесу оболонки (в інший термінал), який ви можете використовувати $?після закінчення myScript.
MattBianco

Відповіді:


61

Процеси можуть викликати _exit()системний виклик (в Linux, див. Також exit_group()) з цілим аргументом, щоб повідомити про вихідний код своєму батькові. Хоча це ціле число, для батьків є лише 8 найменш значущих бітів (виняток - при використанні waitid()або обробці на SIGCHLD у батьків, щоб отримати цей код , хоча не в Linux).

Батько, як правило, виконує wait()або waitpid()отримує статус своєї дитини як ціле число (хоча waitid()з дещо різною семантикою також можна використовувати).

В Linux і більшість Юніксів якщо процес припиняється , як правило, бітам з 8 по 15 з цього стану числа буде містити код завершення , як передається exit(). Якщо ні, то 7 найменш значущих бітів (0 до 6) будуть містити число сигналу, а біт 7 буде встановлений, якщо ядро ​​було скинуто.

perl«И $?, наприклад , містить це число , як встановлено 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

Оболонки, подібні до Борна, також вносять статус виходу останньої команди запуску у власну $?змінну. Однак воно не містить прямо повернене число waitpid(), а перетворення на ньому, і воно різне між оболонками.

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

Де вона відрізняється, коли процес припиняється сигналом. У всіх випадках, а це вимагає POSIX, число буде перевищувати 128. POSIX не вказує, яке значення може бути. На практиці, однак у всіх відомих мені оболонках Борна, найнижчі 7 біт $?містять номер сигналу. Але де nномер сигналу,

  • in ash, zsh, pdksh, bash, оболонка Bourne, $?є 128 + n. Що це означає, що в цих оболонках, якщо ви отримуєте $?від 129, ви не знаєте , чи є це тому , що процес завершився з exit(129)або він був убитий за сигналом 1( HUPв більшості систем). Але обґрунтуванням є те, що оболонки, коли вони самі виходять, за замовчуванням повертають статус виходу останньої випущеної команди. Переконайтесь, що $?він ніколи не перевищує 255, що дозволяє мати постійний статус виходу:

    $ 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, $?є 256 + n. Це означає, що за значенням $?ви можете розмежовувати процес вбитого та не вбитого. Більш новіші версії ksh, після виходу, якщо їх $?було більше 255, вбиває себе тим самим сигналом, щоб мати можливість повідомляти про той самий статус виходу своєму батькові. Хоча це звучить як гарна ідея, це означає, що kshгенерує додатковий дамп ядра (потенційно перезаписуючи інший), якщо процес був убитий ядром, що генерує сигнал:

    $ 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.
    

    Де ви навіть можете сказати, що виникла помилка - це те, що ksh93вбиває себе, навіть якщо $?походить від return 257виконаної функції:

    $ 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. yashпропонує компроміс. Це повертається 256 + 128 + n. Це означає, що ми можемо також розмежовувати процес вбитого та той, що закінчився належним чином. А після виходу він повідомить про себе, 128 + nне потребуючи самогубства та побічних ефектів, які він може мати.

    $ 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
    

Для отримання сигналу від значення $?портативного способу є використання kill -l:

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

(для портативності ніколи не слід використовувати сигнальні номери, а лише імена сигналів)

На фронтах, що не є Борном:

  • csh/ tcshі те fishсаме, що оболонка Bourne, за винятком того, що статус $statusзамість $?(зауважте, що zshтакож встановлено $statusсумісність із csh(на додаток до $?)).
  • rc: статус виходу також є $status, але коли його вбиває сигнал, ця змінна містить ім'я сигналу (як-от sigtermабо, sigill+coreякщо було створено ядро) замість числа, що є ще одним підтвердженням хорошої конструкції цієї оболонки .
  • es. статус виходу не є змінною. Якщо ви дбаєте про нього, ви запускаєте команду як:

    status = <={cmd}
    

    який поверне номер або sigtermабо sigsegv+coreяк у rc.

Можливо, для повноти слід згадати масиви zsh's $pipestatusі bash', $PIPESTATUSякі містять статус виходу компонентів останнього трубопроводу.

А також для повноти, якщо мова йде про функції оболонки та джерела файлів, за замовчуванням функції повертаються зі статусом виходу останнього запуску команди, але також можуть встановлювати статус повернення явно за допомогою returnвбудованого. І тут ми бачимо деякі відмінності:

  • bashі mksh(оскільки R41, регресія ^ Wchange, очевидно, введена навмисно ), скоротить число (позитивне чи негативне) до 8 біт. Так, наприклад , return 1234буде встановлений $?в 210, return -- -1буде встановлено $?255.
  • zshі pdksh(та похідні, крім mksh) дозволяють будь-якому підписаному 32-бітовому десятковому цілому числу (-2 31 до 2 31 -1) (і усікають число до 32 біт ).
  • ashі yashдозволити будь-яке додатне ціле число від 0 до 2 31 -1 і повернути помилку для будь-якого числа із цього.
  • ksh93для , return 0щоб return 320встановити , $?як є, але для чого - небудь ще, вкоротити до 8 біт. Остерігайтеся, як уже згадувалося, що повернення числа між 256 і 320 може призвести kshдо вбивства при виході.
  • rcі esдозволяти повертати що-небудь навіть списки.

Також зауважте, що деякі оболонки також використовують спеціальні значення $?/, $statusщоб повідомити про деякі умови помилок, які не є статусом виходу з процесу, наприклад, 127або 126для команди, яку не знайдено або не виконується (або синтаксична помилка у файлі, що розміщується) ...


1
an exit code to their parentі to get the *status* of their child. ви додали акцент на "статус". Це exit codeі *status*те саме? Справа так, яке походження мати два імені? Справа не однакова, ви могли б дати визначення / посилання на статус?
n611x007

2
Тут є 3 номери. Код виходу : номер, переданий до exit(). Статус виходу : число, отримане за допомогою waitpid()якого включає код виходу, номер сигналу та чи було скинуто ядро. І число, яке деякі оболонки роблять доступним в одній зі своїх спеціальних змінних ( $?, $status), що є перетворенням статусу виходу таким чином, що має містити код виходу, якщо було нормальне припинення, але також несе інформацію про сигнал, якщо процес був убитий (цей також загалом називають статусом виходу ). Це все пояснено у моїй відповіді.
Стефан Шазелас

1
Я бачу дякую! Я безумовно вдячний цій явній ноті відзнаки тут. Ці вирази щодо виходу в деяких місцях використовуються так взаємозамінно, що варто це зробити. Чи має варіант змінної оболонки навіть загальну назву? Тому я б запропонував очистити його чітко, перш ніж розбиратися в деталях про оболонках. Я б запропонував вставити пояснення (з вашого коментаря) після першого чи другого абзацу.
n611x007

1
Чи можете ви вказати на цитату POSIX, яка говорить про те, що перші 7 біт є сигналом? Все, що я міг знайти, - це > 128частина: "Стан виходу команди, яка припинилася, оскільки вона отримала сигнал, повідомляється як більша ніж 128". pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

1
@cuonglm, я не думаю, що він доступний публічно в будь-якому іншому місці через HTTP, ви все одно можете отримати його від gmane через NNTP. Шукайте ідентифікатор повідомлення efe764d811849b34eef24bfb14106f61@austingroupbugs.net(від 06.05.2015) абоXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas

23

Коли процес закінчується, він повертає цілому значенню операційній системі. У більшості варіантів unix це значення приймається за модулем 256: все, крім бітів низького порядку, ігнорується. Статус дочірнього процесу повертається його батькові через 16-бітове ціле число, в якому

  • біти 0–6 (7 бітів низького порядку) - номер сигналу, який використовувався для вбивства процесу, або 0, якщо процес нормально вийшов;
  • біт 7 встановлюється, якщо процес був убитий сигналом та скинутим ядром;
  • біти 8–15 - код виходу процесу, якщо процес вийшов нормально, або 0, якщо процес був убитий сигналом.

Статус повертається waitсистемним викликом або одним із братів та сестер. POSIX не визначає точне кодування стану виходу та номера сигналу; він лише забезпечує

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

Строго кажучи, немає коду виходу, коли процес вбивається сигналом: натомість існує статус виходу .

У скрипті оболонки стан виходу команди повідомляється через спеціальну змінну $?. Ця змінна кодує статус виходу неоднозначно:

  • Якщо процес вийшов нормально, то $?це його вихідний статус.
  • Якщо процес був убитий сигналом, $?це 128 плюс число сигналу в більшості систем. POSIX має лише мандати, що $?перевищує 128 в цьому випадку; ksh93 додає 256 замість 128. Я ніколи не бачив unix-варіант, який би робив щось інше, ніж додавати константу до числа сигналу.

Таким чином, у скрипті оболонки ви не можете остаточно сказати, чи була команда вбита сигналом чи вийшла з кодом статусу більше 128, за винятком ksh93. Дуже рідко програми виходять із кодами статусу понад 128, частково тому, що програмісти уникають цього через $?неоднозначність.

SIGINT є сигналом 2 для більшості варіантів unix, таким чином, $?це 128 + 2 = 130 для процесу, який був убитий SIGINT. Ви побачите 129 для SIGHUP, 137 для SIGKILL тощо.


Набагато краще і чіткіше, ніж моє, навіть якщо воно по суті говорить одне і те ж. Ви можете уточнити, що $?це лише для снарядів Борна. Дивіться також yashпро іншу (але все ж POSIX) поведінку. Також відповідно до POSIX + XSI (Unix), a kill -2 "$pid"відправить SIGINT в процес, але фактичне число сигналу може бути не 2, так що $? не обов'язково буде 128 + 2 (або 256 + 2 або 384 + 2), хоча kill -l "$?"повернеться INT, тому я б порадив для переносимості не посилатися на самі числа.
Стефан Шазелас

8

Це залежить від вашої оболонки. На bash(1)головній сторінці, розділ SHELL GRAMMAR , підрозділ Прості команди :

Повернене значення простої команди становить [...] 128+ n, якщо команда припиняється сигналом n .

Оскільки SIGINTу вашій системі є сигнал №2, повернене значення 130, коли воно виконується під Bash.


1
Як у світі ви це знаходите, чи навіть знаєте, де шукати? Я кланяюся перед вашим генієм.
Cory Klein

1
@CoryKlein: Досвід, здебільшого. О, і ви, ймовірно, захочете і signal(7)сторінку чоловіка.
Ігнасіо Васкес-Абрамс

круті речі; чи знаєте ви, чи випадково я включаю файли в C із цими константами? +1
Rui F Ribeiro

@CoryKlein Чому ти не вибрав це як правильну відповідь?
Rui F Ribeiro

3

Здається, це правильне місце, щоб згадати, що SVr4 представив waitid () у 1989 році, але, здається, жодна важлива програма не використовує його до цих пір. waitid () дозволяє отримати повні 32 біти з коду exit ().

Близько 2 місяців тому я переписав частину контролю за роботою на очікування / роботу Борнської оболонки, щоб використовувати waitid () замість waitpid (). Це було зроблено для того, щоб зняти обмеження, яке маскує вихідний код з 0xFF.

Інтерфейс waitid () набагато чистіший, ніж попередні реалізації wait (), за винятком дзвінка cwait () від UNOS з 1980 року.

Можливо, вам буде цікаво прочитати сторінку чоловіка за адресою:

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

і перевірте розділ "Заміна параметрів", який зараз знаходиться на сторінці 8.

Для інтерфейсу waitid () були введені нові змінні .sh. *. Цей інтерфейс більше не має неоднозначних значень для чисел, відомих за $? і зробити спілкування набагато простіше.

Зауважте, що для використання цієї функції вам потрібно мати POSIX-сумісний сервіс waitid (), тому Mac OS X і Linux в даний час не пропонують цього, але в якості виклику waitpid (), тобто в не на POSIX-платформі, ви все одно отримаєте лише 8 біт від вихідного коду.

Якщо коротко: .sh.status - числовий код виходу, .sh.code - числова причина виходу.

Для кращої портативності є: .sh.codename для текстової версії причини виходу, наприклад, "DUMPED" та .sh.termsig, окреме ім'я для сигналу, який припинив процес.

Для кращого використання є два значення .sh.codename, що не стосуються виходу: "NOEXEC" та "NOTFOUND", які використовуються, коли програма взагалі не може бути запущена.

FreeBSD виправив свою помилку keridnel keridnel протягом 20 годин після мого звіту, Linux ще не почав їх виправляти. Я сподіваюся, що через 26 років після впровадження цієї функції, яка є в POSIX зараз, усі ОС скоро її підтримають.


Відповідна відповідь - unix.stackexchange.com/a/453432/5132 .
JdeBP
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.