關(guān)于進程和線程的關(guān)系,之前一口君寫過這幾篇文章,大家可以參考下。
本文從頭帶著大家一起學(xué)習(xí)Linux進程
《搞懂進程組、會話、控制終端關(guān)系,才能明白守護進程干嘛的?》
Linux 進程篇
一、進程相關(guān)概念
了解進程的時候先來了解幾個問題,明白以下問題,就懂了進程的概念
1.什么是程序,什么是進程,兩者之間的區(qū)別?
- 程序是靜態(tài)的概念,gcc ?xxx.c -o pro 磁盤中生成pro文件,叫做程序 ? ? ? ? ? ? ?程序如:電腦上的圖標(biāo)進程是程序的一次運行活動, 通俗點說就是程序跑起來了,系統(tǒng)中就多了一個進程
2.如何查看系統(tǒng)中有哪些進程?
使用ps指令查看 ? ? :? ps-aux ?在ubuntu下查看,在實際工作中,配合grep來查找程序中是否存在某一個進程
grep ?過濾進程 ? :ps -aux | grep init 就只把帶有init的進程過濾出來
使用top指令查看,類似windows任務(wù)管理器
3.什么是進程標(biāo)識符?
每一個進程都有一個非負整數(shù)表示的唯一ID,叫做pid,類似身份證
pid =0 :稱為交換進程(swapper)作用:進程調(diào)度pid=1 ?:init 進程作用:系統(tǒng)初始化
- 編程調(diào)用getpid函數(shù)獲取自身的進程標(biāo)識符;
#include<sys/types.h>
#include<unistd.h>
pid_t?getpid(void);
pid_t?getppid(void);
getpid示例代碼:
#include<stidio.h>
#include<sys/types.h>
#include<unistd.h>
int?main()
{
????pid_t?pid;
????pid?=?getpid();
???printf("my?pid?is?%dn",pid);
???return?0;
}
- getppid獲取父進程的進程標(biāo)識符;
4. 第一個進程 ?init 進程
在這里插入圖片描述
Linux內(nèi)核啟動之后,會創(chuàng)建第一個用戶級進程init,由上圖可知, init 進程 (pid=1) 是除了 idle 進程(pid=0,也就是 init_task) 之外另一個比較特殊的進程,它是 Linux 內(nèi)核開始建立起進程概念時第一個通過 kernel_thread 產(chǎn)生的進程,其開始在內(nèi)核態(tài)執(zhí)行,然后通過一個系統(tǒng)調(diào)用,開始執(zhí)行用戶空間的 / sbin/init 程序。
5.什么叫父進程,什么叫子進程?
進程A創(chuàng)建了進程B,那么A叫做父進程,B叫做子進程,父進程是相對的概念,理解為人類中的父子關(guān)系
6. c程序的存儲空間是如何分配的?
gcc xxx.c ?-o a.out ? ?當(dāng)執(zhí)行 ./a.out 時候,操作系統(tǒng)會劃分一塊內(nèi)存空間,如何分配呢?如下圖:
二、創(chuàng)建進程函數(shù)fork的使用
==pid_t fork(void);==
功能:使用fork函數(shù)創(chuàng)建一個進程
fork函數(shù)調(diào)用成功,返回兩次 返回值為0 ?,代表當(dāng)前進程是子進程
返回值非負數(shù),代表當(dāng)前進程為父進程 調(diào)用失敗 ,返回-1
1. fork();示例代碼
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int?main()
{
????pid_t?pid;
????pid?=?getpid();
????
????fork();
????
???printf("my?pid?is?%dn",pid);
???return?0;
}
打印出了兩遍 my pid ?說明,有了兩個進程!執(zhí)行了兩次打印pid
2. 查看父進程/子進程代碼:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int?main()
{
????pid_t?pid;
????pid_t?pid2;
????pid?=?getpid();
????printf("brfore?fork?pid?is?%dn",pid);
????fork();
????pid2?=?getpid();
????printf("brfore?fork?pid?is?%dn",pid2);
????if(pid?==?pid2){
?????????printf("this?is?father?printn");
????}else{
?????????printf("this?is?child?print?,?child?pid?is?=%dn",getpid());
????}
???return?0;
}
父子進程都會進入if 中,但是輸出結(jié)果會不同
在fork之前的pid 是8915 是父進程 ? ,fork之后pid是子進程 8916
3. 用返回值來判斷父/子進程代碼(1):
返回值為0 ?,代表當(dāng)前進程是子進程
返回值非負數(shù),代表當(dāng)前進程為父進程
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int?main()
{
???pid_t?pid;
???printf("father:?id=%dn",getpid());
???pid?=?fork();
???if(pid?>?0){
?????????printf("this?is?father?print?,pid?=%dn",getpid());
???}else?if?(pid?==?0){
?????????printf("this?is?child?print,?child?pid?=?%dn",getpid());
???}
???return?0;
}
在這里插入圖片描述
4. 用返回值來判斷父子進程代碼(2):
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int?main()
{
???pid_t?pid;
???pid_t?pid2;
???pid_t?retpid;
???pid?=?getpid();
???printf("before?fork:?pid?=?%dn",pid);
???retpid?=?fork();
???pid2?=?getpid();
???printf("after?fork:pid?=?%dn",pid2);
???if(pid?==?pid2){
???????printf("this?is?father?print?:retpid?=?%dn",retpid);
???}else{
???????printf("this?is?child?print?:retpid?=%d,child?pid=?%dn",retpid,pid2);
???}
???return?0;
}
這樣更清楚明了的看到
fork 返回值:9915>0 ?是父進程 ? ?父進程號是9114
fork 返回值:=0 ? ? 是子進程 ? ? ? ? 子進程號是9915
三、進程創(chuàng)建后 發(fā)生了什么事?
在這里插入圖片描述
1 在內(nèi)存空間中fork后發(fā)生了什么?
在這里插入圖片描述
2. ./demo4 運行的程序父進程是誰?
int?main(int?argc,?const?char?*argv[])
{
????????while(1);
????????return?0;
}
./ demo4 編譯運行后,我們ps -ef 查看進程ID由上圖可知,./demo4 進程的進程ID是12677,父進程ID是12587,即進程bash:==bash的父進程是gnome-terminal,所以我們打開1個Linux終端,其實就是啟動了1個gnome-terminal進程。我們在這個終端上執(zhí)行./a.out其實就是利用gnome-terminal的子進程bash通過execve()將創(chuàng)建的子進程裝入a.out:==
四、創(chuàng)建新進程的實際應(yīng)用場景
1. fork創(chuàng)建子進程的一般目的:
一個父進程希望復(fù)制自己,使父、子進程同時執(zhí)行不同的代碼段。這在網(wǎng)絡(luò)服務(wù)進程中是常見的——父進程等待客戶端的服務(wù)請求。當(dāng)這種情求達到時,父進程調(diào)用fork,使子進程處理此請求。父進程則繼續(xù)等待下一個服務(wù)請求到達。
一個進程要執(zhí)行一個不同的程序。這對shell是常見的情況,在這種情況下子進程從fork返回后立即調(diào)用exec。
在這里插入圖片描述
2. 模擬socket 創(chuàng)建進程(服務(wù)器對接客戶端的應(yīng)用場景)示例代碼:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int?main()
{
???pid_t??pid;
???int?data;
???while(1){
????????printf("please?input?a?datan");
????????scanf("%d",&data);
????????if(data?==1?)
????????{
????????????pid?=?fork();
????????????if(pid?>0){
????????????}
????????????else?if(pid?==?0){
??????????????????while(1){
????????????????????????printf("do?net?request,pid=%dn",getpid());
????????????????????????sleep(3);
??????????????????}
????????????}
????????}
????????else{
???????????printf("wait,?do?notingn");
????????}
???}
???return?0;
}
輸入非1時候,模擬沒有客戶端進行交互
輸入1時候,模擬有客戶端進行交互 ,創(chuàng)建子進程來進行交互,子進程號為:9756
模擬多個客戶端進行交互時 ,創(chuàng)建多個子進程來進行交互,子進程號為:9756 ? / 9758 ?/ 9759
查看系統(tǒng)進程:
3. fork總結(jié):
一個現(xiàn)有進程可以調(diào)用fork函數(shù)創(chuàng)建一個新進程。
#include cunistd.h>
pid_t fork(void);
返回值:子進程中返回0。父進程中返回子進程ID.出錯返回-1
由fork創(chuàng)建的新進程被稱為子進程(child
process)。fork函數(shù)被調(diào)用一次,但返回兩次。兩次返回的唯一區(qū)別是子進程的返回值是0,而父進程的返回值則是新子進程的進程ID。將子進程ID返回給父進程的理由是:因為一個進程的子進程可以有多個,并且沒有一個函數(shù)使一個進程可以獲得其所有子進程的進程ID。fork使子進程得到返回值0的理由是:一個進程只會有一個父進程,所以子進程總是可以調(diào)用getppid以獲得其父進程的進程ID(進程ID0總是由內(nèi)核交換進程使用,所以一個子進程的進程ID不可能為0)。
子進程和父進程繼續(xù)執(zhí)行fork調(diào)用之后的指令。子進程是父進程的副本。例如,子進程獲得父進程數(shù)據(jù)空間、堆和棧的副本。注意,這是子進程所擁有的副本。父、子進程并不共享這些存儲空間部分。父、子進程共享正文段。由于在fork之后經(jīng)常跟隨著exec,所以現(xiàn)在的很多實現(xiàn)并不執(zhí)行一個父進程數(shù)據(jù)段、棧和堆的完全復(fù)制。作為替代,使用了寫時復(fù)制(Copy-On-Write,COW)技術(shù)。這些區(qū)域由父、子進程共享,而且內(nèi)核將它們的訪問權(quán)限改變?yōu)橹蛔x的。如果父、子進程中的任一個試圖修改些區(qū)域,則內(nèi)核只為修改區(qū)域的那塊內(nèi)存制作一個副本,通常是虛擬存儲器系統(tǒng)中的一“頁”。Bach和McKusick等對這種特征做了更詳細的說明。
五、vfork創(chuàng)建進程
1. vfork函數(shù) 也可以創(chuàng)建進程,與fork有什么區(qū)別?
關(guān)鍵區(qū)別一:vfork直接使用父進程存儲空間,不用拷貝關(guān)鍵區(qū)別二:vfork保證子進程先運行,當(dāng)子進程調(diào)用exit退出后,父進程才執(zhí)行
2. fork 進程調(diào)度 父子進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int?main()
{
????pid_t?pid;
????pid?=?fork();
????if(pid?>0){
???????while(1){
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(3);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(3);
?????????}
????}
???return?0;
}
在這里插入圖片描述
3. ?vfork 進程調(diào)度 父子進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int?main()
{
????pid_t?pid;
????int?cnt=0;
????pid?=?vfork();
????if(pid?>0){
???????while(1){
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
???????????????????exit(0);
???????????????}
?????????}
????}
???return?0;
}
vfork保證子進程先運行,當(dāng)子進程調(diào)用3次 ? exit退出后,父進程才執(zhí)行
4. 子進程改變cnt值,在父進程運行時候也被改變
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int?main()
{
????pid_t?pid;
????int?cnt=0;
????printf("cnt=%dn",cnt);
????pid?=?vfork();
????if(pid?>0){
???????while(1){
???????????????printf("cnt=%dn",cnt);
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(0);
???????????????}
?????????}
????}
???return?0;
}
在這里插入圖片描述
六、ps 常帶的一些參數(shù)
下面對ps命令選項進行說明:
命令參數(shù) | 說明 |
---|---|
-e | 顯示所有進程. |
-f | 全格式。 |
-h | 不顯示標(biāo)題。 |
-l | 長格式。 |
-w | 寬輸出。 |
-a | 顯示終端上的所有進程,包括其他用戶的進程。 |
-r | 只顯示正在運行的進程。 |
-u | 以用戶為主的格式來顯示程序狀況。 |
-x | 顯示所有程序,不以終端機來區(qū)分。 |
ps -ef ?顯示所有進程,全格式形式查看進程:
ps -ef 的每列的含義是什么呢?
在這里插入圖片描述
命令參數(shù) | 說明 |
---|---|
UID: | 程序被該 UID 所擁有,指的是用戶ID |
PID: | 就是這個程序的 ID |
PPID : | PID的上級父進程的ID |
C : | CPU使用的資源百分比 |
STIME : | 系統(tǒng)啟動時間 |
TTY: | 登入者的終端機位置 |
TIME : | 使用掉的 CPU時間。 |
CMD: | 所下達的指令為何 |
七、進程退出
1. 子進程退出方式
正常退出:
- Mian函數(shù)調(diào)用return進程調(diào)用exit(),標(biāo)準c庫進程調(diào)用_exit()或者——Exit(),屬于系統(tǒng)調(diào)用進程最后一個線程返回最后一個線程調(diào)用pthread_exit
異常退出:
- 調(diào)用abort當(dāng)進程收到某些信號時候,如ctrl+C最后一個線程對取消(cancellation),請求作出響應(yīng)
不管進程如何終止,最后都會執(zhí)行內(nèi)核中的同一段代碼。這段代碼為相應(yīng)進程關(guān)閉所有打開描述符,釋放它所使用的存儲器等。
對上述任意一種終止情形,我們都希望終止進程能夠通知其父進程它是如何終止的。對于三個終止函數(shù)(exit、_exit和_Exit),實現(xiàn)這一點的方法是,將其退出狀態(tài)作為參數(shù)傳送給函數(shù)?!救缟厦媸纠锩鎸懙降腸nt==3情況下,exit(0);
這個0就是子進程退出狀態(tài)?!吭诋惓=K止情況下,內(nèi)核(不是進程本身)產(chǎn)生一個指示其異常終止原因的終止?fàn)顟B(tài)。在任何一種情況下,該終止進程的父進程都能用wait或者waitpid取得其終止?fàn)顟B(tài)。
正常退出的三個函數(shù):
#include<stdlib.h>
void?exit(int?status);
#include<unistd.h>
void?_exit(int?status);
#include<stdlib.h>
void?_Exit(int?status);
記得在結(jié)束子進程的時候要手動退出,不要使用break;會導(dǎo)致數(shù)據(jù)被破壞。?三種退出函數(shù)種,更推薦exit(); ?exit是 _exit 和_Exit 的一個封裝, 會清除,沖刷緩沖區(qū),把緩存區(qū)數(shù)據(jù)進程處理在退出。
2. 等待子進程退出
==為什么要等待子進程退出?==
創(chuàng)建子進程的目的就是為了讓它去干活,在網(wǎng)絡(luò)請求當(dāng)中來了一個新客戶端介入,創(chuàng)建子進程去交互,干活也要知道它干完沒有.比如正常退出(exit/_exit /_Exit)為 完成任務(wù)
若異常退出 ?(abort)不想干了, 或被殺了
所有要等待子進程退出,而且還要收集它退出的狀態(tài)
等待就是調(diào)用wait函數(shù) 和 waitpid函數(shù)
3. 僵尸進程
子進程退出狀態(tài)不被收集,會變成僵死進程(僵尸進程)
正如以下例子,就是子進程退出沒有被收集,成了僵尸進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int?main()
{
????pid_t?pid;
????int?cnt=0;
????printf("cnt=%dn",cnt);
????
????pid?=?vfork();
????
????if(pid?>0){
???????while(1){
???????????????printf("cnt=%dn",cnt);
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(0);
???????????????}
?????????}
????}
???return?0;
}
運行三次子進程后,退出,父進程一直運行
結(jié)果:在查看進程時發(fā)現(xiàn),父進程11314正在運行 ?“S+” ? 而子進程11315 停止運行 “z+” z表示zombie(僵尸)
4. 等待函數(shù):wait(狀態(tài)碼); 的使用:
#include<sys/types.h>
#inlcude<sys/wait.h>
pid_t?wait(int?*status);????//參數(shù)status?是一個地址????
pid_t?waitpid(pid_t?pid?,?int?*status?,int?options);
int?waitid(idtype_t?idtype?,id_t?id?,siginfo_t??*infop,?int??options);
如果其所有子進程都還在運行,則阻塞。:通俗的說就是子進程在運行的時候,父進程卡在wait位置阻塞,等子進程退出后,父進程開始運行。
如果一個子進程已終止,正等待父進程獲取其終止?fàn)顟B(tài),則會取得該子進程的終止?fàn)顟B(tài)立即返回。
如果它沒有任何子進程,則立即出錯返回。
status參數(shù):是一個整型數(shù)指針
非空:子進程退出狀態(tài)放在它所指向的地址中??眨翰魂P(guān)心退出狀態(tài)
檢查wait 和 waitpid 所返回的終止?fàn)顟B(tài)的宏
宏 | 說明 |
---|---|
WIFEXITED (status) | 若為正常終止子進程返回的狀態(tài),則為真。對于這種情況可執(zhí)行WEXITSTATUS(status),取子進程傳送給exit、_exit 或_Exit參數(shù)的低8位 |
WIFSIGNALED (status) | 若為異常終止子進程返回的狀態(tài),則為真(接到一個不捕捉的信號)。對于這種情況,可執(zhí)行WTERMSIG(status),取使子進程終止的信號編號。另外,有些實現(xiàn)(非Single UNIX Specification)宏義宏WCOREDUMP(status),若已產(chǎn)生終止進程的core文件,則它返回真 |
WIFSTOPPED (status) | 若為當(dāng)前暫停子進程的返回的狀態(tài),則為真,對于這種情況,可執(zhí)行WSTIOPSIG(status),取使子進程暫停的信號編號 |
WIFCONTINUED (status) | 若在作業(yè)控制暫停后已經(jīng)繼續(xù)的子進程返回了狀態(tài),則為真。(POSIX.1的XSI擴展,僅用于waitpid。) |
比如說:exit(3) ?wait (狀態(tài)碼); 要通過宏來解析狀態(tài)碼 |
5. 收集退出進程狀態(tài)
pid?=?vfork();
????if(pid?>0){
???????while(1){
???????????????printf("cnt=%dn",cnt);
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????wait(NULL);????//?參數(shù):status ?是一個地址??為空?表示不關(guān)心退出狀態(tài)
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(0);
???????????????}
?????????}
????}
wait(NULL); ? ?// 參數(shù):status ?是一個地址 ?為空 表示不關(guān)心退出狀態(tài)
沒有了11567子進程,這樣就不是僵尸進程了
收集子進程退出狀態(tài)示例代碼:
int?main()
{
????pid_t?pid;
????int?cnt=0;
????int?status?=10;
????printf("cnt=%dn",cnt);
????pid?=?vfork();
????if(pid?>0){
???????wait(&status);?????//?參數(shù)status是一個地址??
???????printf("child?out?,chile?status?=%dn",WEXITSTATUS(status));??//要解析狀態(tài)碼,需要借助WEXITSTATUS
???????while(1){
???????????????printf("cnt=%dn",cnt);
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(5);
???????????????}?
??????????}
???}
int status =10;
wait(&status); ? ? // 參數(shù)status是一個地址
printf("child out ,chile status =%dn",WEXITSTATUS(status)); ?//要解析狀態(tài)碼,需要借助WEXITSTATUS
結(jié)果顯示:exit(5); ? 就能看到子進程退出的狀態(tài) status=5
6. 等待函數(shù):waitpid()的使用;
wait和waitpid的區(qū)別之一:
wait使父進程(調(diào)用者)阻塞,waitpid有一個選項 ,可以使父進程(調(diào)用者)不阻塞。
pid_t waitpid(pid_t pid , int *status ,int options);
對于waitpid函數(shù)種pid參數(shù)的作用解釋如下:
pid == -1 | 等待任一子進程。就這一方面而言,waitpid與wait等效。 |
pid > 0 | 等待其進程ID與pid相等的子進程。 |
pid == 0 | 等待其組ID等于調(diào)用進程組ID的任一子進程 |
pid <-1 | 等待其組ID等于pid絕對值的任一子進程。 |
waitpid 的 options 常量:
WCONTINUED | 若實現(xiàn)支持作業(yè)控制,那么由pid指定的任一子進程在暫停后已經(jīng)繼續(xù),但其狀態(tài)尚未報告,則返回其狀態(tài)(POSIX.1的XSI擴展) |
WNOHANG | 若由pid指定的子進程并不是立即可用的,則waitpid不阻塞,此時其返回值為0; |
WUNTRACED | 若某實現(xiàn)支持作業(yè)控制,而由pid指定的任一子進程已處于暫停狀態(tài)。 |
waitpid 來使得父進程不阻塞代碼:
int?main()
{
????pid_t?pid;
????int?cnt=0;
????int?status?=10;
????
????printf("cnt=%dn",cnt);
????pid?=?vfork();
????if(pid?>0){
???????waitpid(pid,&status,WNOHANG);?//?參數(shù)pid 是子進程號,WNOHANG是若由pid指定的子進程并不是立即可用的,則waitpid不阻塞,此時其返回值為0;
???????printf("child?out?,chile?status?=%dn",WEXITSTATUS(status));??
???????while(1){
???????????????printf("cnt=%dn",cnt);
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????????????sleep(1);
???????}
????}else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%dn",getpid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(5);
???????????????}
??????????}
???}
子進程和父進程同時進行
但是發(fā)現(xiàn)子進程12275 ?在系統(tǒng)查詢進程中 ?還是變成了僵尸進程原因是 ==WNOHANG是不等待參數(shù),它只運行一遍== ,當(dāng)他運行時候,子進程沒死,等子進程死后,他沒運行,就沒有收到停止?fàn)顟B(tài),所以成了僵尸進程。
八、孤兒進程
1. 孤兒進程的概念:
父進程如果不等待子進程退出,在子進程結(jié)束前就了結(jié)束了自己的“生命”,此時子進程就叫做孤兒進程。
2.孤兒進程被收留:
Linux避免系統(tǒng)存在過多孤兒進程,init進程收留孤兒進程,變成孤兒進程的父進程【init進程(pid=1)是系統(tǒng)初始化進程】。init 進程會自動清理所有它繼承的僵尸進程。
孤兒進程的代碼:
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int?main()
{
????pid_t?pid;
????int?cnt=0;
????int?status?=10;
????pid?=?fork();
????if(pid?>0){
???????????????printf("this?is?father?print?pid?is?%dn",getpid());
???????}
????else?if(pid?==?0){
?????????while(1){
???????????????printf("this?is?child?print?pid?is?=%d,my?father?pid?is=%dn",getpid(),getppid());
???????????????sleep(1);
???????????????cnt++;
???????????????if(cnt?==?3?){
??????????????????exit(5);
???????????????}
?????????}
????}
???return?0;
}
父進程運行結(jié)束前,子進程的父進程pid還是13098。父進程運行結(jié)束后,子進程的父進程變成了init進程( pid=1)。
九、exec族函數(shù)
1. exec族函數(shù)的作用:
我們用fork函數(shù)創(chuàng)建新進程后,經(jīng)常會在新進程中調(diào)用exec函數(shù)去執(zhí)行另外一個程序。當(dāng)進程調(diào)用exec函數(shù)時,該進程被完全替換為新程序因為調(diào)用exec函數(shù)并不創(chuàng)建新進程,所以前后進程的ID并沒有改變。
2. 為什么要用exec族函數(shù),有什么作用?
- 一個父進程希望復(fù)制自己,使父、子進程同時執(zhí)行不同的代碼段。這在網(wǎng)絡(luò)服務(wù)進程中是常見的——父進程等待客戶端的服務(wù)請求。當(dāng)這種請求到達時,父進程調(diào)用fork,使子進程處理此請求。父進程則繼續(xù)等待下一個服務(wù)請求到達。一個進程要執(zhí)行一個不同的程序。這對shell是常見的情況。在這種情況下,子進程從fork返回后立即調(diào)用exec。
3. exec族函數(shù)定義:
功能:
exec函數(shù)族提供了一種在進程中啟動另一個程序執(zhí)行的方法,它可以根據(jù)指定的文件名或目錄名找到可執(zhí)行文件,并用它來取代原調(diào)用進程的數(shù)據(jù)段、代碼段和堆棧段。在執(zhí)行完之后,原調(diào)用進程的內(nèi)容除了進程號外,其他全部都被替換了。在調(diào)用進程內(nèi)部執(zhí)行一個可執(zhí)行文件,可執(zhí)行文件既可以是二進制文件,也可以是linux下可執(zhí)行的腳本文件?!就ㄋ桌斫饩褪菆?zhí)行demo1的同時,執(zhí)行一半去執(zhí)行demo2?!?/p>
函數(shù)族:
execl、execlp、execle、execv、execvp、execvpe
函數(shù)原型:
#include<unistd.h>
extern?char?**environ;
int?execl(char?*path??,?char?*arg?,??...);
int?execlp(char?*file??,?char?*arg?,??...);
int?execle(char?*path??,?char?*arg?,??...?,?char?*const?envp[]?);
int?execv(char?*path??,?char?*const?argv[]?);
int?execvp(char?*file??,?char?*const?atgv[]?);
int?execvpe(char?*file??,?char?*const?argv[]?,?char?*const?envp[]);
返回值:
exec函數(shù)族的函數(shù)執(zhí)行成功后不會返回,調(diào)用失敗時,會設(shè)置errno并返回-1,然后從原程序的調(diào)用點接著往下執(zhí)行。
參數(shù)說明:
path :可執(zhí)行文件的路徑名字arg:可執(zhí)行程序所帶的參數(shù),第一個參數(shù)為可執(zhí)行文件名字,沒有帶路徑且arg必須以NULL結(jié)束。file:如果參數(shù)file中包含/,則就將其視為路徑名,否則就按PATH環(huán)境變量,在它所指定的各目錄中搜尋可執(zhí)行文件。
exec族函數(shù)參數(shù)極難記憶和分辨,函數(shù)名中的字符會給我們一些幫助:
字符 | 說明 |
---|---|
l | 使用參數(shù)列表 |
p | 使用文件名,并從PATH環(huán)境尋找可執(zhí)行文件 |
v | 應(yīng)該先構(gòu)造一個指向各參數(shù)的指針數(shù)組,然后將該數(shù)組的地址作為這些函數(shù)的參數(shù)。 |
e | 多了envp[]數(shù)組,使用新的環(huán)境變量代替調(diào)用進程的環(huán)境變量 |
4. exec函數(shù) 帶 l ?帶p ?帶v ?來說明參數(shù)特點
先寫一個帶參數(shù)的程序,輸入?yún)?shù) 輸出參數(shù),在上一篇Linux文件編程里,main參數(shù)我們學(xué)過。
./echoarg代碼:
#include<stdio.h>
int?main(int?argc?,?char?*argv[])
{
?????int?i?=0;
?????for(i?=0?;i?<argc;i++){
?????????printf("argv[%d]:%sn",i?,argv[i]);
?????}
???return?0;
}
在執(zhí)行a.out 代碼一半的時候,調(diào)用上面的代碼echoarg
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int?main(void)
{
?????printf("brfore?execln");
?????//int?execl(char?*path??,?char?*arg?,??...);
?????if(execl("/bin/echoarg","echoarg","abc",NULL)==-1)
?????{
?????????printf("execl?failed!n");
?????}
?????printf("after?execl?n");
?????return?0;
}
exec函數(shù)族的函數(shù)執(zhí)行成功后不會返回,調(diào)用失敗時,會設(shè)置errno并返回-1,然后從原程序的調(diào)用點接著往下執(zhí)行。
if(execl("/bin/echoarg","echoarg","abc",NULL)==-1)源代碼:int execl(char *path ?, char *arg , ?...);
//最后一個參數(shù)是:arg必須以NULL結(jié)束。
在執(zhí)行a.out 代碼一半的時候,調(diào)用上面的代碼echoarg:
exec函數(shù)族的函數(shù)執(zhí)行成功后不會返回,調(diào)用失敗時,會設(shè)置errno并返回-1,然后從原程序的調(diào)用點接著往下執(zhí)行。
==perror("why"); ?//用來在執(zhí)行錯誤時候,查詢錯誤原因==
若要調(diào)用ech 執(zhí)行一般執(zhí)行l(wèi)s ,同理。只需要改動
if(execl("/bin/ls","ls",NULL,NULL)==-1)
若要調(diào)用ech 執(zhí)行一般執(zhí)行l(wèi)s-l ,同理。
if(execl("/bin/ls","ls","-l",NULL)==-1)
execlp 和execl 的區(qū)別
帶p : 可以通過環(huán)境變量PATH環(huán)境尋找可執(zhí)行文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int?main(void)
{
?????printf("brfore?execln");
?????//int?execl(char?*path??,?char?*arg?,??...);
?????if(execl("ls",";s",NULL,NULL)==-1)
?????{
?????????printf("execl?failed!n");
?????}
?????printf("after?execl?n");
?????return?0;
}
在路徑中不用寫具體路徑,就可以自動找到文件
execvp 和execl 的區(qū)別
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int?main(void)
{
?????printf("brfore?execln");
?????char?*argv[]?=?{"ps",NULL,NULL};
?????if(execvp("ps",argv)==-1)
?????{
?????????printf("execl?failed!n");
?????}
?????printf("after?execl?n");
?????return?0;
}
char *argv[] = {"ps",NULL,NULL};
if(execvp("ps",argv)==-1)
結(jié)果與上面相同
5. 任何目錄下執(zhí)行程序
一個程序在目錄下能運行,換一個目錄就無法運行,如果把程序配置到環(huán)境變量里面去。
==pwd顯示當(dāng)前路徑
echoPATH: [pwd顯示的當(dāng)前路徑]==
就可以在任何目錄下執(zhí)行程序了
6. ?exec配合fork使用
一個進程要執(zhí)行一個不同的程序。這對shell是常見的情況。在這種情況下,子進程從fork返回后立即調(diào)用exec。
1. 不用exec的方法: 實現(xiàn)功能,當(dāng)父進程檢查到輸入為1的時候,創(chuàng)建子進程把配置文件的字段值修改掉。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>?
#include<string.h>?
#include<stdlib.h>?
#include<unistd.h>
int?main()
{
???pid_t?pid;
???int?data?=?10;
???while(1){
?????????printf("please?input?a?datan");
?????????scanf("%d",&data);
?????????if(data?==?1){
?????????????????pid?=?fork();
?????????????????if(pid>0)
?????????????????{?
???????????????????wait(NULL);
?????????????????}
?????????????????if(pid?==?0){
???????????????????????int?fdSrc;
???????????????????????char?*readBuf=NULL;
????????????????????????fdSrc?=?open("config.txt",O_RDWR);
????????????????????????int?size?=?lseek(fdSrc,0,SEEK_END);
????????????????????????lseek?(fdSrc,0,SEEK_SET);
????????????????????????readBuf?=(char?*)malloc(sizeof(char)*size+8);
????????????????????????int?n_read=?read(fdSrc,readBuf,size);
????????????????????????char?*p=strstr(readBuf,"LENG=");?//找到(要修改的)位置???
??//參數(shù)1?要找的源文件??2.“要找的字符串”
????????????????????????if(p==NULL){
???????????????????????????????printf("not?foundn");
???????????????????????????????exit(-1);
?????????????????????????}
?????????????????????????????p=p+strlen("LENG=");??//移動字符串個字節(jié)
???????????????????????????????*p='0';??????//*p??取內(nèi)容
lseek?(fdSrc,0,SEEK_SET);
??????????????????????????int?n_write?=write(fdSrc,readBuf,strlen(readBuf));
??????????????????????????close(fdSrc);
??????????????????????????exit(0);
??????????????????????}
????????????????????}else?{
????????????????????????????????printf("do?notingn");
??????????????????????????}
???????????}
????????????????return?0;
}
實現(xiàn)了當(dāng)父進程檢查到輸入為1的時候,創(chuàng)建子進程把配置文件的字段值修改掉。
2. 用exec的方法: 實現(xiàn)功能,當(dāng)父進程檢查到輸入為1的時候,創(chuàng)建子進程把配置文件的字段值修改掉。
int?main()
{
???pid_t?pid;
???int?data?=?10;
???while(1){
?????????printf("please?input?a?datan");
?????????scanf("%d",&data);
?????????if(data?==?1){
?????????????????pid?=?fork();
?????????????????if(pid?>?0){
?????????????????????wait(NULL);
?????????????????}
?????????????????if(pid?==?0){
???????????????????execl("./changdata","changdata","config.txt",NULL);
???????????????????exit(0);
??????????????????????}
????????????????????}else?{
????????????????????????????????printf("do?notingn");
??????????????????????????}
???????????}
????????????????return?0;
}
使用execl 和 fork ?結(jié)合 也能做到上面結(jié)果,而且更方便,但是在 ./changdata 可執(zhí)行文件存在的情況下。
十、system函數(shù)
1. system函數(shù)定義:
函數(shù)原型:
?#include<stdlib.h>
int?system(const?char?*?string);
函數(shù)說明:
system()會調(diào)用fork()產(chǎn)生子進程,由子進程來調(diào)用/bin/sh-c
string來執(zhí)行參數(shù)string字符串所代表的命令,此命令執(zhí)行完后隨即返回原調(diào)用的進程。在調(diào)用system()期間SIGCHLD
信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。
返回值:
system()函數(shù)的返回值如下:成功,則返回進程的狀態(tài)值;當(dāng)sh不能執(zhí)行時,返回127;失敗返回-1;
2. system函數(shù)的使用:
用system也可以做到execl的功能用system實現(xiàn)修改配置 數(shù)值代碼:
int?main()
{
???pid_t?pid;
???int?data?=?10;
???while(1){
?????????printf("please?input?a?datan");
?????????scanf("%d",&data);
?????????if(data?==?1){
?????????????????pid?=?fork();
?????????????????if(pid?>?0){
?????????????????????wait(NULL);
?????????????????}
?????????????????if(pid?==?0){
???????????????????execl("./changdata?config.txt");
???????????????????exit(0);
??????????????????????}
????????????????????}else?{
????????????????????????????????printf("do?notingn");
??????????????????????????}
???????????}
????????????????return?0;
}
在這里插入圖片描述
3. system和execl不同的是:
sysem運行完調(diào)用的可執(zhí)行文件后還會繼續(xù)執(zhí)行源代碼。
==附加說明:==
在編寫具有SUID/SGID權(quán)限的程序時請勿使用system(),system()會繼承環(huán)境變量,通過環(huán)境變量可能會造成系統(tǒng)安全的問題。
十一、popen函數(shù)
1. ?popen函數(shù)的定義:
函數(shù)原型:
#include<stdio.h>
FILE?*popen?(const?char?*command?,const?char?*type);?
int?pclose(FILE?*stream);?
參數(shù)說明:
command: 是一個指向以NULL結(jié)束的shell命令字符串的指針。這行命令將被傳到bin/sh并且使用 -c標(biāo)志
,shell將執(zhí)行這個命令。
type: 只能是讀或者寫中的一種,得到的返回值(標(biāo)準I/O流)也具有和type相應(yīng) ? 的只讀或只寫類型。如果type是”r“
則文件指針連接到command的標(biāo)準輸出;如果type是”w“則文件指針連接到command的標(biāo)準輸入。
返回值:
如果調(diào)用成功,則返回一個讀或者打開文件的指針,如果失敗,返回NULL,具體錯誤要根據(jù)errno判斷
int pclose(FILE *stream)
參數(shù)說明:stream:popen返回對丟文件指針
返回值:如果調(diào)用失敗,返回-1
作用:
popen()函數(shù)用于創(chuàng)建一個管道:其內(nèi)部實現(xiàn)為調(diào)用fork產(chǎn)生一個子進程,執(zhí)行一個shell以運行命令來開啟一個進程這個進程必須由pclose()函數(shù)關(guān)閉。
popen比system 在應(yīng)用中的好處:==可以獲取運行的輸出結(jié)果==
popen函數(shù)執(zhí)行完,執(zhí)行結(jié)果到管道內(nèi),數(shù)據(jù)流出的時候,在管道尾部fread就可以讀出執(zhí)行數(shù)據(jù),就能實現(xiàn)把數(shù)據(jù)讀到或?qū)懙较胍木彌_區(qū)里。
2. popen函數(shù)的使用:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int?main(void)
{
???char?ret[1024]={0};
???FILE?*fp;
???fp?=?popen("ps","r");
???int?nread?=?fread(ret,1,1024,fp);
???printf("read?ret?%d?byte?,ret?=%sn",nread?,ret);
???return?0;
}
結(jié)果發(fā)現(xiàn):popen函數(shù)結(jié)束后,ps 輸出的內(nèi)容, 都捕獲到 ret 數(shù)組里面去了。popen可以獲取運行的輸出結(jié)果 ,可以讀取也可以寫入文件中。