Подтвердить что ты не робот

Что может привести к сбою exec? Что будет дальше?

Каковы причины, по которым может произойти сбой exec (execl, execlp и т.д.)? Если вы выполняете вызов exec, и он возвращается, есть ли какие-либо передовые методы, кроме просто паники и вызова выхода?

4b9b3361

Ответ 1

На странице exec(3):

Функции execl(), execle(), execlp(), execvp() и execvp() могут выйти из строя и установить errno для любой из ошибок, указанных для библиотечных функций execve(2) и malloc(3).

Функция execv() может выйти из строя и установить errno для любой из ошибок, указанных для библиотечной функции execve(2).

И затем из execve(2) справочная страница:

ОШИБКИ

Execve() завершится с ошибкой и вернется к вызывающему процессу, если:

  • [E2BIG] - количество байтов в списке аргументов нового процесса больше, чем системный предел. Этот предел задается переменной sysctl(3) MIB KERN_ARGMAX.
  • [EACCES] - разрешение поиска отклонено для компонента префикса пути.
  • [EACCES] - Новый файл процесса не является обычным файлом.
  • [EACCES] - Новый режим файла процесса отказывает в разрешении на выполнение.
  • [EACCES] - Новый файл процесса находится на файловой системе, установленной с отключенным исполнением (MNT_NOEXEC в <sys/mount.h>).
  • [EFAULT] - Новый файл процесса не так длинный, как указано значениями размера в его заголовке.
  • [EFAULT] - Path, argv или envp указывают на незаконный адрес.
  • [EIO] - При чтении из файловой системы произошла ошибка ввода-вывода.
  • [ELOOP] - Слишком много символических ссылок было встречено при переводе пути. Это считается показателем циклической символической ссылки.
  • [ENAMETOOLONG] - Компонент пути превысил символы {NAME_MAX} или полное имя пути превысило {PATH_MAX}.
  • [ENOENT] - Новый файл процесса не существует.
  • [ENOEXEC] - Новый файл процесса имеет соответствующее разрешение доступа, но имеет нераспознанный формат (например, недопустимое магическое число в его заголовке).
  • [ENOMEM] - для нового процесса требуется больше виртуальной памяти, чем допускается наложенным максимумом (getrlimit(2)).
  • [ENOTDIR] - Компонент префикса пути не является каталогом.
  • [ETXTBSY] - новый файл процесса представляет собой файл чистой процедуры (общий текст), который в настоящее время открыт для записи или чтения некоторым процессом.

malloc() намного менее сложна и использует только ENOMEM. Из malloc(3) man page:

В случае успеха функции calloc(), malloc(), realloc(), reallocf() и valloc() возвращают указатель на выделенную память. Если есть ошибка, они возвращают указатель NULL и устанавливают errno на ENOMEM.

Ответ 2

Проблема с обработкой ошибки exec заключается в том, что обычно exec выполняется в дочернем процессе, и вы хотите выполнять обработку ошибок в родительском процессе. Но вы не можете просто exit(errno), потому что (1) вы не знаете, соответствуют ли коды ошибок в коде выхода, и (2) вы не можете отличить отказ от exec и коды выхода отказа от нового программа exec.

Лучшее решение, которое я знаю, это использование труб для сообщения об успешном завершении или неудаче exec:

  • Перед форсированием откройте канал в родительском процессе.
  • После форматирования родительский элемент закрывает конец письма и читает его с конца чтения.
  • Ребенок закрывает конец чтения и устанавливает флаг закрытия для exec для конца записи.
  • Ребенок вызывает exec.
  • Если exec не работает, дочерний элемент возвращает код ошибки обратно родительскому каналу, а затем завершает работу.
  • Родитель читает eof (чтение с нулевой длиной), если ребенок успешно выполнил exec, так как close-on-exec сделал успешным exec закрыть конец записи в трубе. Или, если exec не удалось, родитель считывает код ошибки и может действовать соответствующим образом. В любом случае родительские блоки до тех пор, пока ребенок не назовет exec.
  • Родитель закрывает считывающий конец канала.

Ответ 3

То, что вы делаете после возврата вызова exec(), зависит от контекста - от того, что должна делать программа, от какой ошибки и от того, что вы могли бы сделать для решения этой проблемы.

Одним из источников проблем может быть то, что вы указали простое имя программы вместо имени пути; возможно, вы можете повторить попытку с помощью execvp() или преобразовать команду в вызов sh -c 'what you originally specified'. Независимо от того, разумно ли это, зависит от приложения. Если есть серьезные проблемы с безопасностью, возможно, вы не попробуете еще раз.

Если вы указали путь и есть проблема с этим (ENOTDIR, ENOENT, EPERM), тогда у вас может не быть разумного резерва, но вы можете сообщить об ошибке осмысленно.

В старые времена (еще 10 лет назад) некоторые системы не поддерживали '#!' shebang notation, и если вы не были уверены, выполняете ли вы исполняемый файл или оболочку script, вы попробовали его как исполняемый файл, а затем повторили его как оболочку script. Это может работать или не работать, если вы используете Perl script, но в те дни вы написали свои сценарии Perl, чтобы обнаружить, что они запускаются оболочкой и повторно запускаются с Perl. К счастью, эти дни в основном закончились.

В максимально возможной степени важно обеспечить, чтобы процесс сообщал о проблеме, чтобы ее можно было проследить - записать ее сообщение в файл журнала или только в stderr (или, возможно, даже syslog()), так что те, кто должен решить, что пошло не так, имеют больше информации, чтобы помочь им, кроме несчастного отчета конечного пользователя "Я попробовал X, и он не работал". Очень важно, что если ничего не работает, то статус выхода не равен 0, так как это указывает на успех. Даже это может быть проигнорировано, но вы сделали то, что могли.

Ответ 4

Нежели паника, вы можете принять решение, основанное на значении errno.

Ответ 5

Exec всегда должен быть успешным. (за исключением оболочек, то есть если пользователь ввел фиктивную команду)

Если exec не работает, он указывает:

  • "ошибка" с программой (отсутствующий или плохой компонент, неправильный путь, плохая память,...) или
  • серьезная системная ошибка (из памяти, слишком много процессов, ошибка диска,...)

Для любой серьезной ошибки нормальный подход заключается в том, чтобы написать сообщение об ошибке на stderr, а затем выйти с кодом отказа. Практически все стандартные инструменты делают это. Для exec:

execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);

Оболочка тоже делает это (более или менее).

Обычно, если дочерний процесс выходит из строя, родитель тоже не сработал и должен выйти. Неважно, был ли ребенок неудачным в exec или во время запуска программы. Если exec завершился с ошибкой, не имеет значения, почему exec завершился с ошибкой. Если дочерний процесс завершился неудачно по какой-либо причине, вызывающий процесс находится в беде и должен остановиться.

Не тратьте много времени, пытаясь предвидеть все возможные ошибки. Не пишите код, который пытается обработать каждый код ошибки наилучшим образом. Вы просто раздуваете код и вводите много новых ошибок. Если ваша программа нарушена или она подвергается насилию, она просто должна потерпеть неудачу. Если вы заставите его продолжать, из-за этого будет еще хуже.

Например, если система потеряла память и обменивает своп, мы не хотим снова и снова запускать цикл, чтобы запустить процесс; это только ухудшит ситуацию. Если мы получим ошибку файловой системы, мы не хотим продолжать работу в этой файловой системе; это может ухудшить коррупцию. Если программа была установлена ​​неправильно или имеет ошибку, или имеет повреждение памяти, мы хотим как можно скорее остановиться, прежде чем эта сломанная программа действительно наносит какой-либо реальный ущерб (например, отправляет поврежденный отчет клиенту, удаляя базу данных...).

Одна из возможных альтернатив: неудачный процесс может вызвать помощь, приостановить сам (SIGSTOP), а затем повторить операцию, если будет сказано продолжить. Это может помочь, когда система не работает, или диски заполнены, или, возможно, даже в случае ошибки в программе. Немногие операции настолько дороги и важны, что это будет стоить.

Если вы создаете интерактивную графическую программу, попробуйте сделать ее как тонкую оболочку над многократно используемыми инструментами командной строки (которые выходят, если что-то пойдет не так). Каждая функция в вашей программе должна быть доступна через графический интерфейс, через командную строку и как вызов функции. Напишите свои функции. Напишите несколько инструментов для создания обмоток commmand-line и GUI для любой функции. Также используйте подпроцессы.

Если вы создаете действительно критическую систему, например, контроллер для атомной электростанции или программу для прогнозирования цунами, то что вы делаете, читая мои немые советы? Критические системы не должны полностью зависеть от компьютеров или программного обеспечения. Там должно быть "ручное переопределение", с кем-то, чтобы управлять им. В частности, не пытайтесь построить критическую систему на MS Windows, это похоже на строительство песчаных замков под водой.