programing

하위 프로세스가 종료될 때까지 기다리는 방법

telecom 2023. 7. 14. 23:40
반응형

하위 프로세스가 종료될 때까지 기다리는 방법

과정의 , 하위프스경우의세로,,wait()그리고.waitpid()함수를 사용하여 하위 프로세스가 종료될 때까지 현재 프로세스의 실행을 일시 중단할 수 있습니다.그러나 이 기능은 하위 프로세스가 아닌 경우에는 사용할 수 없습니다.

프로세스가 종료될 때까지 기다릴 수 있는 다른 기능이 있습니까?

▁to에 해당하는 것은 .wait()일반적인 방법은 다음을 사용하여 폴링하는 것입니다.kill(pid, 0) 및 반환값 -1을 .errnoESRCH프로세스가 사라졌음을 나타냅니다.

업데이트: 리눅스 커널 5.3 이후로 pidfd_open syscall이 있으며, 이는 주어진 pid에 대한 fd를 생성하며, pid가 종료되었을 때 알림을 받기 위해 폴링할 수 있습니다.

BSD 및 OS X에서 Kqueue를 EVFILT_PROC+NOTE_EXIT와 함께 사용하여 정확하게 이 작업을 수행할 수 있습니다.폴링이 필요하지 않습니다.안타깝게도 리눅스에 필적하는 것은 없습니다.

지금까지 Linux에서 이를 수행하는 세 가지 방법을 찾았습니다.

  • 여부를 합니다.kill또는 존재 여부를 테스트함으로써./proc/$pid다른 대부분의 대답과 마찬가지로
  • 을 합니다.ptrace프로세스에 디버거처럼 연결하여 프로세스가 종료될 때 알림을 받기 위한 시스템 호출(예: 3nm의 응답)
  • 을 합니다.netlink를 듣기 위한 PROC_EVENT_EXIT메시지 - 프로세스가 종료될 때마다 커널이 프로그램에 알려주고 사용자는 올바른 프로세스 ID를 기다립니다.는 이것이 인터넷에서 한 곳에서만 묘사된 것을 보았습니다.

파렴치한 플러그:저는 이 세 가지 중 하나를 수행하는 프로그램(오픈 소스, GPLv2)을 개발하고 있습니다.

소켓이나 FIFO를 만들어 읽어볼 수도 있습니다.FIFO는 특히 간단합니다. 자녀의 표준 출력을 FIFO와 연결하고 판독하십시오.읽기는 아이가 종료될 때까지(어떤 이유로든) 또는 일부 데이터를 내보낼 때까지 차단됩니다.따라서 불필요한 텍스트 데이터를 폐기하려면 약간의 루프가 필요합니다.

아이의 소스에 액세스할 수 있는 경우, FIFO가 시작될 때 쓰기를 위해 FIFO를 열고 잊어버립니다.자식이 종료되고 대기 중인 "부모" 프로세스가 실행되면 OS가 열려 있는 파일 설명자를 정리합니다.

이 과정은 시작하거나 소유하지 않은 과정일 수 있습니다.이 경우 이진 실행 파일을 실제 이진을 시작하면서 위에서 설명한 대로 모니터링을 추가하는 스크립트로 바꿀 수 있습니다.

다음은 리눅스의 프로세스(반드시 하위 프로세스는 아님)가 폴링 없이 종료(또는 종료)될 때까지 기다리는 방법입니다.

inotify를 사용하여 /proc 'pid'가 삭제될 때까지 기다리는 것이 완벽한 해결책이 될 수 있지만, 불행히도 inotify는 /proc와 같은 유사 파일 시스템에서 작동하지 않습니다.그러나 우리는 프로세스의 실행 파일과 함께 사용할 수 있습니다.프로세스가 여전히 존재하는 동안 이 파일은 열려 있습니다.따라서 IN_CLOSE_NOWRITE로 innotify를 사용하여 파일이 닫힐 때까지 차단할 수 있습니다.물론 다른 이유(예: 동일한 실행 파일을 가진 다른 프로세스가 종료되는 경우)로 인해 종료될 수 있으므로 다른 방법으로 이벤트를 필터링해야 합니다.

킬(pid, 0)을 사용할 수는 있지만, 동일한 프로세스라면 보장할 수 없습니다.만약 우리가 이것에 대해 정말로 편집증적이라면, 우리는 다른 것을 할 수 있습니다.

여기 pid-reuse 문제에 대해 100% 안전해야 하는 방법이 있습니다. 우리는 유사 디렉토리 /proc/'pid'를 열고 완료될 때까지 열어 둡니다.그 사이에 동일한 pid로 새 프로세스가 생성되는 경우, 보유하고 있는 디렉터리 파일 설명자는 여전히 원래 프로세스를 참조하지만(또는 이전 프로세스가 더 이상 존재하지 않는 경우에는 유효하지 않음) 재사용된 pid로 새 프로세스를 참조하지 않습니다.그런 다음 예를 들어 "cmdline" 파일이 openat() 디렉터리에 있는지 확인하여 원래 프로세스가 여전히 존재하는지 확인할 수 있습니다.프로세스가 종료되거나 종료되면 이러한 유사 파일도 존재하지 않으므로()에서 열기가 실패합니다.

다음은 예제 코드입니다.

// return -1 on error, or 0 if everything went well
int wait_for_pid(int pid)
{
    char path[32];
    int in_fd = inotify_init();
    sprintf(path, "/proc/%i/exe", pid);
    if (inotify_add_watch(in_fd, path, IN_CLOSE_NOWRITE) < 0) {
        close(in_fd);
        return -1;
    }
    sprintf(path, "/proc/%i", pid);
    int dir_fd = open(path, 0);
    if (dir_fd < 0) {
        close(in_fd);
        return -1;
    }

    int res = 0;
    while (1) {
        struct inotify_event event;
        if (read(in_fd, &event, sizeof(event)) < 0) {
            res = -1;
            break;
        }
        int f = openat(dir_fd, "fd", 0);
        if (f < 0) break;
        close(f);
    }

    close(dir_fd);
    close(in_fd);
    return res;
}

다음을 사용하여 프로세스에 첨부할 수 있습니다.ptrace(2) 껍기에서보면데,면▁from기.strace -p PID >/dev/null 2>&1효과가 있는 것 같습니다.이렇게 하면 추적된 프로세스의 속도가 느려지고 모든 프로세스(하위 프로세스보다 약간 나은 사용자 프로세스만)에서 작동하지는 않지만 바쁜 대기 상태를 피할 수 있습니다.

아무 것도 모릅니다.혼돈으로부터의 해결책과는 별개로, 당신이 기다리고 싶은 프로그램을 바꿀 수 있다면 당신은 세마포어를 사용할 수 있습니다.

라이브러리 기능은 다음과 같습니다.sem_open(3),sem_init(3), sem_wait(3),...

sem_wait(3)대기를 수행하므로 혼돈의 해결책처럼 대기하느라 바쁠 필요가 없습니다.물론, 세마포어를 사용하는 것은 여러분의 프로그램을 더 복잡하게 만들고 그것은 수고할 가치가 없을지도 모릅니다.

/proc/[pid] 또는 /proc/[pid]/[something]이(가) 사라질 때까지 기다릴 수 있습니까?

폴()과 다른 파일 이벤트 대기 기능이 있는데, 도움이 될까요?

리눅스 커널 5.3 이후로 pidfd_open syscall이 있으며, 이는 주어진 pid에 대한 fd를 생성하며, pid가 종료되었을 때 알림을 받기 위해 폴링할 수 있습니다.

/proc/[의 22번과 2번 값을 폴링합니다.PID]/stat.값 2에는 실행 파일의 이름이 포함되고 22에는 시작 시간이 포함됩니다.이러한 프로세스가 변경되면 일부 다른 프로세스에서 동일한 PID를 사용했습니다.따라서 이 방법은 매우 신뢰할 수 있습니다.

사용할 수 있습니다.eBPF이를 위해

bcc툴킷은 다음을 기반으로 많은 우수한 모니터링 기능을 구현합니다.eBPF이 중 종료 프로세스를 추적하여 종료 명령 이름과 종료 이유(종료 또는 치명적인 신호)를 표시합니다.

   It catches processes of all users, processes in containers,  as  well  as  processes  that
   become zombie.

   This  works by tracing the kernel sched_process_exit() function using dynamic tracing, and
   will need updating to match any changes to this function.

   Since this uses BPF, only the root user can use this tool.

관련 구현에 대해서는 이 도구를 참조할 수 있습니다.

아래 링크에서 이 도구에 대한 자세한 정보를 얻을 수 있습니다.

먼저 이 도구를 설치하고 필요에 맞게 사용할 수 있는지 확인한 다음 구현을 참조하여 코딩하거나 제공하는 일부 라이브러리를 사용하여 사용자 고유의 기능을 구현할 수 있습니다.

exitsnoop예:

   Trace all process termination
          # exitsnoop

   Trace all process termination, and include timestamps:
          # exitsnoop -t

   Exclude successful exits, only include non-zero exit codes and fatal signals:
          # exitsnoop -x

   Trace PID 181 only:
          # exitsnoop -p 181

   Label each output line with 'EXIT':
          # exitsnoop --label EXIT

다른 옵션

Linux의 PROC_EVENTS를 사용하여 (하위가 아닌) 프로세스가 종료될 때까지 대기

참조 프로젝트: https://github.com/stormc/waitforpid

프로젝트에 언급된 내용:

Linux의 PROC_EVENTS를 사용하여 (하위가 아닌) 프로세스가 종료될 때까지 기다립니다.wait forpid binary에 허용된 CAP_NET_ADMIN POSIX 기능 덕분에 suid 루트를 설정할 필요가 없습니다.CONFIG_PROC_EVENTS를 사용하도록 설정된 Linux 커널이 필요합니다.

kqueue를 사용한 macOS에 대한 @Hongli의 답변에 감사드립니다.저는 그것을 신속하게 실행합니다.

/// Wait any pids, including non-child pid. Block until all pids exit.
/// - Parameters:
///   - timeout: wait until interval, nil means no timeout
/// - Throws: WaitOtherPidError
/// - Returns: isTimeout
func waitOtherPids(_ pids: [Int32], timeout: TimeInterval? = nil) throws -> Bool {
    
    // create a kqueue
    let kq = kqueue()
    if kq == -1 {
        throw WaitOtherPidError.createKqueueFailed(String(cString: strerror(errno)!))
    }
    
    // input
    // multiple changes is OR relation, kevent will return if any is match
    var changes: [Darwin.kevent] = pids.map({ pid in
        Darwin.kevent.init(ident: UInt(pid), filter: Int16(EVFILT_PROC), flags: UInt16(EV_ADD | EV_ENABLE), fflags: NOTE_EXIT, data: 0, udata: nil)
    })
    
    let timeoutDeadline = timeout.map({ Date(timeIntervalSinceNow: $0)})
    let remainTimeout: () ->timespec? = {
        if let deadline = timeoutDeadline {
            let d = max(deadline.timeIntervalSinceNow, 0)
            let fractionalPart = d - TimeInterval(Int(d))
            return timespec(tv_sec: Int(d), tv_nsec: Int(fractionalPart * 1000 * 1000 * 1000))
        } else {
            return nil
        }
    }
    
    // output
    var events = changes.map{ _ in Darwin.kevent.init() }
    
    while !changes.isEmpty {
        
        // watch changes
        // sync method
        let numOfEvent: Int32
        if var timeout = remainTimeout() {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), &timeout);
        } else {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), nil);
        }
        
        if numOfEvent < 0 {
            throw WaitOtherPidError.keventFailed(String(cString: strerror(errno)!))
        }
        if numOfEvent == 0 {
            // timeout. Return directly.
            return true
        }
        
        // handle the result
        let realEvents = events[0..<Int(numOfEvent)]
        let handledPids = Set(realEvents.map({ $0.ident }))
        changes = changes.filter({ c in
            !handledPids.contains(c.ident)
        })

        for event in realEvents {
            if Int32(event.flags) & EV_ERROR > 0 { // @see 'man kevent'
                let errorCode = event.data
                if errorCode == ESRCH {
                    // "The specified process to attach to does not exist"
                    // ingored
                } else {
                    print("[Error] kevent result failed with code \(errorCode), pid \(event.ident)")
                }
            } else {
                // succeeded event, pid exit
            }
        }
    }
    return false
}
enum WaitOtherPidError: Error {
    case createKqueueFailed(String)
    case keventFailed(String)
}

PR_SET_PDEATSIG는 상위 프로세스 종료를 기다리는 데 사용할 수 있습니다.

언급URL : https://stackoverflow.com/questions/1157700/how-to-wait-for-exit-of-non-children-processes

반응형