[Linux]程序控制

羡鱼OvO發表於2024-11-21

程序控制

程序建立

fork函式

作用

fork函式的作用是用來建立一個新程序,新程序被稱為子程序,而原來的程序稱為父程序。

返回值

fork函式有兩個返回值,給父程序返回子程序pid,給子程序返回0。

  • 為什麼有兩個返回值?

    在執行fork函式的時候,當fork函式的核心邏輯已經執行完成,準備return時,子程序就已經被建立了出來,此時就已經分為了兩個執行流,所以return就被執行了兩次。

  • 為什麼給父程序返回子程序pid,給子程序返回0?

    對於子程序來說,它的父程序是唯一的;而對於父程序來說,它可能會有多個子程序。為了更好的管理子程序,所以給父程序返回的是子程序的pid。

  • 同一個id(儲存fork的返回值),怎麼會同時儲存兩個不同的值?

    當fork函式執行完之後,父子程序誰先返回,就誰先向變數id寫入資料,由於程序具有獨立性,會發生寫時複製。因此,同一個id,地址相同但內容卻不同。

fork呼叫失敗

當系統中的程序超過限制時,fork建立子程序會失敗。下面是一個測試程式碼。

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

int main () 
{
    int cnt = 1;
    while(1) 
    {
        pid_t id = fork();
        if (id == 0)
        {
            while(1)
            {
                sleep(1);
            }
        }
        else if(id < 0)
        {
            printf("fork error!!!\n");
            break;
        }
        cnt++;
        printf("%d\n", cnt);
    }

    return 0;
}

執行結果

[Linux]程序控制

程序終止

程序退出碼

程序退出碼是程序結束時返回的一個整數值,用來標定程序執行的結果是否正確。通常0表示程序正常結束,沒有出現錯誤;非零表示出現了某種異常或錯誤情況,不同的非零值可能代表不同的錯誤型別或原因。

echo $?:用於顯示上一個命令的退出狀態碼。其中$?是一個變數,叫做”退出狀態變數“。

執行如下程式碼

#include <stdio.h>

int Add(int begin, int end)
{
    int sum =  0;
    for (int i = begin; i < end; i++) sum += i;
    return sum;
}

int main()
{
    int ret = Add(0, 100);

    if (ret == 5050) return 0;
    else return 1;
}
[Linux]程序控制

從上面的結果發現為什麼只有第一次的退出碼是1,而之後都是0呢?那是因為echo本身就是一個程序,當第一次執行時,它顯示的mytest的退出碼,當第二次執行時,它顯示的是上一個echo的退出碼,由於上一個echo程序是正常退出的,那麼肯定顯示的就是0了。

常見的退出方法

  1. main返回
  2. 呼叫exit()
  3. _exit()

其中exit()是庫函式,而_exit()是系統呼叫。exit()會在程序終止前重新整理並關閉所有開啟的緩衝區,將未輸出的資料輸出;_exit()則不會重新整理緩衝區,直接終止程序,可能導致未輸出的資料丟失。

示例:

[Linux]程序控制

return退出return是一種更常見的退出程序方法。執行return n等同於執行exit(n),因為呼叫main的執行時函式會將main的返 回值當做exit的引數。

程序等待

子程序退出,父程序如果不管,那麼就有可能造成殭屍程序的問題,進而造成記憶體洩漏;另外,程序一旦變成殭屍程序,那麼使用kill -9命令都無法結束該程序。最後父程序派給子程序的任務完成的如何,父程序同樣要知道,例如子程序是否正常退出,執行結果是否正確。因此父程序需要透過程序等待的方式回收子程序資源,獲取子程序的退出資訊。

程序等待方法

  • wait()

    pid_t wait(int *status)

    作用:用來暫停父程序的執行,獲取子程序的終止狀態,包括正常結束的退出碼或異常終止的訊號。

    返回值:成功時返回子程序的pid,失敗返回-1。

    示例:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main()
    {
        pid_t id = fork();//建立子程序
        //子程序執行5秒後退出
        if (id == 0)
        {
            int cnt = 5;
            while(cnt)
            {
                printf("子程序,pid:%d, pid:%d, cnt:%d\n", getpid(), getppid(), cnt);
                cnt--;
                sleep(1);
            }
            exit(0);
        }
        //父程序
        sleep(8);
        pid_t ret = wait(NULL);
        if (ret > 0)	
        {
            printf("wait success:%d\n", ret);
        }
    }
    
  • waitpid()

    pid_t waitpid(pid_t pid, int *status, int options)

    引數:

    • pid:
      • pid = 1,等待任意一個子程序,與wait等效。
      • pid > 0,等待指定pid的子程序。
    • status:
      • 是一個點陣圖結構,用於獲取子程序的終止狀態資訊。
      • 可以透過宏(如 WIFEXITEDWEXITSTATUS 等)來解析和提取具體的狀態細節。
    • options:
      • WNOHANG: 非阻塞等待,若pid指定的子程序沒有結束,則waitpid()函式返回0,不予以等待。若正常結束,則返回該子程序的ID。

    status引數

    在返回的status值中,低16位包含了子程序的終止資訊。

    1. 低 8 位(即 0 - 7 位):如果子程序是透過 exit 正常終止的,這 8 位儲存了子程序的退出狀態碼。可以透過 WEXITSTATUS(status) 宏來獲取。如果WIFEXITED宏對waitwaitpid返回的狀態值進行判斷後返回非零值,就表示子程序正常退出。
    2. 高 8 位(即 8 - 15 位):如果子程序是由於訊號而終止的,這 8 位儲存了導致子程序終止的訊號編號。可以透過 WTERMSIG(status) 宏來獲取。

    可以使用status & 0xFF獲取它的低八位,使用(status >> 8) & 0xFF獲取高八位。

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    int main()
    {
        pid_t id = fork();//建立子程序
        //子程序執行5秒後退出
        if (id == 0)
        {
            int cnt = 5;
            while(cnt)
            {
                printf("子程序,pid:%d, pid:%d, cnt:%d\n", getpid(), getppid(), cnt);
                cnt--;
                sleep(1);
            }
            exit(10);//退出碼
        }
    
        int status = 0;
        int ret = waitpid(id, &status, 0);//阻塞式等待
        if (ret > 0)
        {
            if (WIFEXITED(status))//判斷是否正常退出
            {
                printf("exit code: %d\n", WEXITSTATUS(status));//獲取退出狀態碼
            }
            else printf("child exit not normal\n");
            printf("wait success, exit code : %d, sig: %d\n", (status>>8) & 0xff, status & 0xff);
        }
    

阻塞等待與非阻塞等待

阻塞等待:當子程序還在執行時,父程序一直在等待子程序結束執行,在等待的過程中父程序不會去執行其他任務。

非阻塞等待:當子程序還在執行時,父程序在等待的途中會去執行其他任務。

示例1:阻塞式等待

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

int main()
{
    pid_t id = fork();//建立子程序
    //子程序執行5秒後退出
    if (id == 0)
    {
        int cnt = 3;
        while(cnt)
        {
            printf("子程序,pid:%d, pid:%d, cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(10);
    }
    //父程序
    sleep(5);
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);//阻塞式等待
    if (ret > 0)
    {
        printf("wait success:%d, sig number: %d, child exit code: %d\n", ret, (status & 0xff), (status >> 8) & 0xff);
    }
    sleep(5);
}
[Linux]程序控制

從上面的結果可以看出,在前3秒的時候子程序正在執行,而父程序在阻塞等待子程序執行結束,第4秒時,子程序執行結束,狀態變為殭屍狀態,第6秒的時候,父程序成功回收子程序,拿到了子程序的退出訊號和退出碼。

示例2:非阻塞輪詢等待

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

int main()
{
    pid_t id = fork();//建立子程序
    //子程序執行5秒後退出
    if (id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("子程序,pid:%d, pid:%d, cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(10);
    }
    //父程序
    int status = 0;
    while (1)
    {       
        pid_t ret = waitpid(id, &status, WNOHANG);//非阻塞式等待
        if (ret == 0)
        {
            printf("wait done, but child is running\n");
        }
        else if (ret > 0)
        {
            printf("wait success:%d, sig number: %d, child exit code: %d\n", ret, (status & 0xff), (status >> 8) & 0xff);
            break;
        }
        else printf("wait failed\n");
        sleep(1);
    }
}

[Linux]程序控制

這段程式碼和上面的差不多,只不過子程序在執行任務的時候,父程序是以非阻塞的形式去等待子程序的。

相關文章