Функция waitpid() возвращает ОШИБКУ (-1), почему?

Я пишу программу, похожую на оболочку Linux, на C.

Среди прочего, я реализую две встроенные команды: jobs, history. В jobs я печатаю список работающих в данный момент команд (в фоновом режиме). В history я печатаю список всей истории команд до сих пор, указывая для каждой команды, ВЫПОЛНЯЕТСЯ она или ВЫПОЛНЕНА.

Чтобы реализовать их, моя идея состояла в том, чтобы иметь список команд, сопоставляя имя команды с их PID. Как только вызывается команда jobs/history, я просматриваю их, проверяю, какие из них выполняются или выполняются, и соответствующим образом печатаю.

Я читал в Интернете, что функция: waitpid(pid, &status, WNOHANG) может определить по PID, выполняется ли процесс или завершен, без остановки процесса. Работает хорошо, кроме вот этого:

Когда программа жива, функция возвращает ее. Когда программа завершена, первый раз, когда я вызываю ее, возвращается значение done, а затем при повторном вызове с тем же PID она возвращает -1 (ОШИБКА).

Например, это будет выглядеть так: (команда & символизирует фон)

$ sleep 3 &
$ jobs
sleep ALIVE 
$ jobs  (withing the 3 seconds)
sleep ALIVE
$ jobs (after 3 seconds)
sleep DONE
$ jobs 
sleep ERROR
$ jobs 
sleep ERROR
....

Кроме того, на них не влияют другие вызовы команд, которые я мог бы выполнить до или после, кажется, что описанное выше поведение не зависит от других команд.

Я прочитал в Интернете различные причины, по которым waitpid может возвращать -1, но я не смог определить причину в моем случае. Так же пытался искать как понять что это за ошибка waitpid, но опять безуспешно.

Мои вопросы:

  1. Как вы думаете, почему такое поведение происходит
  2. Если у вас есть решение (в идеале, чтобы оно продолжало возвращать DONE)
  3. Если у вас есть лучшее представление о том, как реализовать команду jobs/history,

Одним из решений этой проблемы является то, что как только я получаю DONE, я подписываю команду как DONE и больше не выполняю waitid перед ее печатью. Это решило бы проблему, но я бы остался в неведении относительно того, ПОЧЕМУ это происходит.


person spano    schedule 28.03.2021    source источник
comment
Я запутался из-за смешивания C и оболочки.   -  person Joshua    schedule 28.03.2021
comment
Вы проверили ошибку?   -  person anton-tchekov    schedule 28.03.2021
comment
@at77 Спасибо за совет, получаю: нет дочерних процессов   -  person spano    schedule 28.03.2021
comment
На справочной странице сказано: Функция waitpid() завершится ошибкой, если: ECHILD Процесс, указанный pid, не существует или не является дочерним по отношению к вызывающему процессу, или группа процессов, указанная pid, не существует или не имеет какого-либо процесса-члена. это дочерний процесс вызывающего процесса. Я думаю, что происходит то, что когда программа выполняется и ее статус запрашивается, она удаляется из списка дочерних процессов.   -  person anton-tchekov    schedule 28.03.2021
comment
Почему см. этот ответ: unix.stackexchange.com/a/414974/424512   -  person anton-tchekov    schedule 28.03.2021
comment
Спасибо, я смог решить проблему, никогда больше не проверяя waitpid() после того, как он вернет DONE для какого-то процесса (и примет его как выполненный).   -  person spano    schedule 28.03.2021


Ответы (1)


Вам следует ознакомиться с тем, как обрабатываются дочерние процессы в средах Unix. В частности, прочитайте об процессах-зомби.

Когда процесс умирает, он переходит в состояние «зомби», так что его PID по-прежнему зарезервирован и однозначно идентифицирует уже мертвый процесс. Успешный wait в зомби-процессе освобождает дескриптор процесса и его PID. Следовательно, последующие вызовы wait с тем же PID завершатся неудачно, потому что больше нет процессов с этим PID (если только новому процессу не будет выделен тот же PID, и в этом случае ожидание его будет логической ошибкой).

Вы должны реструктурировать свою программу так, чтобы в случае успешного выполнения wait и сообщения о том, что процесс является DONE, вы записывали эту информацию в свою собственную структуру данных и никогда больше не вызывали wait для этого PID.

Для сравнения, как только процесс завершен, оболочка bourne сообщает об этом в последний раз, а затем удаляет его из списка заданий:

$ sleep 10 &
$ jobs
[1] + Running                 sleep 10
$ jobs
[1] + Running                 sleep 10
$ jobs
[1]   Done                    sleep 10
$ jobs
$
person Yakov Galka    schedule 28.03.2021
comment
Большое спасибо! На самом деле это то, что я понял в Интернете, но хотел убедиться в этом. Мое решение теперь состоит в том, чтобы добавить к структуре задания и идентификатору, который представляет 1, если он запущен, и 0, если он выполнен. Я проверяю waitpid() только для запущенных элементов. Если waitpid() возвращает DONE, я меняю идентификатор на 0 и больше никогда не проверяю. - person spano; 28.03.2021
comment
@spano: это хороший способ сделать это. Вам также нужно подумать, когда вы освобождаете свои структуры работы зомби :) - person Yakov Galka; 28.03.2021