Режим сна не работает в дочернем процессе после вызова функции popen

#c #signals #sleep #popen #sigchld

#c #сигналы #режим сна #popen #sigchld

Вопрос:

Я написал небольшую программу на C, которая создает дочерний процесс, а затем запускает команду оболочки с использованием popen. Я создал обработчик сигналов для ожидания завершения дочернего процесса, в то время как родительский процесс просто выполняется в бесконечном цикле. Когда я вызываю «sleep (5)» после системного вызова popen, вызов sleep не работает.

Вместо этого, если я помещаю вызов sleep перед popen, он работает. Кроме того, если я удалю обработчик сигнала и просто дождусь дочернего процесса в родительском процессе, тогда проблем не будет (т. Е. sleep выполняется правильно в любом месте).

 //Assume all required header files and function prototypes added

int main()
{
    int pid,status;
    signal(SIGCHLD,reaper);
    pid = fork();
    if(pid == 0)
       runCommand();
    while(1);
}

void runCommand()
{
    FILE *pipen;
    char *buffer;

    buffer = (char*) malloc(100);
/*sleep #1*/    sleep(5);
    if ((pipen = popen("uname -a", "r")) == NULL) {
        perror("nError during shell command execution: ");
        exit(0);
    }
/*sleep #2*/    sleep(5);
    fgets(buffer, 100, pipen);
/*sleep #3*/    sleep(5);
    printf("n%sn",buffer);
    exit(0);
}

void reaper(int signal)
{
    int status;
    waitpid(-1,amp;status,WNOHANG);
}

  

Когда я запускаю вышеупомянутую программу, sleep # 1 работает, и процесс переходит в спящий режим в течение заданного времени. Я узнаю об этом, наблюдая за временем, которое требуется для печати результата команды (с помощью инструкции printf).
Но для sleep # 2 и sleep # 3 я ожидаю, что процесс перейдет в режим сна, но, похоже, этого не происходит, поскольку результат команды мгновенно выводится на консоль.

Поведение можно наблюдать, сохраняя только вызов sleep # 2 или sleep # 3 и удаляя два других вызова sleep.

Кто-нибудь знает, почему это происходит?

Комментарии:

1. Как вы узнаете, какой режим сна игнорируется? Он печатает только один раз, после того, как все сделано. Если это занимает менее 15 секунд, вы не можете определить, какой из них был пропущен.

2. Я комментирую два вызова режима ожидания и сохраняю только по одному во время выполнения.

3. Лучшим способом было бы помещать printf() вызовы после каждого сна.

4. Когда я тестирую его, просто игнорируется sleep # 2.

5. Я добавил printfs перед каждым спящим режимом и, как вы предложили, и вы правы. Он игнорировал только sleep # 2. Но затем, когда я удалил sleep # 1 и sleep # 2, он проигнорировал sleep # 3.

Ответ №1:

popen() работает путем разветвления дочернего процесса для выполнения /bin/sh -c "uname -a" . Когда этот дочерний процесс завершается, процесс получает SIGCHLD сигнал. Это прерывает sleep() вызов и запускает вашу reaper функцию.

Я не смог найти упоминания об этом ни в одной документации Linux или POSIX, но это есть в документации SunOS:

Обработчик сигнала для SIGCHLD должен быть установлен по умолчанию при использовании popen() . Если процесс установил обработчик сигнала для SIGCHLD , он будет вызван при завершении команды. Если обработчик сигнала или другой поток в том же процессе выполнит wait(2) вызов, это повлияет на возвращаемое значение pclose() . Если обработчик сигнала процесса для SIGCHLD был настроен на игнорирование сигнала, pclose() произойдет сбой, и для errno будет установлено значение ECHILD .

Вы должны вернуть SIGCHLD обработчику значение по умолчанию в дочернем процессе.

 void runCommand()
{
    FILE *pipen;
    char *buffer;

    signal(SIGCHLD, SIG_DFL);

    buffer = (char*) malloc(100);
/*sleep #1*/    sleep(5);
    if ((pipen = popen("uname -a", "r")) == NULL) {
        perror("nError during shell command execution: ");
        exit(0);
    }
/*sleep #2*/    sleep(5);
    fgets(buffer, 100, pipen);
/*sleep #3*/    sleep(5);
    printf("n%sn",buffer);
    exit(0);
}
  

Комментарии:

1. Похоже, что дочерний процесс, созданный popen, завершается после завершения вызова popen. Итак, что делает вызов pclose?

2. @pranav Он вызывает waitpid() , чтобы получить статус завершения процесса, который затем возвращается как его собственное значение. Дочерний процесс будет зомби, пока вы не вызовете pclose() . Но если у вас есть свой собственный SIGCHLD обработчик, который обрабатывает процесс, pclose() вы не сможете этого сделать.