Time продолжительность программы, вызываемой execv

Я делаю программу C, которая использует fork и execv для параллельного запуска других программ.

Кажется, я не могу рассчитать продолжительность выполнения программы, вызываемой execv, поскольку новый процесс умирает сразу после завершения этой программы. Другая сложность заключается в невозможности использовать родительский процесс для ожидания завершения дочернего процесса (я использую waitpid), потому что мне нужно, чтобы родительский процесс выполнял какую-то другую работу, а не ждал завершения дочернего процесса.

Итак, мой вопрос: есть ли способ измерить продолжительность вызова execv без использования вспомогательной вилки, pthread или текстового файла?

заранее спасибо


person Saucy Goat    schedule 29.09.2018    source источник
comment
Вы можете взять отметку времени в начале main и зарегистрировать обработчик с atexit. Обработчик может взять вторую метку времени и вычислить разницу. Конечно, если код всегда доходит до конца main, то можно просто взять вторую отметку времени там.   -  person user3386109    schedule 29.09.2018
comment
@ user3386109 Спасибо за ответ. Так вот, я не вижу, чтобы это работало; если бы я создал эти временные метки в выполняемой программе, родительский процесс все равно не имел бы к ним доступа. Или я что-то пропустил?   -  person Saucy Goat    schedule 29.09.2018
comment
Если вы не можете модифицировать программу, которую вы вызываете для измерения времени (и вы, вероятно, не должны, даже если бы могли), то вы застряли без использования какой-либо вспомогательной операции fork/exec. Надежные способы включают в себя родительский процесс, управляющий синхронизацией во время выполнения дочернего процесса. Если ваш непосредственный родительский процесс не может посвятить себя синхронизации, вам нужно создать другой процесс, который может посвятить себя синхронизации, что означает, что вам понадобится дополнительный цикл fork/exec. Вот и все — без лишнего процесса (fork/exec) не обойтись.   -  person Jonathan Leffler    schedule 30.09.2018
comment
Хм, вы используете возвращенный статус для чего-нибудь? Статус, возвращаемый waitpid, может использоваться для сообщения о разнице во времени. Конечно, вам нужно быть осторожным с кодировкой, так как у вас есть только 8 бит для игры.   -  person user3386109    schedule 30.09.2018
comment
@JonathanLeffler Спасибо за ответ. Я думаю, не все сообщения StackOverflow имеют счастливый конец :) Ура!   -  person Saucy Goat    schedule 30.09.2018
comment
@user3386109 user3386109 Сейчас я. Но я могу найти способ не нуждаться в этом. Что касается последней части; ну, я нуб C. Не могли бы вы рассказать мне, как мне использовать статус возврата и ту кодировку, о которой вы упомянули, или указать мне место, где я мог бы прочитать об этом?   -  person Saucy Goat    schedule 30.09.2018
comment
Самый простой подход — выполнить вариант на /usr/bin/time your-command arg1 arg2 …, но команда time сама разветвится и выполнит команду для вас. Вы можете написать свой собственный код для выполнения этой работы, возможно, сообщая время в микросекундах или даже наносекундах (хотя точность таких времен может быть немного спорной). Но вы правы, иногда нет положительного ответа на вопросы, заданные на SO; иногда люди просят о невозможном, и тогда лучшее, что можно сказать, это сожаление; вы не можете сделать это, как вы хотели бы.   -  person Jonathan Leffler    schedule 30.09.2018
comment
Когда вы вызываете exit() с кодом возврата или просто return из main с кодом возврата, это значение становится доступным для родителя через аргумент stat_loc для waitpid. Таким образом, начать следует с справочной страницы для waitpid, особенно с той части, где обсуждается WEXITSTATUS.   -  person user3386109    schedule 30.09.2018
comment
Что касается кодирования разницы во времени, наилучшее кодирование зависит от того, сколько времени вы ожидаете от программы. Например, если программа занимает менее четверти секунды, то время может быть просто числом от 0 до 250 в миллисекундах.   -  person user3386109    schedule 30.09.2018
comment
@JonathanLeffler Я должен еще раз поблагодарить вас за ответ. Хотя добиться того, что я считал возможным, не удалось, ваши ответы были поучительными и очень полезными.   -  person Saucy Goat    schedule 30.09.2018
comment
@user3386109 user3386109 Итак, у меня есть все, кроме части фактического кодирования. Могу ли я сохранить время в чем-то вроде беззнакового символа?   -  person Saucy Goat    schedule 30.09.2018
comment
Интуитивное чувство: использование статуса выхода не будет работать так хорошо. В традиционных системах Unix вы можете записывать только статусы выхода в диапазоне 0..255. Он также полагается на то, что процесс сообщает о затраченном времени в статусе выхода. Это не совсем невозможно; Я просто не уверен, что это надежно или точно (особенно если вам нужна синхронизация менее секунды).   -  person Jonathan Leffler    schedule 30.09.2018
comment
Я согласен с этим. Кроме того, ваша оболочка действительно хочет знать, каким был статус возврата дочернего элемента; в противном случае вы не сможете сообщить пользователю о сбое процессов.   -  person rici    schedule 30.09.2018
comment
@JonathanLeffler и rici, вы настоящие рыцари в блестящих доспехах, когда дело доходит до C. Спасибо вам обоим за то, что вы рядом. Парадигма побеждена, спасибо! user3386109 хотя на мой первоначальный вопрос был дан ответ, я очень ценю ваши ответы, и идея, которую вы представили, была очень творческой, если не сказать больше. Я тоже попробую после этой работы. Ваше здоровье!   -  person Saucy Goat    schedule 30.09.2018


Ответы (1)


Ваш родительский процесс знает, когда он выполнил системный вызов fork(). Это не совсем тот момент, когда запускается процесс execv, так как системный вызов execv() занимает некоторое время, но включать это время в подсчет вполне разумно. Если вы принимаете это ограничение, вы можете просто записать время начала как время, когда вы вызвали fork().

Когда дочерний процесс завершится, родитель получит сигнал SIGCHLD. Действие по умолчанию для SIGCHLD — игнорировать его, но вы, вероятно, все равно захотите это изменить. Если вы прикрепите обработчик сигнала к SIGCHLD, то в этом обработчике сигнала вы можете вызывать waitpid (с опцией WNOHANG) до тех пор, пока не получите все уведомления о завершении дочернего процесса. Для каждого уведомления вы записываете время уведомления как время окончания процесса. (Опять же, если система находится под большой нагрузкой, сигнал может отставать от терминации, что приведет к неточности измерения времени. Но в большинстве случаев оно будет точным.)

Очевидно, что родитель должен отслеживать более одного дочернего процесса. Поэтому вам нужно будет использовать дочерний PID для индексации этих значений.

Теперь у вас есть время начала и время окончания для каждого дочернего процесса.

Однако есть небольшая проблема. Вы не можете привязать время начала к PID дочернего процесса, пока вызов fork() не вернется к родительскому процессу. Но вполне возможно, что вызов fork() вернется к дочернему процессу, и что дочерний процесс вызовет execv(), и процесс execv() завершится до того, как вызов fork() вернется к родительскому. (Честно. Бывает.)

Таким образом, обработчик SIGCHLD может получить уведомление о завершении процесса, время начала которого еще не записано.

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

Таким образом, код будет выглядеть примерно так:

1. Allocate storage for a new process times table entry
   (PID / start time / end time / status result). Set all
   fields to 0 to indicate that the entry is available.
2. Recall the current time as start_time (a local variable,
   not the table entry).
3. Fork()
4. (Still in the parent). Using an atomic compare-and-swap
   (or equivalent), set the PID of the table entry created
   in step 1 to the child's PID. If the entry was 0 (and is
   now the PID) or if the entry was already the PID, then
   continue to step 6.
5. If the entry has some other non-zero PID, find an empty entry
   in the table and return to step 4.
6. Now record the start time in the table entry. If the table entry
   already has an end time recorded, then the signal handler already
   ran and you know how long it took and what its return status is.
   (This is the case where the child terminated before you got to
   step 4.) You can now report this information.

В обработчике сигнала SIGCHLD вам нужно сделать что-то вроде этого:

For each successful call to waitpid():
1. Find the entry in the child process information table whose PID
   corresponds to the PID returned by waitpid(). If you find one,
   skip to step 4.
2. Find an empty entry in the child process information table.
   Note that the signal handler cannot be interrupted by the main
   program, so locking is not required here.
3. Claim that entry by setting its PID field to the PID returned by
   waitpid() above.
4. Now that you have an entry, record the end time and return status
   information in the table entry. If the table entry existed
   previously, you need to put the entry on a notification queue
   so that the main process can notify the user. (You cannot call
   printf in a signal handler either.) If the table entry didn't
   exist before, then the main process will notice by itself.

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

Кроме того, если вы не делали ничего из этого раньше, вам захочется немного почитать :-)

  • waitpid(). Обратите особое внимание на макросы, используемые для извлечения информации о состоянии.

  • sigaction(). Как назначить функцию-обработчик сигналу. Если это все еще кажется вам греческим, начните с signal(7) или соответствующую главу в вашем учебнике по программированию Unix.

  • Условия гонки (из Википедии)

  • Сравнить и поменять местами (в Википедии). (Не используйте их пример кода, он не работает. В GCC есть встроенное расширение, реализующее атомарное сравнение и переключитесь на любую архитектуру, которая поддерживает ее. Я знаю, что этот раздел помечен как устаревший, и вам следует использовать более сложные функции в следующем разделе __atomic, но в данном случае значения по умолчанию подходят. Но если вы используете __atomic_compare_exchange_n, слава.)

person rici    schedule 29.09.2018
comment
Вполне начитанное. После (я думаю) понимания вашего решения я придумал собственное решение. Если возможно, я хотел бы узнать ваше мнение об этой модели: Переменные: - массивы для хранения PID, времени начала и окончания и результата состояния. Все инициализируются с помощью mmap, чтобы быть доступными для всех процессов - глобальный счетчик общего количества процессов (с использованием mmap) Дочерний процесс: запись PID и время начала по индексу process_counter-1, увеличение общего счетчика процессов Обработчик: запись времени окончания и состояния на индекс process_counter-1 - person Saucy Goat; 30.09.2018
comment
@saucy: это ваш проект, поэтому вы должны делать это так, как хотите. Я уже сказал, как я это сделаю, и я думаю, что это лучший способ :-), потому что, несмотря на кажущуюся сложность, он не требует межпроцессной координации, а также не требует дополнительного форка. (Если я понимаю, что вы предлагаете, я не понимаю, как ваш обработчик сигналов может определить индекс процесса. Также учтите, что оболочки используются в течение длительного времени - в моем случае дни - поэтому счетчик процесса может не быть лучший идентификатор. - person rici; 30.09.2018
comment
Я попробую свою небольшую схему, а затем спрошу своего учителя, есть ли недостатки в межпроцессном взаимодействии, которые, вероятно, есть. Кроме того, идентификатор процесса должен хранить сам дочерний процесс, а не обработчик. Я постараюсь заставить все работать, используя мой план спагетти, и как только у меня будет более глубокое понимание того, что я делаю, я обязательно еще раз взгляну на то, что вы опубликовали. Спасибо :) - person Saucy Goat; 30.09.2018
comment
@saucy: если ребенок вводит данные, то сработает таблица результатов, хотя вам все равно нужно подумать, как поступать с долговечными оболочками. Однако есть только два способа добиться того, чтобы что-то произошло в конце выполнения дочернего процесса: изменить дочерний процесс (который не позволяет вам запускать стандартные утилиты) или сделать дополнительный форк и execv, что удвоит количество ваших процессов. Но это вполне может быть приемлемо в вашей среде. Получайте удовольствие! - person rici; 30.09.2018