程序控制
程序建立
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;
}
執行結果
程序終止
程序退出碼
程序退出碼是程序結束時返回的一個整數值,用來標定程序執行的結果是否正確。通常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;
}
從上面的結果發現為什麼只有第一次的退出碼是1
,而之後都是0
呢?那是因為echo
本身就是一個程序,當第一次執行時,它顯示的mytest
的退出碼,當第二次執行時,它顯示的是上一個echo
的退出碼,由於上一個echo
程序是正常退出的,那麼肯定顯示的就是0
了。
常見的退出方法
- 從
main
返回 - 呼叫exit()
- _exit()
其中exit()是庫函式,而_exit()是系統呼叫。exit()會在程序終止前重新整理並關閉所有開啟的緩衝區,將未輸出的資料輸出;_exit()
則不會重新整理緩衝區,直接終止程序,可能導致未輸出的資料丟失。
示例:
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:
- 是一個點陣圖結構,用於獲取子程序的終止狀態資訊。
- 可以透過宏(如
WIFEXITED
、WEXITSTATUS
等)來解析和提取具體的狀態細節。
- options:
- WNOHANG: 非阻塞等待,若pid指定的子程序沒有結束,則waitpid()函式返回0,不予以等待。若正常結束,則返回該子程序的ID。
status引數
在返回的
status
值中,低16位包含了子程序的終止資訊。- 低 8 位(即 0 - 7 位):如果子程序是透過
exit
正常終止的,這 8 位儲存了子程序的退出狀態碼。可以透過WEXITSTATUS(status)
宏來獲取。如果WIFEXITED
宏對wait
或waitpid
返回的狀態值進行判斷後返回非零值,就表示子程序正常退出。 - 高 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); }
- 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 = 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);
}
從上面的結果可以看出,在前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);
}
}
這段程式碼和上面的差不多,只不過子程序在執行任務的時候,父程序是以非阻塞的形式去等待子程序的。