文章目錄
- 進程創建
- 再次認識fork()函數
- fork()函數返回值
- 寫時拷貝
- fork常規?法以及調用失敗的原因
- 進程終?
- 進程終止對應的三種情況
- 進程常?退出?法
- _exit函數
- exit函數
- return退出
- 進程等待
- 進程等待的必要性
- 進程等待的?法
進程創建
再次認識fork()函數
fork函數初識:在linux中fork函數是?常重要的函數,它從已存在進程中創建?個新進程。新進程為?進程,?原進程為?進程。
#include <unistd.h>
pid_t fork(void);
返回值:?進程中返回0,?進程返回?進程id,出錯返回-1
進程調?fork,當控制轉移到內核中的fork代碼后,內核做:
- 分配新的內存塊和內核數據結構給?進程
- 將?進程部分數據結構內容拷???進程
- 添加?進程到系統進程列表當中
- fork返回,開始調度器調度
可以看到, 里面創建了一個進程pid3109,這其實就是子進程。
當?個進程調?fork之后,就有兩個?進制代碼相同的進程。?且它們都運?到相同的地?。但每個進
程都將可以開始它們??的旅程,看如下程序。
int main( void )
{pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
}
運?結果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
這?看到了三?輸出,??before,兩?after。進程43676先打印before消息,然后它有打印after。
另?個after消息有43677打印的。注意到進程43677沒有打印before,為什么呢?如下圖所?
所以,fork之前?進程獨?執?,fork之后,??兩個執?流分別執?。注意,fork之后,誰先執?完全由調度器決定。
fork()函數返回值
- ?進程返回0,
- ?進程返回的是?進程的pid。
寫時拷貝
寫時拷貝(Copy-on-write, COW)是一種優化技術,廣泛應用于計算機系統中,特別是在操作系統、虛擬化和內存管理領域。其主要目的是節省內存資源和提高效率。
工作原理:
寫時拷貝的基本思想是,當多個進程共享相同的資源(例如內存或文件)時,如果一個進程對這些資源進行修改,系統并不會立即為該進程創建資源的副本,而是推遲到該進程真正進行修改時,才為它分配一個新的副本。具體步驟如下:
- 共享資源:多個進程最初可以共享同一塊內存區域或文件(即資源是只讀的)。
- 標記只讀:系統會將這些共享的資源標記為只讀。
- 修改時拷貝:當一個進程嘗試修改共享資源時,操作系統會為該進程創建資源的副本,并將其設為可寫。其他進程仍然使用原始資源,而修改的進程則使用新的副本。
- 繼續共享:如果其他進程繼續只讀訪問原始資源,不會進行拷貝,節省內存和計算資源。
具體的理解可以看下面這一張圖片:
優點:
- 節省內存:由于多個進程或線程可以共享同一資源副本,減少了內存的消耗。
- 提高性能:避免不必要的拷貝操作,只有在修改資源時才進行拷貝,從而提高了效率。
- 提高數據一致性:寫時拷貝確保在修改數據時不會影響其他進程或線程讀取到的數據,避免了數據沖突。
缺點:
- 延遲開銷:在第一次修改資源時,系統需要創建資源的副本,這可能帶來一定的性能開銷。
- 資源消耗:如果多個進程頻繁進行寫操作,系統會進行多次資源拷貝,可能增加資源消耗。
fork常規?法以及調用失敗的原因
- ?個?進程希望復制??,使??進程同時執?不同的代碼段。例如,?進程等待客?端請求,?成?進程來處理請求。
- ?個進程要執??個不同的程序。例如?進程從fork返回后,調?exec函數。
原因:
- 系統中有太多的進程
- 實際??的進程數超過了限制
進程終?
進程終?的本質是釋放系統資源,就是釋放進程申請的相關內核數據結構和對應的數據和代碼。
進程終止對應的三種情況
- 代碼運?完畢,結果正確
- 代碼運?完畢,結果不正確
- 代碼異常終止
進程常?退出?法
正常終?(可以通過 echo $? 查看進程退出碼):
- 從main返回
- 調?exit
- _exit
異常退出:
- ctrl + c,信號終?
退出碼(退出狀態)可以告訴我們最后?次執?的命令的狀態。在命令結束以后,我們可以知道命令是成功完成的還是以錯誤結束的。其基本思想是,程序返回退出代碼 0 時表?執?成功,沒有問題。
代碼 1 或 0 以外的任何代碼都被視為不成功。
下面是Linuxshell常見的退出碼
_exit函數
#include <unistd.h>
void _exit(int status);
參數:status 定義了進程的終?狀態,?進程通過wait來獲取該值
- 說明:雖然status是int,但是僅有低8位可以被?進程所?。所以_exit(-1)時,在終端執?$?發現返回值是255。
exit函數
#include <unistd.h>
void exit(int status);
exit最后也會調?_exit, 但在調?_exit之前,還做了其他?作:
- 執???通過 atexit或on_exit定義的清理函數。
- 關閉所有打開的流,所有的緩存數據均被寫?
- 調?_exit
示例;
int main()
{
printf("hello");
exit(0);
}int main()
{
printf("hello");
_exit(0);
}
上面的結果分別為:
運?結果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
運?結果:
[root@localhost linux]# ./a.out
[root@localhost linux]#
return退出
return是?種更常?的退出進程?法。執?return n等同于執?exit(n),因為調?main的運?時函數會將main的返回值當做 exit的參數。
進程等待
進程等待是指在操作系統中,當一個進程無法繼續執行時,它進入一種阻塞狀態,等待某些條件或事件的發生才能恢復執行。等待通常發生在進程需要等待資源(如CPU、內存、I/O設備等)或與其他進程之間的同步和通信。
進程等待的必要性
-
資源共享與避免沖突:多個進程共享資源時,等待機制確保不會發生沖突,避免競爭條件。
-
進程同步與通信:確保進程按照正確順序執行,例如生產者和消費者模型。
-
CPU資源管理:避免無謂的CPU占用,讓等待的進程釋放CPU,提高系統效率。
-
防止死鎖:通過合理設計等待策略,避免多個進程互相等待,進入死鎖狀態。
-
提升并發性:使系統能夠并發執行多個進程,最大化資源利用。
-
提高系統穩定性:管理進程優先級,保證重要任務及時執行,確保系統穩定運行。
進程等待的?法
- wait?法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待進程pid,失敗返回-1。
參數:
輸出型參數,獲取?進程退出狀態,不關?則可以設置成為NULL
- waitpid?法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當正常返回的時候waitpid返回收集到的?進程的進程ID;
如果設置了選項WNOHANG,?調?中waitpid發現沒有已退出的?進程可收集,則返回0;
如果調?中出錯,則返回-1,這時errno會被設置成相應的值以指?錯誤所在;
參數:
pid:
Pid=-1,等待任?個?進程。與wait等效。
Pid>0.等待其進程ID與pid相等的?進程。
status: 輸出型參數
WIFEXITED(status): 若為正常終??進程返回的狀態,則為真。(查看進程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED?零,提取?進程退出碼。(查看進程的退出碼)
options:默認為0,表?阻塞等待。
WNOHANG: 若pid指定的?進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該?進程的ID。
- 如果?進程已經退出,調?wait/waitpid時,wait/waitpid會?即返回,并且釋放資源,獲得?進程退出信息。
- 如果在任意時刻調?wait/waitpid,?進程存在且正常運?,則進程可能阻塞。
- 如果不存在該?進程,則?即出錯返回。
- 獲取?進程status
- wait和waitpid,都有?個status參數,該參數是?個輸出型參數,由操作系統填充。
- 如果傳遞NULL,表?不關??進程的退出狀態信息。
- 否則,操作系統會根據該參數,將?進程的退出信息反饋給?進程。
- status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16?特位):
進程的阻塞等待?式:
int main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;} else if( pid == 0 ){ //childprintf("child is run, pid is : %d\n",getpid());sleep(5);exit(257);} else{int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S printf("this is test for wait\n");if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}return 0;
}
運?結果:
[root@localhost linux]# ./a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is :1.
進程的?阻塞等待?式:
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)(); // 函數指針類型
std::vector<handler_t> handlers; // 函數指針數組
void fun_one()
{printf("這是?個臨時任務1\n");
}
void fun_two()
{printf("這是?個臨時任務2\n");
}
void Load()
{handlers.push_back(fun_one);handlers.push_back(fun_two);
}
void handler()
{if (handlers.empty())Load();for (auto iter : handlers)iter();
}
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;} else if (pid == 0) { // childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);} else {int status = 0;pid_t ret = 0;do {ret = waitpid(-1, &status, WNOHANG); // ?阻塞式等待 if (ret == 0) {printf("child is running\n");}handler();} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));} else {printf("wait child failed, return.\n");return 1;}}return 0;
}