Код завершения по умолчанию, когда процесс завершается?


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 и большинстве Unix, если процесс завершается нормально, биты 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номер сигнала,

  • в золе, зш, пдкш, баш, скорлупа Борна, $?есть 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's', $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/…
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件

1
@cuonglm, я не думаю, что это общедоступно где-либо еще через HTTP, вы все равно можете получить его от gmane через NNTP. Ищите идентификатор сообщения efe764d811849b34eef24bfb14106f61@austingroupbugs.net(от 2015-05-06) илиXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Стефан Шазелас

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 и т. Д.


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

8

Это зависит от вашей оболочки. На bash(1)странице man раздела SHELL GRAMMAR , подраздел Simple Commands :

Возвращаемое значение простой команды - [...] 128+ n, если команда заканчивается сигналом n .

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


1
Как в мире вы найдете это, или даже знаете, где искать? Я преклоняюсь перед твоим гением.
Кори Кляйн

1
@CoryKlein: опыт, в основном. О, и вы, вероятно, также захотите signal(7)страницу руководства .
Игнасио Васкес-Абрамс

крутая вещь; Вы знаете, случайно ли у меня есть включаемые файлы в C с этими константами? +1
Руи Ф. Рибейро

@CoryKlein Почему вы не выбрали это как правильный ответ?
Руи Ф. Рибейро

3

Кажется, стоит упомянуть, что SVr4 ввел waitid () в 1989 году, но пока ни одна важная программа не использует его. waitid () позволяет получить полные 32 бита из кода выхода ().

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

Интерфейс waitid () намного чище, чем предыдущие реализации wait (), за исключением вызова cwait () из UNOS 1980 года.

Вам может быть интересно прочитать справочную страницу по адресу:

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

и проверьте раздел «Подстановка параметров», который в данный момент начинается на стр. 8.

Новые переменные .sh. * Были введены для интерфейса waitid (). Этот интерфейс больше не имеет неоднозначных значений для чисел, известных как $? и сделать взаимодействие намного проще.

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

Вкратце: .sh.status - это числовой код выхода, .sh.code - это числовая причина выхода.

Для лучшей переносимости, есть: .sh.codename для текстовой версии причины выхода, например, «DUMPED» и .sh.termsig, имя сингальса для сигнала, который завершил процесс.

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

FreeBSD устранила ошибку ядра waitid () в течение 20 часов после моего отчета, Linux еще не запустила их исправление. Я надеюсь, что через 26 лет после введения этой функции, которая сейчас есть в POSIX, все ОС скоро ее поддержат.


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