1.3進(jìn)程的控制

引出

我們學(xué)習(xí)了進(jìn)程,是為了去用多進(jìn)程,那么,為什么需要用到多進(jìn)程呢?

1:為了提高效率,支持大用戶量的并發(fā)。

2:一些大型的服務(wù)器程序,是常年補(bǔ)下電的,需要一直運行,而服務(wù)器系統(tǒng)崩潰是很嚴(yán)重的事情,那么,就需要有多進(jìn)程,一個進(jìn)程掛掉了不能影響另外一個進(jìn)程的使用,這樣,對用戶來說,是體會不到的,不影響業(yè)務(wù)的進(jìn)行。

舉例:銀行怎么提高效率?多開幾個窗口,每個窗口都在做業(yè)務(wù)。

舉例:之前在華為的網(wǎng)關(guān)產(chǎn)品項目中使用多進(jìn)程的舉例。

一:進(jìn)程的創(chuàng)建

一個現(xiàn)有進(jìn)程(父進(jìn)程)可以通過調(diào)用fork()函數(shù)來創(chuàng)建一個跟現(xiàn)有進(jìn)程一模一樣的新進(jìn)程(子進(jìn)程)。

頭文件:? #include <unistd.h>

函數(shù)原型:pid_t fork(void);pid_t 實際就是int類型。

返回值:? 如果創(chuàng)建子進(jìn)程成功,則返回給父進(jìn)程的是子進(jìn)程的id,返回給子進(jìn)程的是0。

? ? ? ? ? 如果失敗,則返回給父進(jìn)程的是-1,并置errno。

注意:1:子進(jìn)程創(chuàng)建的過程:會復(fù)制父進(jìn)程的所有資源,包括堆,棧,rodata段,data段,bss段, 緩沖區(qū)。但是系統(tǒng)相關(guān)信息,代碼段共享。

? ? ? 2:fork之后父,子進(jìn)程誰先執(zhí)行是不確定,取決系統(tǒng)的調(diào)度算法。

? ? ? 3:fork之后,父子進(jìn)程都是從fork下一條語句開始執(zhí)行。

? ? ? 4:fork之后,父子進(jìn)程擁有獨立的4G虛擬地址空間?;ハ嗖挥绊?。

? ? ? 5:fork之后,子進(jìn)程會繼承父進(jìn)程的打開的文件描述符集合,共享文件狀態(tài)標(biāo)志位和文件的偏移量。

例如:打開一個文件,在父親進(jìn)程中用文件描述符偏移到文件的中間。如果此時創(chuàng)建子進(jìn)程,則子進(jìn)程也是也是從文件的中間開始的。

步驟:1:int main(){fork();printf("hello\n");}? hello打印了兩遍,說明進(jìn)程已經(jīng)創(chuàng)建。

2:查看返回值,并且在父子進(jìn)程中個分別打印自己的id。

? 3:解釋下邊代碼運行結(jié)果打印兩邊hello的原因。(原因就是fork出來的子進(jìn)程,會完全的復(fù)制父進(jìn)程的所有資源,包括緩沖區(qū))

? ? printf("hello");

? ? pid = fork();


===================================================================

僵尸子進(jìn)程:子進(jìn)程結(jié)束的時候,父進(jìn)程沒有進(jìn)行收尸操作(父進(jìn)程還存在),此時占用資源。

孤兒進(jìn)程:父進(jìn)程結(jié)束了,子進(jìn)程會變成孤兒進(jìn)程,會自動被init進(jìn)程所收養(yǎng)。

===================================================================

我們看到man幫助中,有個copy_on_write這樣的字眼,這個是什么東西?我們來拓展介紹一下。

《寫時拷貝技術(shù)》

<2>vfork

#include <sys/types.h>

#include <unistd.h>

pid_t vfork(void);

功能:創(chuàng)建子進(jìn)程

參數(shù):無

返回值:成功,對父進(jìn)程而言。返回子進(jìn)程的PID好。

? ? ? ? ? ? ? 對子進(jìn)程而言。返回0.

? ? ? ? ? ? ? 錯誤,返回-1。

fork與vfork的區(qū)別:

<1>fork函數(shù)父子進(jìn)程誰先運行不確定,由系統(tǒng)調(diào)度決定。

? vfork函數(shù)子進(jìn)程先運行,此時父進(jìn)程會阻塞,子進(jìn)程會一直運行在父進(jìn)程的地址空間,直到子進(jìn)程調(diào)用exit結(jié)束后才會運行,如果這時子進(jìn)程修改了某個變量,這將影響到父進(jìn)程的變量。


<2>fork 函數(shù)的正文段共享,其他段被子進(jìn)程復(fù)制。

? ? vfork函數(shù)的子進(jìn)程直接共享父進(jìn)程的虛擬地址空間。

<3> 來看一下父子進(jìn)程對同一文件的操作

先open一個文件,再fork,然后分別再父子進(jìn)程中寫入內(nèi)容,結(jié)果是追加寫

先fork,然后分在父子進(jìn)程中打開同一文件,然后分別寫入內(nèi)容,結(jié)果分別寫

為什么?下邊第一個圖是兩個獨立的進(jìn)程打開同一個文件,第二個圖是先open,再fork之后父子進(jìn)程對文件共享的關(guān)系圖。文件表項是在內(nèi)核空間,進(jìn)程間共享的。


二:進(jìn)程的退出

1:相關(guān)退出函數(shù)

? <1>return? 結(jié)束一個函數(shù)的執(zhí)行。(當(dāng)前程序不一定結(jié)束。)

<2>void exit(int status)[庫函數(shù)]

功能:結(jié)束一個進(jìn)程。結(jié)束之前會刷新緩沖區(qū)。

參數(shù):@status? ? 進(jìn)程狀態(tài)的標(biāo)志。0表示正常結(jié)束,其他表示異常結(jié)束。

<3>void? _exit(int status) [系統(tǒng)調(diào)用]

功能:結(jié)束一個進(jìn)程。結(jié)束之前不會刷新緩沖區(qū)。

參數(shù):@status

2:return和exit的區(qū)別

2.1. return返回函數(shù)值,是關(guān)鍵字;exit是一個函數(shù)。

2.2. return是語言級別的,它表示了調(diào)用堆棧的返回;而exit是系統(tǒng)調(diào)用級別的,它表示了一個進(jìn)程的結(jié)束。

2.3. return是函數(shù)的退出(返回);exit是進(jìn)程的退出。

2.4. return是C語言提供的,exit是操作系統(tǒng)提供的(或者函數(shù)庫中給出的)。

2.5. return用于結(jié)束一個函數(shù)的執(zhí)行,將函數(shù)的執(zhí)行信息傳出個其他調(diào)用函數(shù)使用;exit函數(shù)是退出應(yīng)用程序,刪除進(jìn)程使用的內(nèi)存空間,并將應(yīng)用程序的一個狀態(tài)返回給OS,這個狀態(tài)標(biāo)識了應(yīng)用程序的一些運行信息,這個信息和機(jī)器和操作系統(tǒng)有關(guān),一般是?0 為正常退出,非0 為非正常退出。

2.6. 非主函數(shù)中調(diào)用return和exit效果很明顯,但是在main函數(shù)中調(diào)用return和exit的現(xiàn)象就很模糊,多數(shù)情況下現(xiàn)象都是一致的。

3:exit和_exit的區(qū)別

3.1:exit是庫函數(shù),_exit是系統(tǒng)調(diào)用,exit是基于_exit的實現(xiàn)。

3.2:exit退出進(jìn)程會清理IO緩沖區(qū),_eixt不會。

三:進(jìn)程的替換(exec函數(shù)族)

1.環(huán)境變量

或者稱為全局變量,存在與所有的shell 中,在你登陸系統(tǒng)的時候就已經(jīng)有了相應(yīng)的系統(tǒng)定義的環(huán)境變量了。Linux 的環(huán)境變量具有繼承性,即子shell 會繼承父shell 的環(huán)境變量。

查看當(dāng)前系統(tǒng)的全部環(huán)境變量信息:env命令

修改當(dāng)前系統(tǒng)環(huán)境變量信息:直接修改對應(yīng)的環(huán)境變量的值(臨時的,只再當(dāng)前shell生效)

? ? ? ? ? ? ? ? ? ? ? ? ? 永久修改:修改配置文件(按照層級)? ?

/etc/profile.d->?/etc/bashrc?->~/.bash_profile?->?~/.bashrc?

~/.bash_profile? 用戶登錄時被讀取,其中包含的命令被執(zhí)行。

? ? ~/.bashrc? 啟動新的shell時被讀取,并執(zhí)行。

需要重點關(guān)注的:PATH:決定了shell將到哪些目錄中尋找命令或程序

舉例:a.out的執(zhí)行,不帶./看是否可以?

? ? ? 修改PATH環(huán)境變量,把a(bǔ).out的文件所在路徑加進(jìn)去,然后再次執(zhí)行。

2. exec函數(shù)族

?<1>頭文件

#include <unistd.h>

<2>函數(shù)原型

extern char **environ;

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

int execle(const char *path, const char *arg, ..., char * const envp[]);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]);

返回值:成功返回0,失敗返回-1。

參數(shù):@param path: 可執(zhí)行文件的路徑名

? @param file: 可執(zhí)行文件名,只能搜索環(huán)境變量 PATH 指定的路徑

? @parma arg: 可執(zhí)行文件名以及參數(shù),參數(shù)列表需要以 NULL 結(jié)尾

? @param argv[]: 參數(shù)數(shù)組,可以取代參數(shù)列表

? @param evnp[]: 環(huán)境變量數(shù)組

?應(yīng)用舉例:? ? ?

execl("/bin/ls","ls","-l",NULL);? ./a.out? argv[0]:./a.out? 1 + 2? argv[1]

execlp("ls","ls","-l",NULL);

char * const envp[] = {"USER=root","PATH=/bin",NULL};

execle("./app","./app","1",NULL,envp) ;

char * const argv[] = {"ls","-l",NULL};

execv("/bin/ls",argv);

char * const argv[] = {"ls","-l",NULL};

execvp("ls",argv) ;

char * const argv[] = {"ls","-l",NULL};

char * const envp[] = {"USER=root","PATH=/bin",NULL};

execvpe("ls",argv,envp);

.* 練習(xí)

1.自己寫一個calc.c,要求實現(xiàn)加減乘除功能。gcc calc.c -o calc

./calc 12 + 20

2.自己寫一個execl_home.c,要求使用execl調(diào)用calc打印相應(yīng)的內(nèi)容

參考代碼:calc.c,main.c?

calc.c:./calc 10 - 20? ? 10

execl_home.c:./execl 10 + 20

.*練習(xí)

實現(xiàn)一個簡單的shell。

問題描述參考《myshell的實現(xiàn)》? ?

strtok函數(shù)的使用。

四:進(jìn)程的等待

僵尸進(jìn)程:子進(jìn)程結(jié)束的時候,父進(jìn)程沒有進(jìn)行收尸操作(父進(jìn)程還存在),此時子進(jìn)程還占用資源。這時候的子進(jìn)程就是僵尸進(jìn)程。

父進(jìn)程回收子進(jìn)程資源的時機(jī):(1)父進(jìn)程結(jié)束? (2)處理子進(jìn)程結(jié)束時候發(fā)送的SIGCHILD信號來回收資源。

僵尸進(jìn)程的危害:僵尸態(tài)子進(jìn)程已經(jīng)結(jié)束,它占用大部分資源已經(jīng)釋放,但是仍然保留PID資源。 如果父進(jìn)程一直不退出,就一直不會回收子進(jìn)程的僵尸資源,這樣產(chǎn)生僵尸態(tài)子進(jìn)程過多,會導(dǎo)致PID資源耗盡,創(chuàng)建子進(jìn)程失敗。

那為什么還要設(shè)計僵尸進(jìn)程呢?給進(jìn)程設(shè)置僵尸狀態(tài)的目的是維護(hù)子進(jìn)程的信息,以便父進(jìn)程在以后某個時間獲取。這些信息包括子進(jìn)程的進(jìn)程ID、終止?fàn)顟B(tài)以及資源利用信息(CPU時間,內(nèi)存使用量等等)。

解決辦法:為了防止產(chǎn)生僵尸進(jìn)程,在fork子進(jìn)程之后我們都要wait它們;同時,當(dāng)子進(jìn)程退出的時候,內(nèi)核都會給父進(jìn)程一個SIGCHLD信號,所以我們可以建立一個捕獲SIGCHLD信號的信號處理函數(shù),在函數(shù)體中調(diào)用wait(或waitpid),就可以清理退出的子進(jìn)程以達(dá)到防止僵尸進(jìn)程的目的。

<1>wait的用法

函數(shù)原型:pid_t wait(int? *status)

函數(shù)功能:回收僵尸態(tài)子進(jìn)程,如果沒有僵尸態(tài)的子進(jìn)程則阻塞,如果沒有子進(jìn)程會立即返回。

函數(shù)參數(shù):@status? 是一個整型指針,指向的對象用來保存子進(jìn)程退出時的狀態(tài)

? a. status若是為NULL ,表示忽略子進(jìn)程退出時的狀態(tài)。

? b. status若是不為NULL ,表示保存子進(jìn)程退出時的狀態(tài)。

返回值:成功返回僵尸態(tài)子進(jìn)程的PID,失敗返回-1(沒有子進(jìn)程)。

大部分的時候,我們不需要關(guān)注子進(jìn)程退出時候的狀態(tài),只是想把這個僵尸子進(jìn)程干掉,這個時候,我們就不需要傳遞實際的status獲取狀態(tài),直接傳NULL進(jìn)去就可以了,但是,也有的時候,我們是需要關(guān)注這個狀態(tài)的,比如我確實需要知道子進(jìn)程是不是正常退出的,還是異常退出的。那我們呢就需要知道傳出來的status不同的值代表什么意思。

WIFEXITED(status)? ? 宏返回真表示進(jìn)程正常退出

WEXITSTATUS(status)? 取得子進(jìn)程exit()返回的結(jié)束代碼,一般會先用WIFEXITED來判斷是否正常結(jié)束,然后才使用此宏。

===============================================

WIFSIGNALED(status)? 如果子進(jìn)程是因為信號而結(jié)束則,返回值為非0 。

? ? ? ? ? ? ? ? ? ? 否則,返回值為0。

WTERMSIG(status)? ? 取得子進(jìn)程因信號而中止的信號代碼,一般會先用 WIFSIGNALED來判斷后,然后才使用此宏。

注意:wait函數(shù)是以阻塞(暫停)方式等待子進(jìn)程結(jié)束,等待當(dāng)前父進(jìn)程的任一子進(jìn)程的退出!

? ? ? 如果是多個子進(jìn)程,要實現(xiàn)對所有子進(jìn)程的收尸操作,需要循環(huán)調(diào)用wait來實現(xiàn)!

思考:什么是阻塞,什么是非阻塞呢?

阻塞: 得到調(diào)用的結(jié)果之前。一直等待。直到獲得了結(jié)果再去做其他的事情。

非阻塞:得到調(diào)用的結(jié)果之前。你可以做其它的事情。當(dāng)獲得了結(jié)果告訴我一聲就可以了。

例如:exit(5) 結(jié)束子進(jìn)程。則我們調(diào)用WEXITSTATUS(status)就返回5.

練習(xí):

fork一個子進(jìn)程,子進(jìn)程打印自己的pid,然后死循環(huán),(用信號終止子進(jìn)程)

父進(jìn)程wait子進(jìn)程結(jié)束,要獲得子進(jìn)程終止的信號編號。

<2>waitpid的用法

waitpid 函數(shù)常見用法如下:

1. 使用非阻塞的方式等待特定子進(jìn)程退出

while(waitpid(pid,&status,WNOHANG) == 0)

usleep(50000);

2. 阻塞等待任意子進(jìn)程退出

waitpid(-1,&status,0);====wait(&status)

waitpid(-1,NULL,0);=====wait(NULL);

3. 非阻塞等待任意子進(jìn)程退出

waitpid(-1,&status,WNOHANG);

4.阻塞等待特定子進(jìn)程的退出

waitpid(pid,&status, 0);

.*練習(xí)

父? 子

使用waitpid(pid, NULL,0)指定這個子進(jìn)程,阻塞式的等待它退出

waitpid(-1, &sta, WNOHANG)非阻塞等待任意子進(jìn)程退出,獲取它的退出狀態(tài),正常退出打印退出碼,因為信號退出,打印對應(yīng)的信號值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Linux 進(jìn)程管理與程序開發(fā) 進(jìn)程是Linux事務(wù)管理的基本單元,所有的進(jìn)程均擁有自己獨立的處理環(huán)境和系統(tǒng)資源,...
    JamesPeng閱讀 2,576評論 1 14
  • ### main函數(shù)執(zhí)行之前做了什么?(iOS) & dyld 是Apple 的動態(tài)鏈接器;在 xnu 內(nèi)核為程...
    天使君閱讀 767評論 0 1
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,235評論 0 38
  • 一、進(jìn)程的創(chuàng)建和調(diào)度 相關(guān)概念: 最基礎(chǔ)的計算機(jī)動作被稱為指令(instruction)。 程序(program)...
    穹藍(lán)奧義閱讀 5,166評論 0 6
  • 學(xué)習(xí)完整課程請移步 互聯(lián)網(wǎng) Java 全棧工程師 本節(jié)視頻 【視頻】基礎(chǔ)設(shè)施即服務(wù)-Linux-簡介 概述 Lin...
    擼帝閱讀 298評論 0 5

友情鏈接更多精彩內(nèi)容