Фоновые и приостановленные процессы. Реализация оболочки управления заданиями в C

Я внедряю оболочку Job Control в C в Linux как проект по теме, связанной с операционными системами. У меня есть функция main(), которая выполняет управление дочерними процессами, помогая с помощью связанного списка, как показано здесь, в котором хранится информация фоновых и приостановленных заданий:

typedef struct job_
{
    pid_t pgid; /* group id = process lider id */
    char * command; /* program name */
    enum job_state state;
    struct job_ *next; /* next job in the list */
} job;

Каждый раз, когда дочерний процесс завершается или останавливается, SIGCHLD отправляется родительскому процессу, чтобы получить информацию об этом. Затем, как показано здесь, у меня есть обработчик сигнала, который для каждого узла этого связанного списка состояний заданий проверяет, завершился ли процесс, представленный в этом узле, и если он это сделал, этот узел был удален из связанного списка. Вот код для обработчика SIGCHLD, где "job_list" - это связанный список, в котором хранится информация:

void mySIGCHLD_Handler(int signum) {
    block_SIGCHLD();
    if (signum == 17) {
        job *current_node = job_list->next, *node_to_delete = NULL;
        int process_status, process_id_deleted;

        while (current_node) {

            /* Wait for a child process to finish.
            *    - WNOHANG: return immediately if the process has not exited
            */
            waitpid(current_node->pgid, &process_status, WNOHANG);

            if (WIFEXITED(process_status) != 0) {
                node_to_delete = current_node;
                current_node = current_node->next;
                process_id_deleted = node_to_delete->pgid;
                if (delete_job(job_list, node_to_delete)) {
                printf("Process #%d deleted from job list\n", process_id_deleted);
                } else {
                    printf("Process #%d could not be deleted from job list\n", process_id_deleted);
                }
            } else {
                current_node = current_node->next;
            }
        }
    }
    unblock_SIGCHLD();
}

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

Спасибо и извините за потерянное время:(

1 ответ

Решение

Я вижу много проблем в этом коде, но ближайшая проблема, вероятно, здесь:

        waitpid(current_node->pgid, &process_status, WNOHANG);
        if (WIFEXITED(process_status) != 0) {

когда waitpid(pid, &status, WNOHANG) возвращается, потому что процесс не завершился, он ничего не записывает в statusтак что последующее if разветвляется на мусор. Вам необходимо проверить фактическое возвращаемое значение waitpid прежде чем предположить status имеет смысл.

Наиболее важные другие проблемы:

  • Ядру разрешено отправлять только одно SIGCHLD сказать вам, что несколько процессов вышли. Когда вы получаете SIGCHLDнужно позвонить waitpid(0, &status, WNOHANG) в цикле, пока он не скажет вам, что больше нет процессов, ожидающих, и вам нужно обработать (без каламбура) все идентификаторы вышедших процессов, о которых он говорит вам.

  • Не безопасно звонить printf или же free из асинхронного обработчика сигнала. Вместо этого добавьте завершенные процессы в список отложенных задач. Обязательно заблокируйте SIGCHLD в коде основного цикла, который использует этот список.

  • Не блокируйте и не разблокируйте SIGCHLD себя в обработчике; это неизбежное состояние гонки. Вместо этого, пусть ядро ​​сделает это за вас, атомарно, правильно настроив ваш обработчик сигнала: use sigaction и не кладите SA_NODEFER в sa_flags, (Сделать ставить SA_RESTART в sa_flags, если у вас нет очень веских причин не делать этого.)

  • Буквенное число 17 должно быть постоянной сигнала SIGCHLD вместо. Некоторые номера сигналов были стабильными во всех Unix-системах на протяжении истории, но SIGCHLD не один из них.

Другие вопросы по тегам