殭屍進程以及消滅殭屍進程

進程:佔用內存空間的正在運行的程序,是系統進行資源分配和調度的基本單位。
進程在完成工作後應該被銷燬,如果完成工作後,仍佔用系統資源不被銷燬,就會變爲殭屍進程,給系統帶來負擔。

殭屍進程的產生

向exit函數傳遞參數值,或者是通過return語句返回的值都會傳遞給操作系統,而操作系統如果沒有把這些值傳遞給產生該子進程的父進程,那麼操作系統就不會銷燬子進程.處於這種狀態下的進程就是殭屍進程。

下面是一個產生殭屍進程的示例
zombie.c

#include<stdio.h>
#include<unistd.h>

int main(int argc,char *argv[]){
    pid_t pid = fork();
    if(pid == 0){
        fprintf(stdout,"child process.\n");
    }
    else{
        fprintf(stdout,"child process ID:%d\n",pid);
        sleep(30);
    }
    if(pid == 0){
        fprintf(stdout,"End child process.\n");
    }
    else{
        fprintf(stdout,"End parent process.\n");
    }

    return 0;
}

父進程在休眠,而子進程已經結束,且父進程並未處理子進程的返回值,這時子進程就會變爲殭屍進程.
在這裏插入圖片描述如圖,這裏3636就是產生的殭屍進程,進程stat爲Z

銷燬殭屍進程

如果銷燬呢?可以考慮產生殭屍進程的原因。
把子進程exit參數值或return語句返回值傳遞給父進程,即可消滅殭屍進程。

1.wait 函數
#include<sys/wait.h>
pid_t wait(int *statloc);

調用該函數時,會阻塞進程,如果已經有子進程終止,那麼子進程終止時傳遞的返回值將保存到statloc所指向的內存空間。
但該空間還保存有其他信息,需要利用宏來分離.
WIFEXITED();//子進程正常終止時返回真.
WEXITSTATUS();//返回子進程的返回值.

wait.c


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(int argc,char *argv[]){
    int status;
    pid_t pid = fork();

    if(pid == 0){
        return 3;
    }
    else{
        fprintf(stdout,"Child PID: %d\n",pid);
        pid = fork();
        if(pid == 0){
            exit(4);
        }
        else{
            fprintf(stdout,"Child PID: %d\n",pid);
            wait(&status);
            if(WIFEXITED(status))
                fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
            fprintf(stdout,"status = %d\n",status); 
            wait(&status);
            if(WIFEXITED(status))
                fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
            sleep(20);
        }
    }
    return 0;
}

在這裏插入圖片描述這樣就不會產生殭屍進程了。不過該函數會一直阻塞進程,直到有子進程終止,所以有可能會造成不必要的麻煩。

2.waitpid 函數
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *statloc,int options);

pid:等待終止的的目標子進程ID,若傳遞-1,則與wait函數相同,等待任意進程的終止.
statloc:保存返回信息的空間指針
options: 有以下幾個選項
(1) WNOHANG:若制定的pid未終止,則返回0,不會阻塞進程.
(2) WUNTRACED: 若子進程進入暫停狀態,則馬上返回,但子進程的結束狀態不予以理會。WIFSTOPPED(status)宏確定返回值是否對應與一個暫停子進程。

waitpid.c

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(int argc,char *argv[]){
   int status;
   pid_t pid = fork();
   if(pid == 0){
       sleep(10);
       return 2;
   }
   else{
       while(!waitpid(-1,&status,WNOHANG)){
           sleep(1);
           fprintf(stdout,"sleep 1 sec.\n");
       }
       if(WIFEXITED(status))
           fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
   }
   return 0;
}

在這裏插入圖片描述很顯然這裏的父進程併爲被阻塞.但仍存在一個問題,就是,我們要一直等待waitpid函數,如果不等待,我們不知道何時子進程終止。這也會對程序造成問題。

信號處理

子進程終止是被操作系統所識別的,如果操作系統能把自己進程終止這一消息告訴父進程,那麼我們就能很好的處理了。

singal函數

#include<signal.h>
void (*signal(int signo,void (*func)(int)))(int);

signo:特殊情況的信息,有如下:

  • SIGALRM:已到通過調用alarm函數註冊的時間
  • SIGINT:輸入CTRL+C.
  • SIGCHLD:子進程終止.

func:發生特殊情況時,調用的函數的指針.

介紹一個alarm函數

#include<unistd.h>
unsigned int alarm(unsigned int sec);

相應sec時間後,將產生一個SIGALRM信號.

一個示例
signal.c

#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void timeout(int sig){
    if(sig == SIGALRM)
        fprintf(stdout,"Time out!\n");
    alarm(2);
}
void keycontrol(int sig){
    if(sig == SIGINT)
        fprintf(stdout,"CTRL+C pressed!\n");
}
int main(int argc,char *argv[]){
    signal(SIGALRM,timeout);
    signal(SIGINT,keycontrol);
    alarm(2);
    
    for(int i=0;i<3;++i){
        fprintf(stdout,"wait...\n");
        sleep(20);
    }
    return 0;
}

執行結果
在這裏插入圖片描述
也可以嘗試在執行中輸入CTRL+C,會調用keycontrol函數
注意在這裏主進程雖然有sleep(20),但等待6秒左右,就會結束整個程序。
因爲進程在休眠中無法調用函數,但此時有信號產生,爲了調用信號處理函數,會把休眠的進程喚醒。而且進程一旦被喚醒,即使爲達到sleep函數調用的時間也不會再繼續休眠,除非再次調用sleep。

**sigaction **

#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction * oldact);
  • signo:與signal函數相同,傳遞信號信息
  • act:相當於信號處理函數
  • oldact:通過此參數獲取之前註冊的信號處理函數指針,若無需要則傳遞0

sigaction結構體

struct sigaciont{
	void(*sa_handler)(int);
	sigset_t sa_mask;
	int sa_flags;
}
  • sa_handler:指向信號處理函數
  • sa_mask和sa_flags:指定信號相關的選項和特性
利用信號技術消滅殭屍進程

signal_remove_zombie.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>

void read_child_send(int sig){
    int status;
    pid_t pid = waitpid(-1,&status,WNOHANG);
    if(WIFEXITED(status)){
        fprintf(stdout,"Removed PID:%d\n",pid);
        fprintf(stdout,"Child send:%d\n",WEXITSTATUS(status));
    }
}

int main(int argc,char *argv[]){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = &read_child_send;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD,&act,0);

    pid = fork();

    if(pid == 0){
        fprintf(stdout,"Child process running.\n");
        sleep(1);
        return 2;
    }
    else{
        fprintf(stdout,"Child process PID:%d\n",pid);
        pid = fork();
        
        if(pid == 0){
            fprintf(stdout,"Child process running.\n");
            sleep(2);
            return 3;
        }
        else{
            fprintf(stdout,"Child process PID:%d\n",pid);
            for(int i=0;i<3;++i){
                fprintf(stdout,"wait...\n");
                sleep(2);
            }
        }
    }
    return 0;
}

運行結果:
在這裏插入圖片描述
參考資料:

  • 《TCP/IP網絡編程》 尹聖雨
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章