linux子進程知道父進程退出的解決方案

原文鏈接:https://blog.csdn.net/SweetTool/article/details/75059060

在實際開發中難免會處理進程間的關係,最常見的是父子進程的相互監督。父進程等待子進程,或者自進程知道父進程運行是否結束,以方便釋放資源。


一、關於進程


進程是操作系統進行資源分配和調度的基本單位。linux系統使用fork創建進程,進程pid 0是swapper進程,進程pid 1是init進程,init進程是所有普通用戶進程的父進程。
fork在 <unistd.h>文件中定義如下:
pid_t fork(void);
當fork成功時會返回兩次,一次返回給父進程,一次返回給子進程。
如果fork調用成功,返回pid > 0 給父進程,返回pid == 0給子進程。
如果fork調用失敗,返回pid == -1給父進程,並且設置errno。


二、父進程與子進程的關係


通過fork後,爲什麼父進程返回pid > 0,而子進程返回pid == 0呢?這是因爲父進程無法知道自己有多少子進程,子進程創建後就獨立於父進程了,無法知道子進程的id。子進程能夠通過getppid獲取父進程的pid。當父進程比子進程提前退出時,子進程會成爲孤兒進程,系統會將重置孤兒進程的父進程到init進程ppid == 1。如果子進程提前退出並且父進程沒有使用wait函數監聽處理結束的子進程,那麼該子進程會成爲殭屍進程,即系統認爲該進程存在,它佔用的進程信息如進程pid等,會導致系統無法重複利用該pid號,可以這麼理解殭屍進程就是名存實亡的進程,系統認爲進程還存在,而子進程已經完成他的運行任務了。


三、簡單的進程例程


simple_process.cpp
 

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main(int argc, char *argv[]) {
    printf("start program %s, pid: %ld, ppid: %ld \n", argv[0], getpid(), getppid());
    pid_t pid = fork();
    if (-1 == pid) {
        printf("fork process failed. errno: %u, error: %s\n", errno, strerror(errno));
        exit(-1);
    }   
    if (pid > 0) { // parent 
        printf("parent precess\n");
        sleep(1);
/** 標記一、wait 等待子進程結束*/
//      int status;
//      wait(&status);
    } else { // child 
        printf("child process, pid: %ld, ppid: %ld\n", getpid(), getppid());
        for (int i = 0; i < 5; i++) {
            printf("child sleep %ds\n", i); 
            sleep(1);
        }   
        printf("##child process, pid: %ld, ppid: %ld\n", getpid(), getppid());
    }   
    return 0;
}

編譯運行
$ g++ simple_process.cpp
$ ./a.out
start program ./a.out, pid: 17164, ppid: 8564
parent precess
child process, pid: 17165, ppid: 17164
child sleep 0s
[sunny@icentos process-wait]$ child sleep 1s
child sleep 2s
child sleep 3s
child sleep 4s
##child process, pid: 17165, ppid: 1
可以看到,當父進程提前結束時,子進程的父進臣變化爲init進程,ppid == 1。
 

四、實現父進程監聽子進程運行結束

如果我們把“標誌一”的wait代碼刪除註釋,

即 使用下面代碼

     /** 標記一、wait 等待子進程結束 */
     int status;
     wait(&status);

編譯
$ g++ simple_process.cpp
$ ./a.out
start program ./a.out, pid: 28989, ppid: 8564
parent precess
child process, pid: 28990, ppid: 28989
child sleep 0s
child sleep 1s
child sleep 2s
child sleep 3s
child sleep 4s
##child process, pid: 28990, ppid: 28989
因爲wait函數的作用是等待子進程結束,並獲取子進程結束的狀態,所以父進程wait等待子進程結束後才繼續運行退出,故自進程的ppid一直是原來fork父進程的pid。因爲wait函數是阻塞的,所以在實際開發中,需要監聽子進程退出,可以新建線程來監聽,並通過回調函數處理。
 

五、實現子進程監聽父進程運行結束


fork創建進程以後子進程就成爲獨立的運行單位(進程是操作系統資源分配和調度的基本單位),雖然子進程會繼承父進程原來的變量,但是子進程對這些變量的操作不會影響相應父進程變量的值等(這裏只是指簡單的int,long變量,如果是socket,file等共享資源那麼還可能會影響)。子進程已經無法通過普通方式知道父進程的運行狀態(正在運行還是運行結束,進程是否存活)。
子進程要知道父進程的狀態只能通過進程通訊方法解決,常用的進程間通訊:匿名管道,有名管道,信號量,消息隊列,信號,共享內存,套接字。
本文將介紹使用套接字實現父進程結束自動結束子進程的方式,主要運用兩個特性。
1)socketpair會產生一個雙向的句柄,兩個句柄相當於通訊兩端,程序能夠通過一端寫入,然後再另一端句柄讀取內容。
2)進程運行退出時系統會自動回收資源,關閉該進程所佔有的文件句柄,套接字句柄也是一種句柄。
代碼實現
 

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
 
void * thr_child(void *arg) {
    int sock = *((int*)arg);
    char buf[2] = {0};
    ssize_t len = 0;
    while (-1 == (len = read(sock, buf, sizeof(buf))) && EINTR == errno);
    printf("thr_child. pid: %ld, ppid:%ld\n", getpid(), getppid());
    exit(-1 == len ? -1 : 0);
}
 
int main(int argc, char *argv[]) {
    printf("start program: %s, pid: %ld, ppid: %ld\n", argv[0], getpid(), getppid());
    int fd[2] = {0};
    if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
        perror("socketpair failed\n");
        exit(-1);
    }
 
    pid_t pid = fork();
    if (-1 == pid) {
        perror("fork failed\n");
        exit(-1);
    }
    if (0 == pid) { // child
        printf("child process, pid: %ld, ppid: %ld\n", getpid(), getppid());
        close(fd[0]);
        int sock = fd[1];
 
        pthread_t pid;
        if (pthread_create(&pid, NULL, thr_child, (void *)&sock)) {
            perror("child. create thread failed\n");
            exit(-1);
        }
 
        for (int i = 0; i < 20; i++) {
            sleep(1);
            printf("child sleep: %ds\n", i + 1);
        }
    } else { // parent
        printf("parent process, pid: %ld, ppid: %ld\n", getpid(), getppid());
        close(fd[1]);
        int sock = fd[0];
        for (int i = 0; i < 5; i++) {
            sleep(1);
            printf("parent sleep: %ds\n", i + 1);
        }
    }
 
    printf("process %s finish\n", pid > 0 ? "parent" : "child");
    return 0;
}

編譯及運行
$ g++ -pthread notify_parent_quit.cpp
$ ./a.out
start program: ./a.out, pid: 31141, ppid: 8564
parent process, pid: 31141, ppid: 8564
child process, pid: 31142, ppid: 31141
parent sleep: 1s
child sleep: 1s
parent sleep: 2s
child sleep: 2s
parent sleep: 3s
child sleep: 3s
parent sleep: 4s
child sleep: 4s
parent sleep: 5s
process parent finish
child sleep: 5s
thr_child. pid: 31142, ppid:1
可以看到,如果使用wait函數,那麼父進程會等待子進程sleep 20秒再自行結束。但是因爲父進程結束時會關閉套接字句柄sock導致子進程read返回0表示達到文件結尾,因爲父進程沒有主動close套接字句柄sock,所以只有父進程退出時由操作系統關閉該句柄。上面的notify程序就是運用這點,在父進程結束時通知子進程的。通過線程函數thr_child可以知道,在線程退出前打印ppid時,子進程的父進程ppid == 1,即由於父進程結束導致子進程成爲孤兒進程。
總結、socketpair只是其中一種簡單的實現方式。這裏只是使用簡單的例子說明,在實際應用中處理的情況會比較複雜。
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章