進程式控制制

来源:https://www.cnblogs.com/songhe364826110/archive/2019/08/29/11432253.html
-Advertisement-
Play Games

1. 程式和進程 什麼是程式?什麼是進程? 程式是電腦存儲系統中的數據文件,如源代碼程式和可執行程式 進程是程式關於某個數據集合的一次運行活動,是程式執行後得到的一個實體 在當代操作系統中,進程是資源分配的基本單位 程式和進程有什麼聯繫? 沒有程式就沒有進程;但有了程式,未必就會有進程,如程式不運 ...


1. 程式和進程

什麼是程式?什麼是進程?

  • 程式是電腦存儲系統中的數據文件,如源代碼程式和可執行程式
  • 進程是程式關於某個數據集合的一次運行活動,是程式執行後得到的一個實體
  • 在當代操作系統中,進程是資源分配的基本單位

程式和進程有什麼聯繫?

  • 沒有程式就沒有進程;但有了程式,未必就會有進程,如程式不運行、程式本身是動態庫等
  • 一個程式可能對應多個進程,如記事本程式多次運行,產生多個記事本進程
  • 一個進程可能包含多個程式,如一個程式依賴多個動態庫,每個動態庫都是一個程式

2. 進程狀態

進程三態模型:就緒、阻塞、運行。

  • 就緒:進程已經做好了一切準備,一旦得到CPU,就會開始運行
  • 阻塞:進程正在等待某一事件發生(如共用資源被釋放、IO完成)而停止運行,在事件發生前,即使得到CPU也無法運行
  • 運行:進程擁有CPU控制權,並正在運行

進程五態模型:與三態模型相比,多了新建、終止兩種狀態。

  • 新建:進程還未創建完畢,不能被系統調度
  • 終止:進程已結束運行,正在回收系統資源

3. 進程標識

每個進程都有一個非負整數的進程ID(pid),作為識別不同進程的唯一標識。
此外,每個進程還有一些其他標識符,包括父進程ID(ppid)、實際用戶ID(uid)、有效用戶ID(euid)、實際組ID(gid)、有效組ID(egid)。

#include <unistd>

pid_t getpid();   //返回值:調用進程的進程ID
pid_t getppid();  //返回值:調用進程的父進程ID
uid_t getuid();   //返回值:調用進程的實際用戶ID
uid_t geteuid();  //返回值:調用進程的有效用戶ID
uid_t getgid();   //返回值:調用進程的實際組ID
uid_t getegid();  //返回值:調用進程的有效組ID

4. 進程創建

一個現有的進程可以調用fork函數創建一個新進程,這個新進程叫做子進程,調用fork的進程叫做父進程。

  • 子進程獲得父進程數據空間、堆、棧的副本
  • 父進程和子進程共用代碼段、文件描述符和文件偏移量
#include <unistd.h>

pid_t fork();  //若成功:子進程返回0,父進程返回子進程ID;若出錯,返回-1

fork的特點為:一次調用,兩次返回。

  • 父進程返回子進程ID的原因:父進程可以有多個子進程,但父進程不能通過函數獲得其所有子進程的ID,因為沒有這樣的函數
  • 子進程返回0的原因:一個進程只會有一個父進程,並且子進程還可以調用getppid獲得其父進程ID

fork有以下兩種用法:

  • 父進程和子進程同時執行不同的代碼段
  • 子進程從fork返回後立即調用exec,執行另一個不同的程式

fork成功返回後,父進程和子進程繼續執行後面的代碼,但父子進程誰先執行,這點是不確定的。

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

int globvar = 10;

int main()
{
    int var = 5;

    pid_t pid = fork();

    if (pid > 0)
    {
        sleep(2); //父進程休眠2秒,讓子進程先運行
    }
    else if (pid == 0)
    {
        globvar++;
        var++;
    }

    printf("pid = %d, globvar = %d, var = %d\n", getpid(), globvar, var);

    return 0;
}

5. 進程終止

正常終止方式:

  • 從main函數return
  • 調用exit()、_Exit()、_exit()(對於linux,後兩個函數是同義的)
  • 進程的最後一個線程在其啟動常式中調用return或pthread_exit()

異常終止方式:

  • 調用abort()以產生SIGABRT信號
  • 進程接收到某些信號
  • 進程的最後一個線程對pthread_cancel()請求做出響應

6. 避免僵屍進程

僵屍進程的產生與危害

  • 一個已經終止、但是其父進程尚未對其進行善後處理的進程,稱為僵屍進程
  • 子進程退出時,內核會釋放它占用的記憶體等資源,但是仍然保留了一些信息,如進程ID
  • 內核為終止子進程保留的信息直到父進程調用wait或waitpid時才會釋放
  • 如果父進程沒有調用wait或waitpid,那麼已經終止的子進程就會變成僵屍進程,其占用的進程ID會無法釋放
  • 大量的僵屍進程可能會使系統沒有可用的進程ID,從而導致系統無法創建新進程

wait函數

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status); //成功返回終止子進程ID,失敗返回-1

當在父進程中調用了wait時:

  • 如果所有子進程都還在運行,則父進程阻塞
  • 如果有任意子進程終止,則取得其終止狀態並立即返回
  • 如果父進程沒有子進程,則立即出錯返回

如果wait的參數status不為NULL,那麼子進程的終止狀態就存放在它指向的記憶體中,如果不關心終止狀態,可以將status指定為NULL。

waitpid函數

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options); //成功返回終止子進程ID,失敗返回-1或0

waitpid的第二個參數status用法和wait一樣,但waitpid相比於wait的不同之處在於:

  • pid > 0時,waitpid可以等待由pid指定的特定子進程
  • options == WNOHANG時,若pid指定的子進程尚未終止,waitpid不會阻塞,而是立即返回0
  • pid == -1 && options == 0時,waitpid等價於wait

雖然waitpid可以實現非阻塞版本的wait,但也存在一個缺陷:如果子進程在父進程waitpid(pid, NULL, WNOHANGE)之後才終止,那麼即使父進程尚未結束,也不會給子進程收屍,也就是說,終止的子進程會一直處於僵屍進程狀態,直到父進程退出。

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

int main()
{    
    pid_t pid = fork();
    
    if (pid == 0)
    {
        sleep(2); //確保父進程執行waitpid時子進程還在休眠
        printf("child %d exit\n", getpid());  
        exit(0);      
    }
        
    if (waitpid(pid, NULL, WNOHANG) == 0)
    {
        printf("waitpid return before child exit\n");
    }
    
    sleep(300);   //雖然waitpid不阻塞,但在父進程終止前,子進程pid會一直是僵屍進程
        
    return 0;
}

如果既要保證父進程不阻塞等待子進程終止,也不希望子進程處於僵屍狀態直到父進程終止,可以採用調用兩次fork的訣竅,其核心思路為:

  • 進程A調用fork產生子進程B,然後立即調用waitpid(pid, NULL, 0),等待進程B終止
  • 進程B再次調用fork產生子進程C,然後立即調用exit(0)終止(必須確保進程B在進程C之前終止)
  • 進程A隨即解除waitpid阻塞,對進程B收屍處理
  • 由於父進程提前終止,進程C由init收養,其終止時也會由init收屍
  • 此時,進程A和進程C就成為相互獨立、互不幹擾的兩個進程,兩者各司其職,分別執行不同的處理
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {
        if ((pid = fork()) > 0)
        {
            exit(0); //子進程B再次調用fork後立即終止,只留下孫子進程C
        }
        
        /*孫子進程C由init進程收養*/
        sleep(2);
        printf("I'm second child %d, my parent bacomes %d\n", getpid(), getppid());
        
        exit(0);
    }
    
    printf("I'm parent %d\n", getpid());
    
    if (waitpid(pid, NULL, 0) == pid) //父進程A立即調用waitpid等待子進程B終止
    {
        printf("first child %d exit\n", pid);
    }
    
    /*此時,進程A和進程C之間就沒有了繼承關係,兩者相互獨立,互不幹擾,各司其職*/
    
    sleep(300);
    
    return 0;
}

從上圖執行結果可以看出:

  • 進程B(pid=9293)已經在進程列表中找不到了,說明已經被收屍處理了,沒有產生僵屍進程
  • 進程C(pid=9294)也不在進程列表中了,說明其終止後由init收屍處理了,也沒有產生僵屍進程

7. exec函數族

exec函數及使用規則

上面提到過fork的兩種用法,其中一種是“子進程從fork返回後立即調用exec,執行另一個不同的程式”。

  • 當進程調用exec函數時,其執行的程式將完全替換為exec指定的新程式,而新程式則從其main()開始執行。
  • 因為exec並不創建新進程,所以替換前後的進程ID不會改變,exec只是用磁碟上的一個新程式替換了調用進程的代碼段、數據段和堆棧。

有7個不同的exec函數可供使用,它們統稱為exec函數族,可以根據需要調用這7個函數中的任意一個。

#include <unistd.h>

/*7個函數返回值均為:若成功,不返回;若失敗,返回-1*/

//以路徑名為參數
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/);
int execve(const char *path, char *const argv[], char *const envp[]);

//以文件名為參數
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);

//以文件描述符為參數
int fexecve(int fd, char *const argv[], char *const envp[]);

這些函數的命名是有規律的:exec[必選:l or v][可選:p or e](fexecve作為特例單獨拎出來)
這些函數的參數用於指定新程式的相關信息,分為3部分:可執行程式、命令行參數、環境變數,具體使用規則為:
【必選項l or v】

  • 帶l,各個命令行參數必須以','間隔,最後一個命令行參數必須是NULL
  • 帶v,需要將l後續的各個命令行參數(包括最後的NULL)構造成一個指針數組,然後以該指針數組作為參數

【可選項p】

  • 不帶p,以可執行程式的路徑名path作為參數
  • 帶p,以可執行程式的文件名file作為參數,如果file中包含/,則視作路徑名,否則從PATH環境變數指定的目錄中搜索可執行程式

【可選項e】

  • 不帶e,複製調用進程的環境變數給新程式使用
  • 帶e,需要傳遞環境變數給新程式使用

【fexecve特例】

  • 尾碼ve含義和上面一樣
  • 首碼f代表新程式由文件描述符fd指定

exec函數使用示例

/* filename - execl.c */

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

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {
        execl("/bin/echo", "echo", "executed by execl", NULL);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}
/* filename - execv.c */

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

char *execv_argv[] = 
{
    "echo",
    "executed by execv",
    NULL
};

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {      
        execv("/bin/echo", execv_argv);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}
/* filename - execlp.c */

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

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {       
        execlp("echo", "echo", "executed by execlp", NULL);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}
/* filename - execvp.c */

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

char *execvp_argv[] = 
{
    "echo",
    "executed by execvp",
    NULL
};

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {
        execvp("echo", execvp_argv);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}
/* filename - execle.c */

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

char *env[] = 
{
    "PATH=/home/delphi",
    "USER=execle",
    NULL
};

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {
        execle("/usr/bin/env", "env", NULL, env);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}
/* filename - execve.c */

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

char *execve_argv[] = 
{
    "env",
    NULL
};

char *env[] = 
{
    "PATH=/home/delphi",
    "USER=execve",
    NULL
};

int main()
{
    pid_t pid = fork();
    
    if (pid == 0)
    {
        execve("/usr/bin/env", execve_argv, env);
    }
    
    waitpid(pid, NULL, 0);
    
    return 0;
}

8. system函數

#include <stdlib.h>

int system(const char *command);

system返回值

在Unix系統中,system在其內部實現調用了fork、exec和waitpid,因此有3種返回值。

  • 如果fork失敗,或者waitpid返回除EINTR之外的錯誤,則system返回-1,並且設置errno以指示錯誤類型
  • 如果exec失敗,比如被信號中斷,或者command命令不存在,system返回127
  • 如果fork、exec和waitpid都成功,system的返回值是shell的終止狀態,即command通過exit或return返回的值

下麵通過一個system的簡易實現,來幫助理解該函數的返回值。

int system(const char * cmdstring)
{
    pid_t pid;
    int status;

    if (cmdstring == NULL)
    {
        return (1); //如果cmdstring為空,返回非零值,一般為1
    }

    if ((pid = fork()) < 0)
    {
        status = -1; //fork失敗,返回-1
    }
    else if (pid == 0)
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); // exec執行失敗返回127,註意exec只在失敗時才返回現在的進程,成功的話現在的進程就不存在啦~~
    }
    else //父進程
    {
        while (waitpid(pid, &status, 0) < 0)
        {
            if (errno != EINTR)
            {
                status = -1; //如果waitpid被信號中斷,則返回-1
                break;
            }
        }
    }

    return status; //如果waitpid成功,則返回子進程的返回狀態
}

仔細看完這個system函數的簡單實現,該函數的返回值就清晰了吧,那麼什麼時候system()返回0呢?答案是只在command命令返回0時。

system使用示例

#include <stdlib.h>

int main()
{
    system("ls -l");
    system("cat func.c");    
    system("gcc -o func.out func.c");
    system("ls -l");
    system("echo main.out begin system func.out");
    system("./func.out");
    
    return 0;
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在Word中,藉助內容控制項,可設計出具有特定功能的文檔或模板。以下表格中簡單介紹了幾種常用的內容控制項: 名稱 簡介 下拉列表內容控制項 下拉列表包含了一個預先定義好的列表。和組合框不同的是下拉列表不允許用戶編輯項。 純文本內容控制項 純文本內容控制項只能包含文本,不能包含其他項,例如表格、圖片或其他內容控 ...
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 每次寫while(true)的時候會不會心虛? 特別邏輯稍微複雜一點 ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 效果 實現 添加兩條Y軸 ZedGraph是預設帶2條Y軸的,所以其自帶YAxis屬 ...
  • c# 非同步( Async ) 不是多線程 c# 非同步( Async ) 不是多線程 誤解 async 在調試 xxxxAsync() 方法的時候,常常會看到調試器界面中會多出一些線程,直覺上誤認為 Async 冠名的函數是多線程。 對於 StringReader 中的 ReadAsync() 方法的 ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 在上面實現曲線相關屬性的設置的基礎上,實現的效果如下: 什麼是曲線標簽,就是上圖中標 ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 在上面實現曲線相關屬性的設置的基礎上,實現的字體效果如下: 實現效果參照原文。 現在 ...
  • 當我們用很多服務時,各個服務間的調用關係是怎麼樣的?各個服務單調用的順序\時間性能怎麼樣?服務出錯了,到底是哪個服務引起的?這些問題我們用什麼方案解決呢,以前的方式是各個系統自己單獨做日誌,出了問題從暴出問題的服務開始一個一個服務的排查,耗時耗力,有些日誌不全的,還不一定查得出來。好在現在有Skyw ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...