• 正文
    • Linux 進(jìn)程篇
    • 一、進(jìn)程相關(guān)概念
    • 二、創(chuàng)建進(jìn)程函數(shù)fork的使用
    • 三、進(jìn)程創(chuàng)建后 發(fā)生了什么事?
    • 四、創(chuàng)建新進(jìn)程的實(shí)際應(yīng)用場景
    • 五、vfork創(chuàng)建進(jìn)程
    • 六、ps 常帶的一些參數(shù)
    • 七、進(jìn)程退出
    • 八、孤兒進(jìn)程
    • 九、exec族函數(shù)
    • 十、system函數(shù)
    • 十一、popen函數(shù)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

Linux 進(jìn)程編程入門

02/02 11:45
197
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

關(guān)于進(jìn)程和線程的關(guān)系,之前一口君寫過這幾篇文章,大家可以參考下。

本文從頭帶著大家一起學(xué)習(xí)Linux進(jìn)程

《搞懂進(jìn)程組、會話、控制終端關(guān)系,才能明白守護(hù)進(jìn)程干嘛的?》

Linux 進(jìn)程篇

一、進(jìn)程相關(guān)概念

了解進(jìn)程的時候先來了解幾個問題,明白以下問題,就懂了進(jìn)程的概念

1.什么是程序,什么是進(jìn)程,兩者之間的區(qū)別?

    程序是靜態(tài)的概念,gcc ?xxx.c -o pro 磁盤中生成pro文件,叫做程序 ? ? ? ? ? ? ?程序如:電腦上的圖標(biāo)進(jìn)程是程序的一次運(yùn)行活動, 通俗點(diǎn)說就是程序跑起來了,系統(tǒng)中就多了一個進(jìn)程

2.如何查看系統(tǒng)中有哪些進(jìn)程?

使用ps指令查看 ? ? :? ps-aux ?在ubuntu下查看,在實(shí)際工作中,配合grep來查找程序中是否存在某一個進(jìn)程

grep ?過濾進(jìn)程 ? :ps -aux | grep init 就只把帶有init的進(jìn)程過濾出來

使用top指令查看,類似windows任務(wù)管理器

3.什么是進(jìn)程標(biāo)識符?

每一個進(jìn)程都有一個非負(fù)整數(shù)表示的唯一ID,叫做pid,類似身份證

pid =0 :稱為交換進(jìn)程(swapper)作用:進(jìn)程調(diào)度pid=1 ?:init 進(jìn)程作用:系統(tǒng)初始化

    編程調(diào)用getpid函數(shù)獲取自身的進(jìn)程標(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獲取父進(jìn)程的進(jìn)程標(biāo)識符;

4. 第一個進(jìn)程 ?init 進(jìn)程

在這里插入圖片描述

Linux內(nèi)核啟動之后,會創(chuàng)建第一個用戶級進(jìn)程init,由上圖可知, init 進(jìn)程 (pid=1) 是除了 idle 進(jìn)程(pid=0,也就是 init_task) 之外另一個比較特殊的進(jìn)程,它是 Linux 內(nèi)核開始建立起進(jìn)程概念時第一個通過 kernel_thread 產(chǎn)生的進(jìn)程,其開始在內(nèi)核態(tài)執(zhí)行,然后通過一個系統(tǒng)調(diào)用,開始執(zhí)行用戶空間的 / sbin/init 程序。

5.什么叫父進(jìn)程,什么叫子進(jìn)程?

進(jìn)程A創(chuàng)建了進(jìn)程B,那么A叫做父進(jìn)程,B叫做子進(jìn)程,父進(jìn)程是相對的概念,理解為人類中的父子關(guān)系

6. c程序的存儲空間是如何分配的?

gcc xxx.c ?-o a.out ? ?當(dāng)執(zhí)行 ./a.out 時候,操作系統(tǒng)會劃分一塊內(nèi)存空間,如何分配呢?如下圖:

二、創(chuàng)建進(jìn)程函數(shù)fork的使用

==pid_t fork(void);==
功能:使用fork函數(shù)創(chuàng)建一個進(jìn)程

fork函數(shù)調(diào)用成功,返回兩次 返回值為0 ?,代表當(dāng)前進(jìn)程是子進(jìn)程
返回值非負(fù)數(shù),代表當(dāng)前進(jìn)程為父進(jìn)程 調(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 ?說明,有了兩個進(jìn)程!執(zhí)行了兩次打印pid

2. 查看父進(jìn)程/子進(jìn)程代碼:

#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;

}

父子進(jìn)程都會進(jìn)入if 中,但是輸出結(jié)果會不同
在fork之前的pid 是8915 是父進(jìn)程 ? ,fork之后pid是子進(jìn)程 8916

3. 用返回值來判斷父/子進(jìn)程代碼(1):

返回值為0 ?,代表當(dāng)前進(jìn)程是子進(jìn)程
返回值非負(fù)數(shù),代表當(dāng)前進(jìn)程為父進(jìn)程

#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. 用返回值來判斷父子進(jìn)程代碼(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 ?是父進(jìn)程 ? ?父進(jìn)程號是9114
fork 返回值:=0 ? ? 是子進(jìn)程 ? ? ? ? 子進(jìn)程號是9915

三、進(jìn)程創(chuàng)建后 發(fā)生了什么事?

在這里插入圖片描述

1 在內(nèi)存空間中fork后發(fā)生了什么?

在這里插入圖片描述

2. ./demo4 運(yùn)行的程序父進(jìn)程是誰?

int?main(int?argc,?const?char?*argv[])
{
????????while(1);
????????return?0;
}

./ demo4 編譯運(yùn)行后,我們ps -ef 查看進(jìn)程ID由上圖可知,./demo4 進(jìn)程的進(jìn)程ID是12677,父進(jìn)程ID是12587,即進(jìn)程bash:==bash的父進(jìn)程是gnome-terminal,所以我們打開1個Linux終端,其實(shí)就是啟動了1個gnome-terminal進(jìn)程。我們在這個終端上執(zhí)行./a.out其實(shí)就是利用gnome-terminal的子進(jìn)程bash通過execve()將創(chuàng)建的子進(jìn)程裝入a.out:==

四、創(chuàng)建新進(jìn)程的實(shí)際應(yīng)用場景

1. fork創(chuàng)建子進(jìn)程的一般目的:

一個父進(jìn)程希望復(fù)制自己,使父、子進(jìn)程同時執(zhí)行不同的代碼段。這在網(wǎng)絡(luò)服務(wù)進(jìn)程中是常見的——父進(jìn)程等待客戶端的服務(wù)請求。當(dāng)這種情求達(dá)到時,父進(jìn)程調(diào)用fork,使子進(jìn)程處理此請求。父進(jìn)程則繼續(xù)等待下一個服務(wù)請求到達(dá)。

一個進(jìn)程要執(zhí)行一個不同的程序。這對shell是常見的情況,在這種情況下子進(jìn)程從fork返回后立即調(diào)用exec。

在這里插入圖片描述

2. 模擬socket 創(chuàng)建進(jìn)程(服務(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時候,模擬沒有客戶端進(jìn)行交互

輸入1時候,模擬有客戶端進(jìn)行交互 ,創(chuàng)建子進(jìn)程來進(jìn)行交互,子進(jìn)程號為:9756

模擬多個客戶端進(jìn)行交互時 ,創(chuàng)建多個子進(jìn)程來進(jìn)行交互,子進(jìn)程號為:9756 ? / 9758 ?/ 9759

查看系統(tǒng)進(jìn)程:

3. fork總結(jié):

一個現(xiàn)有進(jìn)程可以調(diào)用fork函數(shù)創(chuàng)建一個新進(jìn)程。

#include cunistd.h>
pid_t fork(void);
返回值:子進(jìn)程中返回0。父進(jìn)程中返回子進(jìn)程ID.出錯返回-1

由fork創(chuàng)建的新進(jìn)程被稱為子進(jìn)程(child
process)。fork函數(shù)被調(diào)用一次,但返回兩次。兩次返回的唯一區(qū)別是子進(jìn)程的返回值是0,而父進(jìn)程的返回值則是新子進(jìn)程的進(jìn)程ID。將子進(jìn)程ID返回給父進(jìn)程的理由是:因?yàn)橐粋€進(jìn)程的子進(jìn)程可以有多個,并且沒有一個函數(shù)使一個進(jìn)程可以獲得其所有子進(jìn)程的進(jìn)程ID。fork使子進(jìn)程得到返回值0的理由是:一個進(jìn)程只會有一個父進(jìn)程,所以子進(jìn)程總是可以調(diào)用getppid以獲得其父進(jìn)程的進(jìn)程ID(進(jìn)程ID0總是由內(nèi)核交換進(jìn)程使用,所以一個子進(jìn)程的進(jìn)程ID不可能為0)。

子進(jìn)程和父進(jìn)程繼續(xù)執(zhí)行fork調(diào)用之后的指令。子進(jìn)程是父進(jìn)程的副本。例如,子進(jìn)程獲得父進(jìn)程數(shù)據(jù)空間、堆和棧的副本。注意,這是子進(jìn)程所擁有的副本。父、子進(jìn)程并不共享這些存儲空間部分。父、子進(jìn)程共享正文段。由于在fork之后經(jīng)常跟隨著exec,所以現(xiàn)在的很多實(shí)現(xiàn)并不執(zhí)行一個父進(jìn)程數(shù)據(jù)段、棧和堆的完全復(fù)制。作為替代,使用了寫時復(fù)制(Copy-On-Write,COW)技術(shù)。這些區(qū)域由父、子進(jìn)程共享,而且內(nèi)核將它們的訪問權(quán)限改變?yōu)橹蛔x的。如果父、子進(jìn)程中的任一個試圖修改些區(qū)域,則內(nèi)核只為修改區(qū)域的那塊內(nèi)存制作一個副本,通常是虛擬存儲器系統(tǒng)中的一“頁”。Bach和McKusick等對這種特征做了更詳細(xì)的說明。

五、vfork創(chuàng)建進(jìn)程

1. vfork函數(shù) 也可以創(chuàng)建進(jìn)程,與fork有什么區(qū)別?

關(guān)鍵區(qū)別一:vfork直接使用父進(jìn)程存儲空間,不用拷貝關(guān)鍵區(qū)別二:vfork保證子進(jìn)程先運(yùn)行,當(dāng)子進(jìn)程調(diào)用exit退出后,父進(jìn)程才執(zhí)行

2. fork 進(jìn)程調(diào)度 父子進(jìn)程:

#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 進(jìn)程調(diào)度 父子進(jìn)程:

#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保證子進(jìn)程先運(yùn)行,當(dāng)子進(jìn)程調(diào)用3次 ? exit退出后,父進(jìn)程才執(zhí)行

4. 子進(jìn)程改變cnt值,在父進(jìn)程運(yùn)行時候也被改變

#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命令選項(xiàng)進(jìn)行說明:

命令參數(shù) 說明
-e 顯示所有進(jìn)程.
-f 全格式。
-h 不顯示標(biāo)題。
-l 長格式。
-w 寬輸出。
-a 顯示終端上的所有進(jìn)程,包括其他用戶的進(jìn)程。
-r 只顯示正在運(yùn)行的進(jìn)程。
-u 以用戶為主的格式來顯示程序狀況。
-x 顯示所有程序,不以終端機(jī)來區(qū)分。

ps -ef ?顯示所有進(jìn)程,全格式形式查看進(jìn)程:

ps -ef 的每列的含義是什么呢?

在這里插入圖片描述

命令參數(shù) 說明
UID: 程序被該 UID 所擁有,指的是用戶ID
PID: 就是這個程序的 ID
PPID : PID的上級父進(jìn)程的ID
C : CPU使用的資源百分比
STIME : 系統(tǒng)啟動時間
TTY: 登入者的終端機(jī)位置
TIME : 使用掉的 CPU時間。
CMD: 所下達(dá)的指令為何

七、進(jìn)程退出

1. 子進(jìn)程退出方式

正常退出:

    Mian函數(shù)調(diào)用return進(jìn)程調(diào)用exit(),標(biāo)準(zhǔn)c庫進(jìn)程調(diào)用_exit()或者——Exit(),屬于系統(tǒng)調(diào)用進(jìn)程最后一個線程返回最后一個線程調(diào)用pthread_exit

異常退出:

    調(diào)用abort當(dāng)進(jìn)程收到某些信號時候,如ctrl+C最后一個線程對取消(cancellation),請求作出響應(yīng)

不管進(jìn)程如何終止,最后都會執(zhí)行內(nèi)核中的同一段代碼。這段代碼為相應(yīng)進(jìn)程關(guān)閉所有打開描述符,釋放它所使用的存儲器等。

對上述任意一種終止情形,我們都希望終止進(jìn)程能夠通知其父進(jìn)程它是如何終止的。對于三個終止函數(shù)(exit、_exit和_Exit),實(shí)現(xiàn)這一點(diǎn)的方法是,將其退出狀態(tài)作為參數(shù)傳送給函數(shù)?!救缟厦媸纠锩鎸懙降腸nt==3情況下,exit(0);
這個0就是子進(jìn)程退出狀態(tài)?!吭诋惓=K止情況下,內(nèi)核(不是進(jìn)程本身)產(chǎn)生一個指示其異常終止原因的終止?fàn)顟B(tài)。在任何一種情況下,該終止進(jìn)程的父進(jìn)程都能用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é)束子進(jìn)程的時候要手動退出,不要使用break;會導(dǎo)致數(shù)據(jù)被破壞。?三種退出函數(shù)種,更推薦exit(); ?exit是 _exit 和_Exit 的一個封裝, 會清除,沖刷緩沖區(qū),把緩存區(qū)數(shù)據(jù)進(jìn)程處理在退出。

2. 等待子進(jìn)程退出

==為什么要等待子進(jìn)程退出?==

創(chuàng)建子進(jìn)程的目的就是為了讓它去干活,在網(wǎng)絡(luò)請求當(dāng)中來了一個新客戶端介入,創(chuàng)建子進(jìn)程去交互,干活也要知道它干完沒有.比如正常退出(exit/_exit /_Exit)為 完成任務(wù)
若異常退出 ?(abort)不想干了, 或被殺了

所有要等待子進(jìn)程退出,而且還要收集它退出的狀態(tài)
等待就是調(diào)用wait函數(shù) 和 waitpid函數(shù)

3. 僵尸進(jìn)程

子進(jìn)程退出狀態(tài)不被收集,會變成僵死進(jìn)程(僵尸進(jìn)程)

正如以下例子,就是子進(jìn)程退出沒有被收集,成了僵尸進(jìn)程:

#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;
}

運(yùn)行三次子進(jìn)程后,退出,父進(jìn)程一直運(yùn)行

結(jié)果:在查看進(jìn)程時發(fā)現(xiàn),父進(jìn)程11314正在運(yùn)行 ?“S+” ? 而子進(jìn)程11315 停止運(yùn)行 “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);

如果其所有子進(jìn)程都還在運(yùn)行,則阻塞。:通俗的說就是子進(jìn)程在運(yùn)行的時候,父進(jìn)程卡在wait位置阻塞,等子進(jìn)程退出后,父進(jìn)程開始運(yùn)行。

如果一個子進(jìn)程已終止,正等待父進(jìn)程獲取其終止?fàn)顟B(tài),則會取得該子進(jìn)程的終止?fàn)顟B(tài)立即返回。

如果它沒有任何子進(jìn)程,則立即出錯返回。

status參數(shù):是一個整型數(shù)指針
非空:子進(jìn)程退出狀態(tài)放在它所指向的地址中。空:不關(guān)心退出狀態(tài)

檢查wait 和 waitpid 所返回的終止?fàn)顟B(tài)的宏

說明
WIFEXITED (status) 若為正常終止子進(jìn)程返回的狀態(tài),則為真。對于這種情況可執(zhí)行WEXITSTATUS(status),取子進(jìn)程傳送給exit、_exit 或_Exit參數(shù)的低8位
WIFSIGNALED (status) 若為異常終止子進(jìn)程返回的狀態(tài),則為真(接到一個不捕捉的信號)。對于這種情況,可執(zhí)行WTERMSIG(status),取使子進(jìn)程終止的信號編號。另外,有些實(shí)現(xiàn)(非Single UNIX Specification)宏義宏WCOREDUMP(status),若已產(chǎn)生終止進(jìn)程的core文件,則它返回真
WIFSTOPPED (status) 若為當(dāng)前暫停子進(jìn)程的返回的狀態(tài),則為真,對于這種情況,可執(zhí)行WSTIOPSIG(status),取使子進(jìn)程暫停的信號編號
WIFCONTINUED (status) 若在作業(yè)控制暫停后已經(jīng)繼續(xù)的子進(jìn)程返回了狀態(tài),則為真。(POSIX.1的XSI擴(kuò)展,僅用于waitpid。)
比如說:exit(3) ?wait (狀態(tài)碼); 要通過宏來解析狀態(tài)碼

5. 收集退出進(jìn)程狀態(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子進(jìn)程,這樣就不是僵尸進(jìn)程了

收集子進(jìn)程退出狀態(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); ? 就能看到子進(jìn)程退出的狀態(tài) status=5

6. 等待函數(shù):waitpid()的使用;

wait和waitpid的區(qū)別之一:

wait使父進(jìn)程(調(diào)用者)阻塞,waitpid有一個選項(xiàng) ,可以使父進(jìn)程(調(diào)用者)不阻塞。

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

對于waitpid函數(shù)種pid參數(shù)的作用解釋如下:

pid == -1 等待任一子進(jìn)程。就這一方面而言,waitpid與wait等效。
pid > 0 等待其進(jìn)程ID與pid相等的子進(jìn)程。
pid == 0 等待其組ID等于調(diào)用進(jìn)程組ID的任一子進(jìn)程
pid <-1 等待其組ID等于pid絕對值的任一子進(jìn)程。

waitpid 的 options 常量:

WCONTINUED 若實(shí)現(xiàn)支持作業(yè)控制,那么由pid指定的任一子進(jìn)程在暫停后已經(jīng)繼續(xù),但其狀態(tài)尚未報(bào)告,則返回其狀態(tài)(POSIX.1的XSI擴(kuò)展)
WNOHANG 若由pid指定的子進(jìn)程并不是立即可用的,則waitpid不阻塞,此時其返回值為0;
WUNTRACED 若某實(shí)現(xiàn)支持作業(yè)控制,而由pid指定的任一子進(jìn)程已處于暫停狀態(tài)。

waitpid 來使得父進(jìn)程不阻塞代碼:

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 是子進(jìn)程號,WNOHANG是若由pid指定的子進(jìn)程并不是立即可用的,則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);
???????????????}
??????????}
???}

子進(jìn)程和父進(jìn)程同時進(jìn)行

但是發(fā)現(xiàn)子進(jìn)程12275 ?在系統(tǒng)查詢進(jìn)程中 ?還是變成了僵尸進(jìn)程原因是 ==WNOHANG是不等待參數(shù),它只運(yùn)行一遍== ,當(dāng)他運(yùn)行時候,子進(jìn)程沒死,等子進(jìn)程死后,他沒運(yùn)行,就沒有收到停止?fàn)顟B(tài),所以成了僵尸進(jìn)程。

八、孤兒進(jìn)程

1. 孤兒進(jìn)程的概念:

父進(jìn)程如果不等待子進(jìn)程退出,在子進(jìn)程結(jié)束前就了結(jié)束了自己的“生命”,此時子進(jìn)程就叫做孤兒進(jìn)程。

2.孤兒進(jìn)程被收留:

Linux避免系統(tǒng)存在過多孤兒進(jìn)程,init進(jìn)程收留孤兒進(jìn)程,變成孤兒進(jìn)程的父進(jìn)程【init進(jìn)程(pid=1)是系統(tǒng)初始化進(jìn)程】。init 進(jìn)程會自動清理所有它繼承的僵尸進(jìn)程。

孤兒進(jìn)程的代碼:

#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;
}

父進(jìn)程運(yùn)行結(jié)束前,子進(jìn)程的父進(jìn)程pid還是13098。父進(jìn)程運(yùn)行結(jié)束后,子進(jìn)程的父進(jìn)程變成了init進(jìn)程( pid=1)。

九、exec族函數(shù)

1. exec族函數(shù)的作用:

我們用fork函數(shù)創(chuàng)建新進(jìn)程后,經(jīng)常會在新進(jìn)程中調(diào)用exec函數(shù)去執(zhí)行另外一個程序。當(dāng)進(jìn)程調(diào)用exec函數(shù)時,該進(jìn)程被完全替換為新程序因?yàn)檎{(diào)用exec函數(shù)并不創(chuàng)建新進(jìn)程,所以前后進(jìn)程的ID并沒有改變。

2. 為什么要用exec族函數(shù),有什么作用?

    一個父進(jìn)程希望復(fù)制自己,使父、子進(jìn)程同時執(zhí)行不同的代碼段。這在網(wǎng)絡(luò)服務(wù)進(jìn)程中是常見的——父進(jìn)程等待客戶端的服務(wù)請求。當(dāng)這種請求到達(dá)時,父進(jìn)程調(diào)用fork,使子進(jìn)程處理此請求。父進(jìn)程則繼續(xù)等待下一個服務(wù)請求到達(dá)。一個進(jìn)程要執(zhí)行一個不同的程序。這對shell是常見的情況。在這種情況下,子進(jìn)程從fork返回后立即調(diào)用exec。

3. exec族函數(shù)定義:

功能:

exec函數(shù)族提供了一種在進(jìn)程中啟動另一個程序執(zhí)行的方法,它可以根據(jù)指定的文件名或目錄名找到可執(zhí)行文件,并用它來取代原調(diào)用進(jìn)程的數(shù)據(jù)段、代碼段和堆棧段。在執(zhí)行完之后,原調(diào)用進(jìn)程的內(nèi)容除了進(jìn)程號外,其他全部都被替換了。在調(diào)用進(jìn)程內(nèi)部執(zhí)行一個可執(zhí)行文件,可執(zhí)行文件既可以是二進(jìn)制文件,也可以是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)用點(diǎn)接著往下執(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)用進(jìn)程的環(huán)境變量

4. exec函數(shù) 帶 l ?帶p ?帶v ?來說明參數(shù)特點(diǎn)

先寫一個帶參數(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)用點(diǎn)接著往下執(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)用點(diǎn)接著往下執(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í)行程序

一個程序在目錄下能運(yùn)行,換一個目錄就無法運(yùn)行,如果把程序配置到環(huán)境變量里面去。

==pwd顯示當(dāng)前路徑
echo

PATH: [pwd顯示的當(dāng)前路徑]==

 

就可以在任何目錄下執(zhí)行程序了

6. ?exec配合fork使用

一個進(jìn)程要執(zhí)行一個不同的程序。這對shell是常見的情況。在這種情況下,子進(jìn)程從fork返回后立即調(diào)用exec。

1. 不用exec的方法: 實(shí)現(xiàn)功能,當(dāng)父進(jìn)程檢查到輸入為1的時候,創(chuàng)建子進(jìn)程把配置文件的字段值修改掉。

#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;
}

實(shí)現(xiàn)了當(dāng)父進(jìn)程檢查到輸入為1的時候,創(chuàng)建子進(jìn)程把配置文件的字段值修改掉。

2. 用exec的方法: 實(shí)現(xiàn)功能,當(dāng)父進(jìn)程檢查到輸入為1的時候,創(chuàng)建子進(jìn)程把配置文件的字段值修改掉。

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)生子進(jìn)程,由子進(jìn)程來調(diào)用/bin/sh-c
string來執(zhí)行參數(shù)string字符串所代表的命令,此命令執(zhí)行完后隨即返回原調(diào)用的進(jìn)程。在調(diào)用system()期間SIGCHLD
信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。

返回值:

system()函數(shù)的返回值如下:成功,則返回進(jìn)程的狀態(tài)值;當(dāng)sh不能執(zhí)行時,返回127;失敗返回-1;

2. system函數(shù)的使用:

用system也可以做到execl的功能用system實(shí)現(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運(yùn)行完調(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)準(zhǔn)I/O流)也具有和type相應(yīng) ? 的只讀或只寫類型。如果type是”r“
則文件指針連接到command的標(biāo)準(zhǔn)輸出;如果type是”w“則文件指針連接到command的標(biāo)準(zhǔn)輸入。

返回值:

如果調(diào)用成功,則返回一個讀或者打開文件的指針,如果失敗,返回NULL,具體錯誤要根據(jù)errno判斷

int pclose(FILE *stream)
參數(shù)說明:stream:popen返回對丟文件指針
返回值:如果調(diào)用失敗,返回-1

作用:

popen()函數(shù)用于創(chuàng)建一個管道:其內(nèi)部實(shí)現(xiàn)為調(diào)用fork產(chǎn)生一個子進(jìn)程,執(zhí)行一個shell以運(yùn)行命令來開啟一個進(jìn)程這個進(jìn)程必須由pclose()函數(shù)關(guān)閉。

popen比system 在應(yīng)用中的好處:==可以獲取運(yùn)行的輸出結(jié)果==

popen函數(shù)執(zhí)行完,執(zhí)行結(jié)果到管道內(nèi),數(shù)據(jù)流出的時候,在管道尾部fread就可以讀出執(zhí)行數(shù)據(jù),就能實(shí)現(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可以獲取運(yùn)行的輸出結(jié)果 ,可以讀取也可以寫入文件中。

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

公眾號『一口Linux』號主彭老師,擁有15年嵌入式開發(fā)經(jīng)驗(yàn)和培訓(xùn)經(jīng)驗(yàn)。曾任職ZTE,某研究所,華清遠(yuǎn)見教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專利和軟件著作。精通計(jì)算機(jī)網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實(shí)際項(xiàng)目出發(fā),保持原理+實(shí)踐風(fēng)格,適合Linux驅(qū)動新手入門和技術(shù)進(jìn)階。