programing

Waitpid는 타임아웃과 동등합니까?

goodsources 2022. 7. 31. 23:02
반응형

Waitpid는 타임아웃과 동등합니까?

몇 가지 하위 프로세스를 시작하는 프로세스가 있다고 상상해 보십시오.부모는 아이가 언제 나가는지 알아야 한다.

사용할 수 있습니다.waitpid, 해야 할 경우 종료해야 할 를 알 수 waitpid우아하게 퇴장하고 합류할 수 있도록 하겠습니다.스스로 정리하는 것은 좋지만, 그렇게 큰 일은 아닐지도 모릅니다.

사용할 수 있습니다.waitpidWNOHANG비지 대기 시간을 방지하기 위해 임의의 시간 동안 sleep합니다.하지만 아이가 자주 나갔는지 아닌지 알 수 밖에 없어요.제 경우엔 아이가 언제 나올지 아는 게 그렇게 중요한 건 아닐지 몰라도 최대한 빨리 알고 싶어요

를 「」에 할 수 .SIGCHLD시그널 핸들러에서는 아이가 퇴장할 때 내가 하려던 모든 작업을 수행하거나 다른 스레드에 메시지를 전송하여 액션을 수행합니다.하지만 신호 핸들러를 사용하면 코드의 흐름이 약간 흐트러집니다.

건 '우리'를 쓰는 예요.waitpid프로세스 조작이 스레드되어 있는 동안 스레드 신호를 수 있습니다.waitpid항상 반응할 준비가 되어 있습니다.Linux에도 이런 콜이 있나요? 대안 중 어떤 것이 가장 좋습니까?


편집:

한 또 다른 은 '입니다.SIGCHLD에는 「」가 붙어 있습니다.pthread \ \_sigmask() 다음의 스레드에서 호출합니다sigtimedwait()SIGCHLD즉, 콜을 타임아웃하여 스레드가 종료되는지 여부를 체크할 수 있으며, 종료되지 않을 경우 신호를 기다리는 동안 차단 상태를 유지할 수 있습니다. a 번SIGCHLD는 이 스레드에 전달되므로 신호 핸들러를 사용하지 않고 즉시 대기 스레드에 응답할 수 있습니다.

흥미로운 질문입니다.시그타임드웨이트가 할 수 있다는 걸 알았어

2016/08/29 편집: Mark Edington의 제안 감사합니다.Ubuntu 16.04에서 예시를 테스트했습니다만, 기대대로 동작합니다.

참고: 이것은 하위 프로세스에서만 작동합니다.Linux/Unix의 WaitForSingleObject(unrelated_process_handle, timeout)와 동등한 방법으로 관련 없는 프로세스의 종료가 타임아웃 내에 통지되지 않는 것은 유감입니다.

마크 에딩턴의 샘플 코드는 다음과 같습니다.

/* The program creates a child process and waits for it to finish. If a timeout
 * elapses the child is killed. Waiting is done using sigtimedwait(). Race
 * condition is avoided by blocking the SIGCHLD signal before fork().
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

static pid_t fork_child (void)
{
    int p = fork ();

    if (p == -1) {
        perror ("fork");
        exit (1);
    }

    if (p == 0) {
        puts ("child: sleeping...");
        sleep (10);
        puts ("child: exiting");
        exit (0);
    }

    return p;
}

int main (int argc, char *argv[])
{
    sigset_t mask;
    sigset_t orig_mask;
    struct timespec timeout;
    pid_t pid;

    sigemptyset (&mask);
    sigaddset (&mask, SIGCHLD);

    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    pid = fork_child ();

    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;

    do {
        if (sigtimedwait(&mask, NULL, &timeout) < 0) {
            if (errno == EINTR) {
                /* Interrupted by a signal other than SIGCHLD. */
                continue;
            }
            else if (errno == EAGAIN) {
                printf ("Timeout, killing child\n");
                kill (pid, SIGKILL);
            }
            else {
                perror ("sigtimedwait");
                return 1;
            }
        }

        break;
    } while (1);

    if (waitpid(pid, NULL, 0) < 0) {
        perror ("waitpid");
        return 1;
    }

    return 0;
}

waitpid()를 직접 호출하는 대신 SIGCHLD를 사용하여 sigtimedwait()를 호출하여 (자녀 종료 후 부모 프로세스로 전송됨) 현재 스레드로 전송되기를 기다릴 수 있습니다.이 함수의 이름 그대로 타임아웃 파라미터가 지원됩니다.

상세한 것에 대하여는, 다음의 코드 스니펫을 확인해 주세요.


static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
    sigset_t child_mask, old_mask;
    sigemptyset(&child_mask);
    sigaddset(&child_mask, SIGCHLD);

    if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
        printf("*** sigprocmask failed: %s\n", strerror(errno));
        return false;
    }

    timespec ts;
    ts.tv_sec = MSEC_TO_SEC(timeout_ms);
    ts.tv_nsec = (timeout_ms % 1000) * 1000000;
    int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
    int saved_errno = errno;

    // Set the signals back the way they were.
    if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
        printf("*** sigprocmask failed: %s\n", strerror(errno));
        if (ret == 0) {
            return false;
        }
    }
    if (ret == -1) {
        errno = saved_errno;
        if (errno == EAGAIN) {
            errno = ETIMEDOUT;
        } else {
            printf("*** sigtimedwait failed: %s\n", strerror(errno));
        }
        return false;
    }

    pid_t child_pid = waitpid(pid, status, WNOHANG);
    if (child_pid != pid) {
        if (child_pid != -1) {
            printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
        } else {
            printf("*** waitpid failed: %s\n", strerror(errno));
        }
        return false;
    }
    return true;
}

참조: https://android.googlesource.com/platform/frameworks/native/+/master/cmds/dumpstate/DumpstateUtil.cpp#46

중간 자식을 포크합니다. 그러면 실제 자녀와 타임아웃 프로세스가 포크되고 해당 자녀의 모든 (둘 다) 대기합니다.한 명이 빠져나가면 다른 한 명은 죽이고 나갈 거야

pid_t intermediate_pid = fork();
if (intermediate_pid == 0) {
    pid_t worker_pid = fork();
    if (worker_pid == 0) {
        do_work();
        _exit(0);
    }

    pid_t timeout_pid = fork();
    if (timeout_pid == 0) {
        sleep(timeout_time);
        _exit(0);
    }

    pid_t exited_pid = wait(NULL);
    if (exited_pid == worker_pid) {
        kill(timeout_pid, SIGKILL);
    } else {
        kill(worker_pid, SIGKILL); // Or something less violent if you prefer
    }
    wait(NULL); // Collect the other process
    _exit(0); // Or some more informative status
}
waitpid(intermediate_pid, 0, 0);

의외로 심플 :)

프로그램의 다른 모듈이 자체 자식 프로세스를 스왑하지 않는 것이 확실할 경우 중간 자식 프로세스를 생략할 수도 있습니다.

커널(5되는 경우 (5.3 이후)를 사용하는 .pidfd_open (https://lwn.net/Articles/789023/https://man7.org/linux/man-pages/man2/pidfd_open.2.html)).

그 후, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 」, 「 。select,poll ★★★★★★★★★★★★★★★★★」epoll다른 유형의 파일 기술자에서도 대기하는 것과 같은 방법으로.

예를들면,

int fd = pidfd_open(pid, 0);
struct pollfd pfd = {fd, POLLIN, 0};
poll(&pfd, 1, 1000) == 1;

★★★★★★★★★★★★★★★★를 혼용하지 말아 주세요alarm()wait()이렇게 하면 오류 정보가 손실될 수 있습니다.

셀프 파이프 트릭을 사용합니다.에 의해, 모든 가 「」, 「」, 「」, 「」로 .select()「이것들」은 다음과 같습니다.

int selfpipe[2];
void selfpipe_sigh(int n)
{
    int save_errno = errno;
    (void)write(selfpipe[1], "",1);
    errno = save_errno;
}
void selfpipe_setup(void)
{
    static struct sigaction act;
    if (pipe(selfpipe) == -1) { abort(); }

    fcntl(selfpipe[0],F_SETFL,fcntl(selfpipe[0],F_GETFL)|O_NONBLOCK);
    fcntl(selfpipe[1],F_SETFL,fcntl(selfpipe[1],F_GETFL)|O_NONBLOCK);
    memset(&act, 0, sizeof(act));
    act.sa_handler = selfpipe_sigh;
    sigaction(SIGCHLD, &act, NULL);
}

그러면 waitpid와 같은 함수는 다음과 같습니다.

int selfpipe_waitpid(void)
{
    static char dummy[4096];
    fd_set rfds;
    struct timeval tv;
    int died = 0, st;

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    FD_ZERO(&rfds);
    FD_SET(selfpipe[0], &rfds);
    if (select(selfpipe[0]+1, &rfds, NULL, NULL, &tv) > 0) {
       while (read(selfpipe[0],dummy,sizeof(dummy)) > 0);
       while (waitpid(-1, &st, WNOHANG) != -1) died++;
    }
    return died;
}

하다 보면 알 수 요.selfpipe_waitpid()할 수 select()- IO ---IO

줄 요.selectEINTRSIGCHLD아이에 대한 신호입니다.저는 이것이 효과가 있다고 생각합니다.

while(1)
{
  int retval = select(0, NULL, NULL, NULL, &tv, &mask);
  if (retval == -1 && errno == EINTR) // some signal
  { 
      pid_t pid = (waitpid(-1, &st, WNOHANG) == 0);
      if (pid != 0) // some child signaled
  }
  else if (retval == 0)
  {
      // timeout
      break;
  }
  else // error
}

note: 다 note note : 음 、 음 、 다 、 습 、 note 、 note 、 note 、 note 、 note note 、pselect보다 sigmask불필요한 신호로부터의 인터럽트를 회피할 수 있습니다.

메인 스레드에서 동작하기 위해서는 반드시 이것이 필요했고, 다른 스레드에서 동작하고 있기 때문에 셀프 파이프 트릭이나 eventfd를 사용하는 것은 매우 간단하지 않았습니다.그래서 다른 스택 오버플로 핸들러를 긁어모으고 이 방법을 생각해냈습니다.일반적으로 이 작업은 다른 방법으로 수행하는 것이 훨씬 안전하지만 이것은 간단합니다.만약 누군가가 그것이 정말 얼마나 나쁜지에 대해 언급하고 싶다면, 나는 귀를 기울일 것이다.

메모: 이 처리를 실행하는 스레드 저장에서는 반드시 신호 처리를 차단해야 합니다.랜덤 스레드로 신호를 처리하는 것은 혼란스럽기 때문에 기본적으로 이렇게 합니다.

static void ctlWaitPidTimeout(pid_t child, useconds_t usec, int *timedOut) {
    int rc = -1;

    static pthread_mutex_t alarmMutex = PTHREAD_MUTEX_INITIALIZER;

    TRACE("ctlWaitPidTimeout: waiting on %lu\n", (unsigned long) child);

    /**
     * paranoid, in case this was called twice in a row by different
     * threads, which could quickly turn very messy.
     */
    pthread_mutex_lock(&alarmMutex);

    /* set the alarm handler */
    struct sigaction alarmSigaction;
    struct sigaction oldSigaction;

    sigemptyset(&alarmSigaction.sa_mask);
    alarmSigaction.sa_flags   = 0;
    alarmSigaction.sa_handler = ctlAlarmSignalHandler;
    sigaction(SIGALRM, &alarmSigaction, &oldSigaction);

    /* set alarm, because no alarm is fired when the first argument is 0, 1 is used instead */
    ualarm((usec == 0) ? 1 : usec, 0);

    /* wait for the child we just killed */
    rc = waitpid(child, NULL, 0);

    /* if errno == EINTR, the alarm went off, set timedOut to true */
    *timedOut = (rc == -1 && errno == EINTR);

    /* in case we did not time out, unset the current alarm so it doesn't bother us later */
    ualarm(0, 0);

    /* restore old signal action */
    sigaction(SIGALRM, &oldSigaction, NULL);

    pthread_mutex_unlock(&alarmMutex);

    TRACE("ctlWaitPidTimeout: timeout wait done, rc = %d, error = '%s'\n", rc, (rc == -1) ? strerror(errno) : "none");
}

static void ctlAlarmSignalHandler(int s) {
    TRACE("ctlAlarmSignalHandler: alarm occured, %d\n", s);
}

편집: 이후 timerfd를 사용하여 기존 epoll() 기반 이벤트 루프와 잘 연동되는 솔루션을 사용하는 것으로 전환했습니다.어차피 epoll을 사용했기 때문에 플랫폼에 의존하지 않고, 멀티스레딩과 UNIX 신호의 비정상적인 조합이 프로그램을 다시 손상시키지 않는다는 것을 알기 때문에 여분의 수면을 취할 수 있습니다.

이 함수는 신호로 중단될 수 있으므로 waitpid()를 호출하기 전에 타이머를 설정할 수 있으며 타이머 신호가 발생하면 EINTR로 종료됩니다.편집: waitpid()를 호출하기 전에 알람(5)을 호출하는 것과 같이 단순해야 합니다.

(Steve의 제안에 따라) 신호를 사용할 경우 종료할 때 수동으로 신호를 보낼 수 있습니다.그러면 waitpid가 EINTR을 반환하고 스레드가 종료됩니다.정기적인 알람/재기동 불필요.

시그널 핸들러를 SIGCHLD에 사용할 수 있습니다.또, 신호 핸들러에서는, 아이가 퇴장했을 때에 행하고 있던 모든 조작을 실시하거나, 다른 스레드에 메세지를 송신하거나 할 수 있습니다.하지만 신호 핸들러를 사용하면 코드의 흐름이 약간 흐트러집니다.

레이스 조건을 회피하려면 신호 핸들러에서 휘발성 플래그를 변경하는 것보다 복잡한 작업은 피해야 합니다.

이 경우 가장 좋은 옵션은 부모에게 신호를 보내는 것이라고 생각합니다.waitpid()는 errno를 EINTR로 설정하고 되돌립니다.이 시점에서 waitpid return 값과 errno를 체크하고 신호가 전송된 것을 확인하고 적절한 조치를 취합니다.

서드파티 라이브러리가 허용 가능한 경우 libkqueue 프로젝트는kqueue 시스템)및 (*BSD 이벤트 시스템)을 사용하여 기본적인 합니다.EVFILT_PROC+NOTE_EXIT.

「 」를 사용하는 kqueue ★★★★★★★★★★★★★★★★★」libkqueue크로스 플랫폼이기 때문에 신호 처리가 복잡하지 않습니다.하고 있는 I/O 를 하고 있는 는, I/O 를 사용하는 .epoll 여러 가지 양양양 and and and and*fdfunctions)signalfd,eventfd,pidfdetc을 합니다.

#include <stdio.h>
#include <stdint.h>
#include <sys/event.h> /* kqueue header */
#include <sys/types.h> /* for pid_t */

/* Link with -lkqueue */

int waitpid_timeout(pid_t pid, struct timespec *timeout)
{
    struct kevent changelist, eventlist;
    int kq, ret;

    /* Populate a changelist entry (an event we want to be notified of) */
    EV_SET(&changelist, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);

    kq = kqueue();

    /* Call kevent with a timeout */
    ret = kevent(kq, &changelist, 1, &eventlist, 1, timeout);

    /* Kevent returns 0 on timeout, the number of events that occurred, or -1 on error */
    switch (ret) {
    case -1:
        printf("Error %s\n", strerror(errno));
        break;

    case 0:
        printf("Timeout\n");
        break;

    case 1:
        printf("PID %u exited, status %u\n", (unsigned int)eventlist.ident, (unsigned int)eventlist.data);
        break;
    }
    close(kq);

    return ret;
}

behind behind behind behind behind behindlibkqueue Linux 커널 > = 5.3에서 pidfd 또는 waiter 스레드를 사용합니다.SIGCHLD 또는 여러 개의 1개 또는 개의 에게 통지합니다.kqueue로세프).waitid). 않는 그것은 문제가 않습니다.

EVFILT_PROC kqueue, 시시 since시 since since since since since since since since since since 。libkqueuev2.5.0.

언급URL : https://stackoverflow.com/questions/282176/waitpid-equivalent-with-timeout

반응형