?個人主頁: Yohifo
??所屬專欄: Linux學(xué)習(xí)之旅
??每篇一句: 圖片來源
??操作環(huán)境: CentOS 7.6 阿里云遠程服務(wù)器
- Good judgment comes from experience, and a lot of that comes from bad judgment.
- 好的判斷力來自經(jīng)驗,其中很多來自糟糕的判斷力。
??前言
進程
創(chuàng)建后,需要對其進行合理管理,光靠 OS
是無法滿足我們的需求的,此時可以運用 進程
控制相關(guān)知識,對 進程
進行手動管理,如創(chuàng)建 進程
、終止 進制
、等待 進程
等,其中等待 進程
可以有效解決僵尸 進程
問題
汽車的中控臺,可以對汽車進行各種操作
???正文
本文涉及的代碼都是以 C語言 實現(xiàn)的
1、進程創(chuàng)建
在學(xué)習(xí) 進程控制
相關(guān)知識前,先要對回顧如何創(chuàng)建 進程
,涉及一個重要的函數(shù) fork
1.1、fork函數(shù)
#include <unistd.h> //所需頭文件
pid_t fork(void); //fork 函數(shù)
fork
函數(shù)的作用是在當(dāng)前 進程
下,創(chuàng)建一個 子進程
,子進程
創(chuàng)建后,會為其分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)(PCB
),將 父進程
中的數(shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝給 子進程
,同時還會繼承 父進程
中的環(huán)境變量表
- 進程具有獨立性,即使是父子進程,也是兩個完全不同的進程,擁有各自的
PCB
- 假設(shè)
子進程
發(fā)生改寫行為,會觸發(fā)寫時拷貝機制
fork
函數(shù)返回類型為 pid_t
,相當(dāng)于 typedef int
,不過是專門用于進程的,同時它擁有兩個返回值:
- 如果進程創(chuàng)建失敗,返回
-1
-
進程創(chuàng)建成功后
- 給子進程返回
0
- 給父進程返回子進程的
PID
值
- 給子進程返回
通過代碼理解 進程
創(chuàng)建
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //進程等待相關(guān)函數(shù)頭文件
int main()
{
//創(chuàng)建兩個子進程
pid_t id1 = fork();
if(id1 == 0)
{
//子進程創(chuàng)建成功,創(chuàng)建孫子進程
pid_t id2 = fork();
if(id2 == 0)
{
printf("我是孫子進程,PID:%d PPID:%d\n", getpid(), getppid());
exit(1); //孫子進程運行結(jié)束后,退出
}
wait(0); //等待孫子進程運行結(jié)束
printf("我是子進程,PID:%d PPID:%d\n", getpid(), getppid());
exit(1); //子進程運行結(jié)束后,退出
}
wait(0); //等待子進程運行結(jié)束
printf("我是父進程,PID:%d PPID:%d\n", getpid(), getppid());
return 0; //父進程運行結(jié)束后,退出
}
觀察結(jié)果不難發(fā)現(xiàn),兩個子進程已經(jīng)成功創(chuàng)建,但最晚創(chuàng)建的進程,總是最先運行,這是因為 fork
創(chuàng)建進程后,先執(zhí)行哪個進程取決于調(diào)度器
得到子進程后,此時可以在一個程序中同時執(zhí)行兩個進程!(父進程非阻塞的情況下)
注意:fork 可能創(chuàng)建進程失敗
- 系統(tǒng)中的進程過多時
- 實際用戶的進程數(shù)超過了限制
1.2、寫時拷貝
在【進程地址空間】一文中,談到了寫時拷貝機制,實現(xiàn)原理就是通過 頁表+MMU
機制,對不同的進程進行空間尋址,達到出現(xiàn)改寫行為時,父子進程使用不同真實空間的效果
驗證寫時拷貝現(xiàn)象很簡單,創(chuàng)建子進程后,使其對生命周期長的變量作出修改,再觀察父子進程的結(jié)果即可
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //進程等待相關(guān)函數(shù)頭文件
const char* ps = "This is an Apple"; //全局屬性
int main()
{
pid_t id = fork();
if(id == 0)
{
ps = "This is a Banana"; //改寫
printf("我是子進程,我認為:%s\n", ps);
exit(0); //子進程退出
}
wait(0); //等待子進程退出
printf("我是父進程,我認為:%s\n", ps);
return 0;
}
不難發(fā)現(xiàn),子進程對指針 ps
指向內(nèi)容做出改變時,父進程并不受影響,這就是寫時拷貝機制
- 通過地址打印,發(fā)現(xiàn)父子進程中的
ps
地址一致,因為此時是虛擬地址 - 在虛擬地址相同的情況下,真實地址是不同的,得益于
頁表+MMU
機制尋址不同的空間
寫時拷貝機制本質(zhì)上是一種按需申請資源的策略
注意:
- 寫時拷貝不止可以發(fā)生在常規(guī)棧區(qū)、堆區(qū),還能發(fā)生在只讀的數(shù)據(jù)段和數(shù)據(jù)段
- 寫時拷貝后,生成的是副本,不會對原數(shù)據(jù)造成影響
2、進程終止
假設(shè)某個進程陷入了死循環(huán)狀態(tài),可以通過特定方法終止此程序,如在命令行中莫名其妙輸入了一個指令,導(dǎo)致出現(xiàn)非正常情況,可以通過 ctrl + c
終止當(dāng)前進程;對于自己寫的程序,有多種終止方法,程序退出時,還會有一個退出碼,供 父進程
接收
2.1、退出碼
echo $?
main
函數(shù)中的最后一條語句 return 0
表示當(dāng)前程序的退出碼,0
表示程序正常退出,可以通過指令 echo $?
查看最近一次子進程運行的 退出碼
退出碼是給父進程看的,可以判斷子進程是否成功運行
子進程運行情況:
- 運行失敗或異常終止,此時出現(xiàn)終止信號,無退出碼
- 運行成功,返回退出碼,可能出現(xiàn)結(jié)果錯誤的情況
進程退出后,OS
會釋放對應(yīng)的 內(nèi)核數(shù)據(jù)結(jié)構(gòu)+代碼和數(shù)據(jù)
main
函數(shù)退出,表示整個程序退出,而程序中的函數(shù)退出,僅表示該函數(shù)運行結(jié)束
2.2、退出方式
對一個正在運行中的進程,存在兩種終止方式:外部終止和內(nèi)部終止,外部終止時,通過 kill -9 PID
指令,強行終止正在運行中的程序,或者通過 ctrl + c
終止前臺運行中的程序
內(nèi)部終止是通過函數(shù) exit()
或 _exit()
實現(xiàn)的
之前在程序編寫時,發(fā)生錯誤行為時,可以通過 exit(-1)
的方式結(jié)束程序運行,代碼中任意地方調(diào)用此函數(shù),都可以提前終止程序
void exit(int status);
void _exit(int status);
這兩個退出函數(shù),從本質(zhì)上來說,沒有區(qū)別,都是退出進程,但在實際使用時,還是存在一些區(qū)別,推薦使用 exit()
比如在下面這段程序中,分別使用 exit()
和 _exit()
觀察運行結(jié)果
int main()
{
printf("You can see me");
//exit(-1); //退出程序
//_exit(-1); //第二個函數(shù)
return 0;
}
使用 exit()
時,輸出語句
使用 _exit()
時,并沒有任何語句輸出
原因:
exit()
是對_exit()
做的封裝實現(xiàn)_exit()
就只是單純的退出程序- 而
exit()
在退出之前還會做一些事,比如沖刷緩沖區(qū),再調(diào)用_exit()
- 程序中輸出語句位于輸出緩沖區(qū),不沖刷的話,是不會輸出內(nèi)容的
3、進程等待
僵尸進程
是一個比較麻煩的問題,如果不對其做出處理,僵尸進程
就會越來越多,導(dǎo)致 內(nèi)存泄漏
和 標(biāo)識符
占用問題
3.1、等待原因
子進程運行結(jié)束后,父進程沒有等待并接收其退出碼和退出狀態(tài),OS
無法釋放對應(yīng)的 內(nèi)核數(shù)據(jù)結(jié)構(gòu)+代碼和數(shù)據(jù),出現(xiàn) 僵尸進程
為了避免這種情況的出現(xiàn),父進程可以通過函數(shù)等待子進程運行結(jié)束,此時父進程屬于阻塞狀態(tài)
注意:
- 進程的退出狀態(tài)是必要的
- 進程的執(zhí)行結(jié)果是非必要的
也就是說,父進程必須對子進程負責(zé),確保子進程不會連累 OS
,而子進程執(zhí)行的結(jié)果是否正確,需要我們自行判斷
3.2、等待函數(shù)
系統(tǒng)提供的父進程等待函數(shù)有兩個 wait()
和 waitpid()
,后者比較常用
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
wait()
函數(shù)前面已經(jīng)演示過了,這里著重介紹 waitpid()
返回值及其參數(shù)wait()
中的返回值和參數(shù),包含在 waitpid()
中
返回值:
- 等待成功時,返回
>0
的值 - 等待失敗時,返回
-1
- 等待中,返回
0
參數(shù)列表:
pid
表示所等子進程的PID
status
表示狀態(tài),為整型,其中高16
位不管,低16
位中,次低8
位表示退出碼,第7
位表示core dump
,低7
位表示終止信號options
為選項,比如可以選擇父進程是否需要阻塞等待子進程退出
需要特別注意 status
通過代碼演示 waitpid()
的使用
int main()
{
//演示 waitpid()
pid_t id = fork(); //創(chuàng)建子進程
if(id == 0)
{
int time = 5;
int n = 0;
while(n < time)
{
printf("我是子進程,我已經(jīng)運行了:%d秒 PID:%d PPID:%d\n", n + 1, getpid(), getppid());
sleep(1);
n++;
}
exit(244); //子進程退出
}
int status = 0; //狀態(tài)
pid_t ret = waitpid(id, &status, 0); //參數(shù)3 為0,為默認選項
if(ret == -1)
{
printf("進程等待失??!進程不存在!\n");
}
else if(ret == 0)
{
printf("子進程還在運行中!\n");
}
else
{
printf("進程等待成功,子進程已被回收\n");
}
printf("我是父進程, PID:%d PPID:%d\n", getpid(), getppid());
//通過 status 判斷子進程運行情況
if((status & 0x7F))
{
printf("子進程異常退出,core dump:%d 退出信號:%d\n", (status >> 7) & 1, (status & 0x7F));
}
else
{
printf("子進程正常退出,退出碼:%d\n", (status >> 8) & 0xFF);
}
return 0;
}
不發(fā)出終止信號,讓程序自然跑完
發(fā)出終止信號,強行終止進程
waitpid()
的返回值可以幫助我們判斷此時進程屬于什么狀態(tài)(在下一份測試代碼中表現(xiàn)更明顯),而 status
的不同部分,可以幫助我們判斷子進程因何而終止,并獲取 退出碼(終止信號)
在進程的
PCB
中,包含了int _exit_code
和int _exit_signal
這兩個信息,可以通過對status
的位操作間接獲取其中的值
注意:
status
的位操作需要多畫圖理解- 正常退出時,終止信號為0;異常終止時,退出碼沒有,兩者是互斥的
code dump
現(xiàn)階段用不到,但它是伴隨著終止信號出現(xiàn)的
如果覺得 (status >> 8) & 0xFF
和 (status & 0x7F)
這兩個位運算難記,系統(tǒng)還提供了兩個宏來簡化代碼
WIFEXITED(status)
判斷進程退出情況,當(dāng)宏為真時,表示進程正常退出WEXITSTATUS(status)
相當(dāng)于(status >> 8) & 0xFF
,直接獲取退出碼
3.3、等待時執(zhí)行
//options 參數(shù)
WNOHANG
//比如
waitpid(id, &status, WNOHANG);
父進程并非需要一直等待子進程運行結(jié)束(阻塞等待),可以通過設(shè)置 options
參數(shù),進程解除 夯
狀態(tài),父進程變成 等待輪詢
狀態(tài),不斷獲取子進程狀態(tài)(是否退出),如果沒退出,就可以干點其他事
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //進程等待相關(guān)函數(shù)頭文件
int main()
{
//演示 waitpid()
pid_t id = fork(); //創(chuàng)建子進程
if(id == 0)
{
int time = 9;
int n = 0;
while(n < time)
{
printf("我是子進程,我已經(jīng)運行了:%d秒 PID:%d PPID:%d\n", n + 1, getpid(), getppid());
sleep(1);
n++;
}
exit(244); //子進程退出
}
int status = 0; //狀態(tài)
pid_t ret = 0;
while(1)
{
ret = waitpid(id, &status, WNOHANG); //參數(shù)3 設(shè)置為非阻塞狀態(tài)
if(ret == -1)
{
printf("進程等待失敗!進程不存在!\n");
break;
}
else if(ret == 0)
{
printf("子進程還在運行中!\n");
printf("我可以干一些其他任務(wù)\n");
sleep(3);
}
else
{
printf("進程等待成功,子進程已被回收\n");
//通過 status 判斷子進程運行情況
if(WIFEXITED(status))
{
printf("子進程正常退出,退出碼:%d\n", WEXITSTATUS(status));
break;
}
else
{
printf("子進程異常退出,code dump:%d 退出信號:%d\n", (status >> 7) & 1, (status & 0x7F));
break;
}
}
}
return 0;
}
程序正常運行,父進程通過 等待輪詢
的方式,在子進程執(zhí)行的同時,執(zhí)行其他任務(wù)
當(dāng)然也可以通過 kill -9 PID
命令使子進程異常終止
可以看到程序能分別捕捉到正常和異常的情況
注意:如果不寫進程等待函數(shù),會引發(fā)僵尸進程問題
??總結(jié)
以上就是關(guān)于 Linux進程控制(創(chuàng)建、終止、等待) 的相關(guān)知識了,我們學(xué)習(xí)了 子進程
是如何被創(chuàng)建的,創(chuàng)建后又是如何終止的,以及 子進程
終止 父進程
需要做些什么,有了這些知識后,在對 進程
進行操作時能更加靈活和全面
如果你覺得本文寫的還不錯的話,期待留下一個小小的贊??,你的支持是我分享的最大動力!
如果本文有不足或錯誤的地方,隨時歡迎指出,我會在第一時間改正
文章來源:http://www.zghlxwxcb.cn/news/detail-782854.html
相關(guān)文章推薦
Linux進程學(xué)習(xí)【進程地址】
Linux進程學(xué)習(xí)【環(huán)境變量】
Linux進程學(xué)習(xí)【進程狀態(tài)】
Linux進程學(xué)習(xí)【基本認知】
===============
Linux工具學(xué)習(xí)之【gdb】
Linux工具學(xué)習(xí)之【git】
Linux工具學(xué)習(xí)之【gcc/g++】
Linux工具學(xué)習(xí)之【vim】
文章來源地址http://www.zghlxwxcb.cn/news/detail-782854.html
到了這里,關(guān)于Linux進程控制【創(chuàng)建、終止、等待】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!