進程:佔用內存空間的正在運行的程序,是系統進行資源分配和調度的基本單位。
進程在完成工作後應該被銷燬,如果完成工作後,仍佔用系統資源不被銷燬,就會變爲殭屍進程,給系統帶來負擔。
殭屍進程的產生
向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網絡編程》 尹聖雨