(2.5)fork函數詳解、範例演示

一:fork()函數簡單認識

//創建進程;

    //進程的概念:一個可執行程序,執行起來就是一個進程,再執行起來一次,它就又是一個進程(多個進程可以共享同一個  可執行文件)

     //文雅說法:進程 定義爲程序執行的一個實例;

 

    //在一個進程(程序)中,可以用fork()創建一個子進程,當該子進程創建時,

       //它從fork()指令的下一條(或者說從fork()的返回處)開始執行與父進程相同的代碼;

 

//a)說白了:fork()函數產生了一個和當前進程完全一樣的新進程,並和當前進程一樣從fork()函數裏返回;

     //原來一條執行通路(父進程),現在變成兩條(父進程+子進程)

//fork():一分二; *********

(1.1)fork()函數簡單範例

    //ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx'

    //fork()之後,是父進程fork()之後的代碼先執行還是子進程fork()之後的代碼先執行是不一定的;這個跟內核調度算法有關;

    //kill子進程,觀察父進程收到什麼信號:SIGCHLD信號 ,子進程變成了殭屍進程Z

 

//信號處理函數
void sig_usr(int signo)
{
    printf("收到了SIGUSR1信號,進程id=%d!\n",getpid());    
}

int main(int argc, char *const *argv)
{
    pid_t  pid;

    printf("進程開始執行!\n");

    //先簡單處理一個信號
    if(signal(SIGUSR1,sig_usr) == SIG_ERR)  //系統函數,參數1:是個信號,參數2:是個函數指針,代表一個針對該信號的捕捉處理函數
    {
        printf("無法捕捉SIGUSR1信號!\n");
        exit(1);
    }

    //---------------------------------
    pid = fork();  //創建一個子進程

    //要判斷子進程是否創建成功
    if(pid < 0)
    {
        printf("子進程創建失敗,很遺憾!\n");
        exit(1);
    }

    //現在,父進程和子進程同時開始 運行了 
    for(;;)
    {        
        sleep(1); //休息1秒
        printf("休息1秒,進程id=%d!\n",getpid());
    }
    printf("再見了!\n");
    return 0;
}

(1.2)殭屍進程的產生、解決,SIGCHLD

    //殭屍進程的產生:在Unix系統中,一個子進程結束了,但是他的父進程還活着,

     // 但該父進程沒有調用(wait/waitpid)函數來進行額外的處置,那麼這個子進程就會變成一個殭屍進程;

    //殭屍進程:已經被終止,不幹活了,但是依舊沒有被內核丟棄掉,因爲內核認爲父進程可能還需要該子進程的一些信息;

    //作爲開發者,堅決不允許殭屍進程的存在;

    //如何幹掉殭屍進程:

     //a)重啓電腦

     //b)手工的把殭屍進程的父進程kill掉,殭屍進程就會自動消失;

    //SIGCHLD信號:一個進程被終止或者停止時,這個信號會被髮送給父進程;

     //所以,對於源碼中有fork()行爲的進程,我們 應該攔截並處理SIGCHLD信號;

    //waitpid();

#include <stdio.h>
#include <stdlib.h>  //malloc,exit
#include <unistd.h>  //fork
#include <signal.h>
#include <sys/wait.h>  //waitpid

//信號處理函數
void sig_usr(int signo)
{
    int  status;

    switch(signo)
    {
    case SIGUSR1:
        printf("收到了SIGUSR1信號,進程id=%d!\n",getpid());    
        break;
    
    case SIGCHLD:        
        printf("收到了SIGCHLD信號,進程id=%d!\n",getpid());    
        //掌握和使用waitpid即可;
        //這個waitpid說白了獲取子進程的終止狀態,這樣,子進程就不會成爲殭屍進程了;
        pid_t pid = waitpid(-1,&status,WNOHANG); //第一個參數爲-1,表示等待任何子進程,
                                      //第二個參數:保存子進程的狀態信息(大家如果想詳細瞭解,可以百度一下)。
                                       //第三個參數:提供額外選項,WNOHANG表示不要阻塞,讓這個waitpid()立即返回
        if(pid == 0)       //子進程沒結束,會立即返回這個數字,但這裏應該不是這個數字                        
            return;
        if(pid == -1)      //這表示這個waitpid調用有錯誤,有錯誤也理解返回出去,我們管不了這麼多
            return;
        //走到這裏,表示  成功,那也return吧
        return;   
        break;    
    } //end switch
}

int main(int argc, char *const *argv)
{
    pid_t  pid;

    printf("進程開始執行!\n");

    //先簡單處理一個信號
    if(signal(SIGUSR1,sig_usr) == SIG_ERR)  //系統函數,參數1:是個信號,參數2:是個函數指針,代表一個針對該信號的捕捉處理函數
    {
        printf("無法捕捉SIGUSR1信號!\n");
        exit(1);
    }

    if(signal(SIGCHLD,sig_usr) == SIG_ERR) 
    {
        printf("無法捕捉SIGCHLD信號!\n");
        exit(1);
    }

    //---------------------------------
    pid = fork();  //創建一個子進程

    //要判斷子進程是否創建成功
    if(pid < 0)
    {
        printf("子進程創建失敗,很遺憾!\n");
        exit(1);
    }

    //現在,父進程和子進程同時開始 運行了 
    for(;;)
    {        
        sleep(1); //休息1秒
        printf("休息1秒,進程id=%d!\n",getpid());
    }
    printf("再見了!\n");
    return 0;
}

二:fork()函數進一步認識

//b)fork()產生新進程的速度非常快,fork()產生的新進程並不複製原進程的內存空間,而是和

     //原進程(父進程)一起共享一個內存空間,但這個內存空間的特性是“寫時複製”,也就是說:

     //原來的進程和fork()出來的子進程可以同時、自由的讀取內存,但如果子進程(父進程)對

     //內存進行修改的話,那麼這個內存就會複製一份給該進程單獨使用,以免影響到共享這個內存空間的

     //其他進程使用;

 三:完善一下fork()代碼

//fork()回返回兩次:父進程中返回一次,子進程中返回一次,而且,fork()在父進程中返回的值和在子進程中返回的值是不同的

    //子進程的fork()返回值是0;

    //父進程的fork()返回值是新建立的子進程的ID,因爲全局量g_mygbltest的值發生改變,導致主,子進程內存被單獨的分開,所以每個的

     //g_mygbltest值也不同;

#include <stdio.h>
#include <stdlib.h>  //malloc,exit
#include <unistd.h>  //fork
#include <signal.h>

int g_mygbltest = 0;
int main(int argc, char *const *argv)
{
    pid_t  pid;
    printf("進程開始執行!\n");   
    //---------------------------------
    pid = fork();  //創建一個子進程

    //要判斷子進程是否創建成功
    if(pid < 0)
    {
        printf("子進程創建失敗,很遺憾!\n");
        exit(1);
    }

    //現在,父進程和子進程同時開始 運行了 
    //for(;;)
    //{        
    //    sleep(1); //休息1秒
    //    printf("休息1秒,進程id=%d!\n",getpid());
    //}
    //printf("再見了!\n");

    //走到這裏,fork()成功,執行後續代碼的可能是父進程,也可能是子進程
    if(pid == 0)
    {
        //子進程,因爲子進程的fork()返回值會是0;
        //這裏專門針對子進程的處理代碼
        while(1)
        {
            g_mygbltest++;
            sleep(1); //休息1秒
            printf("真是太高興了,我是子進程的,我的進程id=%d,g_mygbltest=%d!\n",getpid(),g_mygbltest);
        }
    }
    else
    {
        //這裏就是父進程,因爲父進程的fork()返回值會 > 0(實際返回的是子進id程)
        //這是專門針對父進程的處理代碼
        while(1)
        {
            g_mygbltest++;
            sleep(5); //休息5秒
            printf("......。。,我是父進程的,我的進程id=%d,g_mygbltest=%d!\n",getpid(),g_mygbltest);
        }
    }

    return 0;
}


(3.1)一個和fork()執行有關的邏輯判斷(短路求值)

 //||或:有1出1,全0出0;

 //&&與:全1出1,有0出0;

#include <stdio.h>
#include <stdlib.h>  //malloc,exit
#include <unistd.h>  //fork
#include <signal.h>

int main(int argc, char *const *argv)
{
    
    fork();  //一般fork都會成功所以不判斷返回值了,我們假定成功
    fork();

    //((fork() && fork()) || (fork() && fork()));
    //printf("每個實際用戶ID的最大進程數=%ld\n",sysconf(_SC_CHILD_MAX));

    
    for(;;)
    {        
        sleep(1); //休息1秒
        printf("休息1秒,進程id=%d!\n",getpid());
    }
    printf("再見了!\n");
    return 0;
}


四:fork()失敗的可能性

    //a)系統中進程太多

    //缺省情況,最大的pid:32767

 

    //b)每個用戶有個允許開啓的進程總數;

    //7788

 

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