進(jìn)程創(chuàng)建
1.fork函數(shù)初識
在Linux上一篇文章進(jìn)程概念詳解我們提到了在linux中fork函數(shù)是非常重要的函數(shù),它從已存在進(jìn)程中創(chuàng)建一個新進(jìn)程。新進(jìn)程為子進(jìn)程,而原進(jìn)程為父進(jìn)程。
返回值
自進(jìn)程中返回0,父進(jìn)程返回子進(jìn)程id,出錯返回-1
1.1那么fork創(chuàng)建子進(jìn)程時,操作系統(tǒng)都做了什么呢?
當(dāng)在操作系統(tǒng)中調(diào)用 fork 函數(shù)創(chuàng)建子進(jìn)程時,操作系統(tǒng)會執(zhí)行以下一系列步驟:
復(fù)制父進(jìn)程: 操作系統(tǒng)會創(chuàng)建一個新的子進(jìn)程,該子進(jìn)程是父進(jìn)程的一個副本。子進(jìn)程將會繼承父進(jìn)程的代碼、數(shù)據(jù)、堆棧、文件描述符等信息。
分配進(jìn)程ID(PID): 操作系統(tǒng)會為新的子進(jìn)程分配一個唯一的進(jìn)程ID(PID)。父進(jìn)程和子進(jìn)程都有不同的PID。
復(fù)制文件描述符表: 子進(jìn)程會復(fù)制父進(jìn)程的文件描述符表。這意味著子進(jìn)程可以訪問與父進(jìn)程相同的打開文件、網(wǎng)絡(luò)連接等資源。
復(fù)制內(nèi)存映像: 子進(jìn)程會復(fù)制父進(jìn)程的內(nèi)存映像,包括代碼段、數(shù)據(jù)段和堆棧。這樣,子進(jìn)程和父進(jìn)程可以開始在不同的內(nèi)存空間中執(zhí)行。
創(chuàng)建唯一的資源: 操作系統(tǒng)會為子進(jìn)程創(chuàng)建一些唯一的資源,如計時器、信號處理等。
設(shè)置返回值: 在父進(jìn)程和子進(jìn)程中,fork 函數(shù)會返回不同的值。在父進(jìn)程中,它返回子進(jìn)程的PID。在子進(jìn)程中,它返回0,表示這是子進(jìn)程。
開始執(zhí)行子進(jìn)程: 子進(jìn)程從 fork 函數(shù)調(diào)用的位置開始執(zhí)行。這意味著子進(jìn)程會執(zhí)行與父進(jìn)程相同的代碼。
總之,fork 函數(shù)通過創(chuàng)建一個幾乎與父進(jìn)程相同的子進(jìn)程,允許父進(jìn)程和子進(jìn)程在獨立的環(huán)境中運行。這是實現(xiàn)多任務(wù)和多進(jìn)程編程的重要機(jī)制之一。
當(dāng)一個進(jìn)程調(diào)用fork之后,就有兩個二進(jìn)制代碼相同的進(jìn)程。而且它們都運行到相同的地方。但每個進(jìn)程都將可以開始它們自己的旅程,看如下程序:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t pid;
7 printf("Before: pid is %d\n", getpid());
8
9 if ( (pid=fork()) == -1 )
10 {
11 perror("fork()");
12 exit(1);
13 }
14 printf("After:pid is %d, fork return %d\n", getpid(), pid);
15 sleep(1);
16 return 0;
17 }
運行結(jié)果
[kingxzq@localhost Documents]$ ./test
Before: pid is 7052
After:pid is 7052, fork return 7053
After:pid is 7053, fork return 0
這里看到了三行輸出,一行before,兩行after
第7行:父進(jìn)程打印了"Before: pid is 7052",表示它的進(jìn)程ID(PID)是7052。
第14行:父進(jìn)程打印了"After:pid is 7052, fork return 7053"。這意味著fork()調(diào)用成功,父進(jìn)程接收到了子進(jìn)程的PID,即7053。
第14行:子進(jìn)程打印了"After:pid is 7053, fork return 0"。在子進(jìn)程中,fork()調(diào)用返回0,表示它是子進(jìn)程。
因此,該程序使用fork()創(chuàng)建了一個子進(jìn)程,父進(jìn)程和子進(jìn)程都從fork()調(diào)用的位置繼續(xù)執(zhí)行。父進(jìn)程接收到子進(jìn)程的PID作為fork()的返回值,而子進(jìn)程接收到0作為返回值。
那么為什么進(jìn)程7053沒有打印before呢?
進(jìn)程7053沒有打印"Before: pid is 7052"是因為在調(diào)用fork()之后,父進(jìn)程和子進(jìn)程是并發(fā)執(zhí)行的。在父進(jìn)程執(zhí)行到打印"Before: pid is 7052"之后,它創(chuàng)建了一個子進(jìn)程。子進(jìn)程繼承了父進(jìn)程的代碼和數(shù)據(jù),包括printf語句,但是子進(jìn)程的輸出緩沖區(qū)是獨立的。
因此,當(dāng)父進(jìn)程執(zhí)行完printf語句后,它的輸出被刷新到終端,而子進(jìn)程的輸出緩沖區(qū)中仍然存在。當(dāng)子進(jìn)程執(zhí)行到打印"After:pid is 7053, fork return 0"時,它的輸出也被刷新到終端。
這就是為什么父進(jìn)程和子進(jìn)程的輸出順序可能會交錯的原因。在這種情況下,父進(jìn)程的輸出先于子進(jìn)程的輸出,因此你看到的輸出是"Before: pid is 7052"在"After:pid is 7053, fork return 0"之前打印的。注意,fork之后,誰先執(zhí)行完全由調(diào)度器決定。
1.2 父子進(jìn)程和CPU中的EIP(指令指針)之間存在一定的關(guān)系
當(dāng)一個程序(進(jìn)程)在執(zhí)行時,CPU會通過EIP來跟蹤下一條要執(zhí)行的指令的內(nèi)存地址。當(dāng)遇到函數(shù)調(diào)用、分支語句或系統(tǒng)調(diào)用等情況時,CPU會根據(jù)程序的邏輯跳轉(zhuǎn)到相應(yīng)的地址執(zhí)行。
在創(chuàng)建子進(jìn)程時,通過fork()系統(tǒng)調(diào)用,操作系統(tǒng)會復(fù)制父進(jìn)程的代碼段、數(shù)據(jù)段和堆棧等信息給子進(jìn)程。這意味著子進(jìn)程會擁有與父進(jìn)程相同的代碼和數(shù)據(jù)。
在fork()調(diào)用之后,父進(jìn)程和子進(jìn)程會在不同的內(nèi)存空間中獨立執(zhí)行。它們各自擁有自己的EIP,用于跟蹤各自的執(zhí)行狀態(tài)。父進(jìn)程和子進(jìn)程的EIP會根據(jù)各自的代碼邏輯獨立地進(jìn)行跳轉(zhuǎn)和執(zhí)行。
因此,父進(jìn)程和子進(jìn)程的EIP是相互獨立的,它們在執(zhí)行過程中不會相互影響。每個進(jìn)程都有自己的EIP,用于指示下一條要執(zhí)行的指令的地址。所以這也就是為什么子進(jìn)程只會執(zhí)行fork函數(shù)之后位置的代碼。
1.3 fork的常規(guī)用法有哪些?
創(chuàng)建子進(jìn)程:最常見的用法是使用fork()創(chuàng)建一個子進(jìn)程。父進(jìn)程調(diào)用fork()后,會創(chuàng)建一個與父進(jìn)程幾乎完全相同的子進(jìn)程。子進(jìn)程從fork()調(diào)用的位置開始執(zhí)行,而父進(jìn)程繼續(xù)執(zhí)行后續(xù)的代碼。
并行處理:通過fork()可以實現(xiàn)并行處理任務(wù)。父進(jìn)程可以將任務(wù)分配給多個子進(jìn)程,每個子進(jìn)程獨立執(zhí)行任務(wù),從而實現(xiàn)并行處理,提高程序的執(zhí)行效率。
進(jìn)程間通信:通過fork()創(chuàng)建的子進(jìn)程可以用于進(jìn)程間通信。父進(jìn)程和子進(jìn)程可以通過管道、共享內(nèi)存、消息隊列等機(jī)制進(jìn)行通信,實現(xiàn)數(shù)據(jù)的交換和共享。
守護(hù)進(jìn)程:守護(hù)進(jìn)程是在后臺運行的長期運行的進(jìn)程,通常通過fork()創(chuàng)建。父進(jìn)程可以通過fork()創(chuàng)建一個子進(jìn)程,并在子進(jìn)程中執(zhí)行守護(hù)進(jìn)程的任務(wù),而父進(jìn)程則可以繼續(xù)執(zhí)行其他任務(wù)或退出。
多進(jìn)程編程:fork()可以用于多進(jìn)程編程,例如使用多個子進(jìn)程同時處理不同的任務(wù),或者使用子進(jìn)程執(zhí)行特定的功能,從而實現(xiàn)更復(fù)雜的程序邏輯。
這些是fork()的一些常規(guī)用法,但并不限于此。fork()是進(jìn)程創(chuàng)建和管理的基礎(chǔ),可以根據(jù)具體的需求和場景進(jìn)行靈活的應(yīng)用。
1.4 fork調(diào)用失敗的原因有哪些?
fork()調(diào)用可能會失敗,導(dǎo)致返回-1。以下是一些可能導(dǎo)致fork()調(diào)用失敗的原因:
系統(tǒng)資源不足:當(dāng)系統(tǒng)中的進(jìn)程數(shù)量已經(jīng)達(dá)到了操作系統(tǒng)的限制時,fork()調(diào)用可能會失敗。這可能是由于系統(tǒng)內(nèi)存不足、進(jìn)程數(shù)量達(dá)到上限或者其他資源限制導(dǎo)致的。
進(jìn)程數(shù)量限制:操作系統(tǒng)可能對每個用戶或每個進(jìn)程組設(shè)置了最大進(jìn)程數(shù)量的限制。當(dāng)達(dá)到這個限制時,fork()調(diào)用可能會失敗。
虛擬內(nèi)存不足:當(dāng)系統(tǒng)的虛擬內(nèi)存空間不足以容納新的進(jìn)程時,fork()調(diào)用可能會失敗。
權(quán)限不足:如果當(dāng)前進(jìn)程沒有足夠的權(quán)限來創(chuàng)建新的進(jìn)程,例如缺少適當(dāng)?shù)臋?quán)限或者超過了進(jìn)程數(shù)量限制,fork()調(diào)用也會失敗。
系統(tǒng)錯誤:其他系統(tǒng)級錯誤,如內(nèi)核錯誤或其他底層問題,也可能導(dǎo)致fork()調(diào)用失敗。
在fork()調(diào)用失敗時,通常會使用perror()函數(shù)打印錯誤信息,并根據(jù)具體的錯誤原因采取適當(dāng)?shù)奶幚泶胧?/p>
2.寫時拷貝
2.1 什么是寫實拷貝?
寫時拷貝(Copy-on-Write,COW)是一種內(nèi)存管理技術(shù),用于在創(chuàng)建子進(jìn)程時延遲復(fù)制父進(jìn)程的內(nèi)存內(nèi)容。在寫時拷貝中,當(dāng)父進(jìn)程創(chuàng)建子進(jìn)程時,子進(jìn)程會與父進(jìn)程共享相同的物理內(nèi)存頁。
在寫時拷貝的情況下,當(dāng)父進(jìn)程或子進(jìn)程嘗試修改共享的內(nèi)存頁時,操作系統(tǒng)會執(zhí)行實際的內(nèi)存復(fù)制操作。這樣,父進(jìn)程和子進(jìn)程就會擁有各自的獨立內(nèi)存副本,而不會相互干擾。
寫時拷貝的主要優(yōu)勢在于節(jié)省內(nèi)存和提高性能。在創(chuàng)建子進(jìn)程時,不需要立即復(fù)制整個父進(jìn)程的內(nèi)存空間,而是共享相同的物理內(nèi)存頁。這樣可以減少內(nèi)存的使用量,并且在父進(jìn)程和子進(jìn)程之間切換時,不需要進(jìn)行大量的內(nèi)存復(fù)制操作,提高了性能。
總結(jié)來說,寫時拷貝是一種延遲復(fù)制的技術(shù),用于在創(chuàng)建子進(jìn)程時共享父進(jìn)程的內(nèi)存,只有在需要修改共享內(nèi)存時才進(jìn)行實際的復(fù)制操作,以提高內(nèi)存利用率和性能。
舉個簡單的例子:
在C語言中,常量字符串是指在代碼中直接使用的字符串字面量,例如:“Hello, World!”。常量字符串在編譯時就會被存儲在程序的只讀數(shù)據(jù)段(常量區(qū))中,而不是在堆?;蚨阎?/mark>。
類似于寫時拷貝,在進(jìn)程調(diào)度中,當(dāng)創(chuàng)建一個新的進(jìn)程時,操作系統(tǒng)通常會延遲復(fù)制父進(jìn)程的內(nèi)存內(nèi)容。這意味著父進(jìn)程和子進(jìn)程會共享相同的物理內(nèi)存頁,包括常量字符串所在的只讀數(shù)據(jù)段。
這種共享常量字符串的方式類似于寫時拷貝的思想。當(dāng)父進(jìn)程或子進(jìn)程嘗試修改共享的常量字符串時,操作系統(tǒng)會執(zhí)行實際的復(fù)制操作,將被修改的字符串復(fù)制到新的內(nèi)存頁中,以確保父進(jìn)程和子進(jìn)程擁有各自的獨立副本。
這種共享常量字符串的方式可以節(jié)省內(nèi)存空間,因為不需要為每個進(jìn)程復(fù)制相同的字符串副本。只有在需要修改字符串時,才會進(jìn)行實際的復(fù)制操作,以確保進(jìn)程間的獨立性。
因此,類似于寫時拷貝,進(jìn)程調(diào)度中的共享常量字符串的方式延遲了復(fù)制操作,提高了內(nèi)存利用率,并在需要修改時才進(jìn)行實際的復(fù)制,以確保進(jìn)程間的獨立性。
通常,父子代碼共享,父子再不寫入時,數(shù)據(jù)也是共享的,當(dāng)任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。具體見下圖:
進(jìn)程終止
1.進(jìn)程退出場景有哪些?
進(jìn)程可以在多種場景下退出。以下是一些常見的進(jìn)程退出場景:
正常退出:進(jìn)程完成了它的任務(wù),并通過調(diào)用exit()系統(tǒng)調(diào)用或從main()函數(shù)中返回來正常退出。在退出之前,進(jìn)程可以釋放資源、保存狀態(tài)或執(zhí)行其他必要的清理操作。
異常退出:進(jìn)程在執(zhí)行過程中遇到了錯誤或異常情況,無法繼續(xù)執(zhí)行下去。這可能是由于內(nèi)存訪問錯誤、除零錯誤、無效指令、段錯誤等導(dǎo)致的。在這種情況下,操作系統(tǒng)會終止進(jìn)程并生成相應(yīng)的錯誤報告。
信號終止:進(jìn)程可以通過接收到特定的信號而終止。例如,當(dāng)進(jìn)程接收到SIGTERM
信號時,它可以選擇優(yōu)雅地終止并執(zhí)行清理操作。另外,一些信號如SIGKILL
和SIGSTOP
是無法被捕獲或忽略的,它們會立即終止進(jìn)程。
父進(jìn)程終止:當(dāng)一個進(jìn)程的父進(jìn)程終止時,操作系統(tǒng)會將該進(jìn)程的父進(jìn)程設(shè)置為init進(jìn)程(通常是進(jìn)程ID為1的進(jìn)程)。如果該進(jìn)程沒有被其他進(jìn)程接管,它可能會成為孤兒進(jìn)程,并由操作系統(tǒng)接管并終止。
資源耗盡:進(jìn)程可能因為系統(tǒng)資源的耗盡而被迫終止。例如,當(dāng)進(jìn)程請求的內(nèi)存超過系統(tǒng)可用內(nèi)存時,操作系統(tǒng)可能會終止該進(jìn)程以保護(hù)系統(tǒng)的穩(wěn)定性。
被其他進(jìn)程終止:其他進(jìn)程可以通過發(fā)送特定的信號(如SIGKILL)來終止目標(biāo)進(jìn)程。這通常是由于需要強(qiáng)制終止進(jìn)程或出于系統(tǒng)管理的目的。
這些是一些常見的進(jìn)程退出場景,但并不限于此。進(jìn)程退出的原因可以是多樣的,具體取決于進(jìn)程的任務(wù)、運行環(huán)境和外部因素。
2.常見查看進(jìn)程退出方法
2.1 正常終止
可以通過 echo $?
查看進(jìn)程退出碼
echo $?
命令用于顯示上一個執(zhí)行的命令的退出狀態(tài)碼(或稱為返回值)。在Unix/Linux
系統(tǒng)中,每個命令在執(zhí)行完畢后都會返回一個退出狀態(tài)碼,用于表示命令執(zhí)行的結(jié)果。
$?
是一個特殊的變量,用于存儲上一個命令的退出狀態(tài)碼。通過在命令行中執(zhí)行echo $?
,可以打印出上一個命令的退出狀態(tài)碼。
退出狀態(tài)碼通常是一個整數(shù)值,其中0表示命令成功執(zhí)行,而非零值表示命令執(zhí)行失敗或出現(xiàn)錯誤。具體的退出狀態(tài)碼的含義可以根據(jù)不同的命令而有所不同,通常會在命令的文檔或手冊中進(jìn)行說明。
echo $?
命令對于調(diào)試和腳本編寫非常有用,可以根據(jù)上一個命令的退出狀態(tài)碼來進(jìn)行條件判斷或錯誤處理。
2.2 異常退出
Ctrl+C
,信號終止
你在終端中按下Ctrl+C
組合鍵時,會發(fā)送一個SIGINT
信號給當(dāng)前正在運行的進(jìn)程。這個信號通常用于請求進(jìn)程終止。
當(dāng)進(jìn)程接收到SIGINT
信號時,默認(rèn)的行為是終止進(jìn)程并進(jìn)行清理操作。這被稱為信號終止。進(jìn)程可以選擇捕獲和處理SIGINT信號,例如執(zhí)行一些清理操作后再終止。
在終端中按下Ctrl+C
時,操作系統(tǒng)會將SIGINT
信號發(fā)送給前臺運行的進(jìn)程組中的所有進(jìn)程。通常情況下,這會導(dǎo)致當(dāng)前正在運行的進(jìn)程終止。
需要注意的是,有些進(jìn)程可能會忽略SIGINT
信號或者通過編寫信號處理程序來自定義處理方式。但是,大多數(shù)情況下,按下Ctrl+C
會導(dǎo)致進(jìn)程異常退出。
2.3 _exit函數(shù)和exit函數(shù)退出
_exit()
函數(shù)和exit()
函數(shù)都用于終止進(jìn)程,但它們之間有一些區(qū)別。
_exit()
函數(shù):
_exit()
函數(shù)是一個系統(tǒng)調(diào)用,用于立即終止進(jìn)程的執(zhí)行。- 它不會執(zhí)行任何清理操作,包括不會刷新緩沖區(qū)、關(guān)閉文件描述符等。
_exit()
函數(shù)的原型為void _exit(int status)
,其中status
參數(shù)是進(jìn)程的退出狀態(tài)碼。- 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的
wait()
或waitpid()
系統(tǒng)調(diào)用來獲取。
說明:雖然status是int,但是僅有低8位可以被父進(jìn)程所用。所以_exit(-1)時,在終端執(zhí)行$?發(fā)現(xiàn)返回值是255
exit()
函數(shù):
exit()
函數(shù)是一個庫函數(shù),用于正常終止進(jìn)程的執(zhí)行。- 在調(diào)用
exit()
函數(shù)之前,會執(zhí)行一些清理操作,例如刷新緩沖區(qū)、關(guān)閉文件描述符等。exit()
函數(shù)的原型為void exit(int status)
,其中status參數(shù)是進(jìn)程的退出狀態(tài)碼。- 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的
wait()
或waitpid()
系統(tǒng)調(diào)用來獲取。
exit
最后也會調(diào)用_exit
, 但在調(diào)用exit之前,還做了其他工作:
- 執(zhí)行用戶通過
atexit
或on_exit
定義的清理函數(shù)。- 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
- 調(diào)用
_exit
總結(jié):
-
_exit()
函數(shù)是一個系統(tǒng)調(diào)用,立即終止進(jìn)程的執(zhí)行,不執(zhí)行清理操作。 -
exit()
函數(shù)是一個庫函數(shù),正常終止進(jìn)程的執(zhí)行,執(zhí)行清理操作后退出。 - 兩者都接受一個退出狀態(tài)碼作為參數(shù),用于表示進(jìn)程的退出狀態(tài)。
- 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的
wait()
或waitpid()
系統(tǒng)調(diào)用來獲取。 -
exit
也是通過調(diào)用_exit
來實現(xiàn)的
2.4 return退出
return是一種更常見的退出進(jìn)程方法。執(zhí)行return n等同于執(zhí)行exit(n),因為調(diào)用main的運行時函數(shù)會將main的返回值當(dāng)做 exit的參數(shù)。
在C語言中,main函數(shù)的返回值類型通常是int類型。根據(jù)C語言標(biāo)準(zhǔn),main函數(shù)的返回值可以是0或者非零的整數(shù)。返回0表示程序成功地執(zhí)行完畢,而非零的返回值通常用于表示程序執(zhí)行過程中的錯誤或異常情況。
非零的返回值可以用于向調(diào)用程序或操作系統(tǒng)報告錯誤信息或狀態(tài)。例如,當(dāng)程序需要在執(zhí)行過程中發(fā)生錯誤時,可以返回一個非零值來指示錯誤的類型或代碼。這樣,調(diào)用程序或操作系統(tǒng)可以根據(jù)返回值來采取相應(yīng)的措施,比如輸出錯誤信息、終止程序或進(jìn)行其他處理。
在實際應(yīng)用中,非零的返回值可以根據(jù)具體需求進(jìn)行定義和使用。不同的程序可能會定義不同的非零返回值來表示不同的錯誤或狀態(tài)。一般來說,返回值的具體含義和用途是由程序員根據(jù)程序的邏輯和需求來決定的。
需要注意的是,main函數(shù)的返回值只能是整數(shù)類型,不能返回其他類型的值。如果需要返回其他類型的值,可以通過全局變量、指針參數(shù)或其他方式來實現(xiàn)。
進(jìn)程等待
1.什么是進(jìn)程等待?
進(jìn)程等待是指一個進(jìn)程在執(zhí)行過程中暫停自己的執(zhí)行,等待某個特定的條件滿足后再繼續(xù)執(zhí)行。進(jìn)程等待的必要性主要體現(xiàn)在以下幾個方面:
同步操作:在多進(jìn)程或多線程的環(huán)境中,進(jìn)程之間可能需要進(jìn)行協(xié)調(diào)和同步。例如,一個進(jìn)程可能需要等待其他進(jìn)程完成某個任務(wù)后才能繼續(xù)執(zhí)行,或者需要等待某個共享資源的釋放。進(jìn)程等待可以確保進(jìn)程之間的操作按照正確的順序進(jìn)行,避免數(shù)據(jù)競爭和不一致的結(jié)果。
資源管理:進(jìn)程等待還可以用于管理系統(tǒng)資源的分配和釋放。當(dāng)一個進(jìn)程需要使用某個資源時,如果該資源已經(jīng)被其他進(jìn)程占用,那么該進(jìn)程可以選擇等待資源的釋放,而不是一直占用CPU資源進(jìn)行忙等待。這樣可以提高系統(tǒng)的資源利用率和效率。
阻塞操作:有些操作需要等待一段時間才能完成,例如網(wǎng)絡(luò)通信、文件讀寫等。在這種情況下,進(jìn)程可以選擇等待操作完成后再繼續(xù)執(zhí)行,而不是一直占用CPU資源進(jìn)行忙等待。這樣可以避免資源的浪費,提高系統(tǒng)的響應(yīng)速度。
總之,進(jìn)程等待是一種有效的管理和調(diào)度進(jìn)程的機(jī)制,可以確保進(jìn)程之間的協(xié)調(diào)和同步,提高系統(tǒng)的資源利用率和效率,以及提供更好的用戶體驗。
2.進(jìn)程等待必要性
之前博客寫過,子進(jìn)程退出,父進(jìn)程如果不管不顧,就可能造成僵尸進(jìn)程的問題,進(jìn)而造成內(nèi)存泄漏。另外,進(jìn)程一旦變成僵尸狀態(tài),那就刀槍不入,kill -9
也無能為力,因為誰也沒有辦法殺死一個已經(jīng)死去的進(jìn)程。最后,父進(jìn)程派給子進(jìn)程的任務(wù)完成的如何,我們需要知道。如,子進(jìn)程運行完成,結(jié)果對還是不對,或者是否正常退出。父進(jìn)程通過進(jìn)程等待的方式,回收子進(jìn)程資源,獲取子進(jìn)程退出信息。
3.進(jìn)程等待的方法
3.1 wait()和waitpid()
wait()pid_t wait(int*status);
返回值:
成功返回被等待進(jìn)程pid,失敗返回-1。
參數(shù):
輸出型參數(shù),獲取子進(jìn)程退出狀態(tài),不關(guān)心則可以設(shè)置成為NULL
waitpid()pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當(dāng)正常返回的時候waitpid返回收集到的子進(jìn)程的進(jìn)程ID;
如果設(shè)置了選項WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進(jìn)程可收集,則返回0;
如果調(diào)用中出錯,則返回-1,這時errno會被設(shè)置成相應(yīng)的值以指示錯誤所在;
參數(shù):
pid:
如果pid等于(pid_t)-1,則請求任何子進(jìn)程的狀態(tài)。在這方面,waitpid()等同于wait()。
如果pid大于0,則指定要請求狀態(tài)的單個子進(jìn)程的進(jìn)程ID。
如果pid為0,則請求任何進(jìn)程組ID與調(diào)用進(jìn)程相同的子進(jìn)程的狀態(tài)。
如果pid小于(pid_t)-1,則請求任何進(jìn)程組ID等于pid的絕對值的子進(jìn)程的狀態(tài)。
status:
WIFEXITED(stat_val)
:如果狀態(tài)是由正常終止的子進(jìn)程返回的,則評估為非零值。WEXITSTATUS(stat_val)
:如果WIFEXITED(stat_val)的值非零,則該宏評估為子進(jìn)程傳遞給_exit()或exit()的狀態(tài)參數(shù)的低8位,或者子進(jìn)程從main()返回的值。WIFSIGNALED(stat_val)
:如果狀態(tài)是由未被捕獲的信號終止的子進(jìn)程返回的,則評估為非零值(參見<signal.h>)。WTERMSIG(stat_val)
:如果WIFSIGNALED(stat_val)的值非零,則該宏評估為導(dǎo)致子進(jìn)程終止的信號編號。WIFSTOPPED(stat_val)
:如果狀態(tài)是由當(dāng)前停止的子進(jìn)程返回的,則評估為非零值。WSTOPSIG(stat_val)
:如果WIFSTOPPED(stat_val)的值非零,則該宏評估為導(dǎo)致子進(jìn)程停止的信號編號。WIFCONTINUED(stat_val)
:如果狀態(tài)是由從作業(yè)控制停止中繼續(xù)的子進(jìn)程返回的,則評估為非零值。
options:
WCONTINUED
:waitpid()函數(shù)將報告由pid指定的任何繼續(xù)運行的子進(jìn)程的狀態(tài),只要該子進(jìn)程自從作業(yè)控制停止后其狀態(tài)尚未被報告。WNOHANG
:如果status對于由pid指定的任何子進(jìn)程不立即可用,waitpid()函數(shù)將不會掛起調(diào)用線程的執(zhí)行(父進(jìn)程非阻塞等待)。WUNTRACED
:任何由pid指定的已停止的子進(jìn)程的狀態(tài),且自從它們停止后其狀態(tài)尚未被報告,也將被報告給請求進(jìn)程。
如果子進(jìn)程已經(jīng)退出,調(diào)用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進(jìn)程退出信息。
如果在任意時刻調(diào)用wait/waitpid,子進(jìn)程存在且正常運行,則進(jìn)程可能阻塞。
如果不存在該子進(jìn)程,則立即出錯返回。
3.2 獲取子進(jìn)程status
wait和waitpid,都有一個status參數(shù),該參數(shù)是一個輸出型參數(shù),由操作系統(tǒng)填充。
如果傳遞NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息。
否則,操作系統(tǒng)會根據(jù)該參數(shù),將子進(jìn)程的退出信息反饋給父進(jìn)程。
status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16bit)
結(jié)合這張圖片我們可以知曉可以用于從狀態(tài)字中提取信號編號和退出碼。
status & 0x7F
:這個表達(dá)式使用了位運算與操作符(&)和一個掩碼(0x7F)。掩碼0x7F的二進(jìn)制表示為01111111,它的作用是將狀態(tài)字中的高位清零,只保留最低的7位。這樣做的目的是提取信號編號,因為信號編號通常存儲在狀態(tài)字的最低位。
(status >> 8) & 0xFF
:這個表達(dá)式使用了右移操作符(>>)和位運算與操作符(&),以及一個掩碼(0xFF)。首先,status >> 8將狀態(tài)字向右移動8位,將退出碼移動到最低位。然后,位運算與操作符&與掩碼0xFF進(jìn)行與操作,將高位清零,只保留最低的8位。這樣做的目的是提取退出碼,因為退出碼通常存儲在狀態(tài)字的高8位。
綜上所述,這種方式通過使用位運算和掩碼,從狀態(tài)字中提取信號編號和退出碼。
3.3 進(jìn)程等待示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int code = 0; // 定義一個全局變量code,用于存儲子進(jìn)程的退出碼
int main()
{
pid_t id = fork(); // 創(chuàng)建一個子進(jìn)程
if(id < 0)
{
perror("fork"); // 如果創(chuàng)建子進(jìn)程失敗,則輸出錯誤信息
exit(1); // 退出程序,返回狀態(tài)碼1
}
else if(id == 0)
{
// 子進(jìn)程
int cnt = 5; // 定義一個計數(shù)器
while(cnt)
{
printf("cnt: %d, 我是子進(jìn)程, pid: %d, ppid : %d\n", cnt, getpid(), getppid()); // 打印子進(jìn)程的信息
cnt--;
sleep(1); // 子進(jìn)程休眠1秒
}
code = 15; // 將全局變量code的值設(shè)置為15
exit(15); // 子進(jìn)程退出,返回退出碼15
}
else
{
// 父進(jìn)程
printf("我是父進(jìn)程, pid: %d, ppid: %d\n", getpid(), getppid()); // 打印父進(jìn)程的信息
int status = 0; // 定義一個變量用于存儲子進(jìn)程的狀態(tài)
pid_t ret = waitpid(id, &status, 0); // 阻塞式的等待子進(jìn)程退出
if(ret > 0)
{
printf("等待子進(jìn)程成功, ret: %d, 子進(jìn)程收到的信號編號: %d, 子進(jìn)程退出碼: %d\n",\
ret, status & 0x7F ,(status >> 8) & 0xFF); // 打印子進(jìn)程的退出信息
printf("code: %d\n", code); // 打印全局變量code的值
}
}
}
這段代碼創(chuàng)建了一個父進(jìn)程和一個子進(jìn)程,父進(jìn)程通過fork()函數(shù)創(chuàng)建子進(jìn)程。子進(jìn)程會打印自己的信息,并在循環(huán)中每秒減少計數(shù)器的值,直到計數(shù)器為0。然后,子進(jìn)程將全局變量code的值設(shè)置為15,并退出。父進(jìn)程會打印自己的信息,并使用waitpid()函數(shù)阻塞等待子進(jìn)程退出。當(dāng)子進(jìn)程退出后,父進(jìn)程會打印子進(jìn)程的退出信息,包括子進(jìn)程收到的信號編號和退出碼,以及全局變量code的值。
第一次運行讓其正常終止,所以沒有信號編號的返回,正常走exit函數(shù)退出,退出碼為15,即為我們自己定義的退出碼。
第二次運行期間我們使用kill -9 子進(jìn)程pid
命令終止子進(jìn)程,信號編號返回9,此時的退出碼并無意義,因為程序非正常退出。
還需要注意的是,這里不管以何種方式終止進(jìn)程,全局變量code始終為0,這是因為子進(jìn)程和父進(jìn)程是兩個獨立的進(jìn)程,它們有各自獨立的內(nèi)存空間。在子進(jìn)程中修改全局變量 code 的值,不會影響父進(jìn)程中的 code 變量。子進(jìn)程的修改只影響子進(jìn)程內(nèi)部的變量,而不會影響父進(jìn)程的變量。
要實現(xiàn)子進(jìn)程修改全局變量并使其對父進(jìn)程可見,可以使用進(jìn)程間通信機(jī)制,例如管道(Pipe)或共享內(nèi)存(Shared Memory)。這樣父子進(jìn)程之間可以共享一塊內(nèi)存區(qū)域,使得修改在兩個進(jìn)程中都可見。
常見的信號編號如下(這里只做了解):
SIGHUP (1): 終端掛起或控制進(jìn)程終止。
SIGINT (2): 中斷信號,通常是Ctrl+C。
SIGQUIT (3): 退出信號,通常是Ctrl+\,會產(chǎn)生核心轉(zhuǎn)儲。
SIGILL (4): 非法指令。
SIGABRT (6): 終止信號,通常是abort()函數(shù)發(fā)出的信號。
SIGFPE (8): 浮點異常。
SIGKILL (9): 強(qiáng)制終止,不能被忽略、阻塞或捕獲。
SIGSEGV (11): 段錯誤,試圖訪問無法訪問的內(nèi)存。
SIGPIPE (13): 管道破裂。
SIGALRM (14): 定時器超時。
SIGTERM (15): 終止信號,常用于請求進(jìn)程正常終止。
SIGUSR1 (10): 用戶自定義信號1。
SIGUSR2 (12): 用戶自定義信號2。
SIGCHLD (17): 子進(jìn)程狀態(tài)改變,例如子進(jìn)程終止時發(fā)送給父進(jìn)程。
SIGCONT (18): 繼續(xù)執(zhí)行一個已停止的進(jìn)程。
SIGSTOP (19): 停止信號,用于停止進(jìn)程的執(zhí)行。
SIGTSTP (20): 終端停止信號,通常是Ctrl+Z。
SIGTTIN (21): 后臺進(jìn)程試圖從控制終端讀取。
SIGTTOU (22): 后臺進(jìn)程試圖向控制終端寫入。
SIGBUS (7): 總線錯誤,試圖訪問不屬于你的內(nèi)存地址。
3.4 進(jìn)程的阻塞等待方式
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/wait.h>
5 int main()
6 {
7 pid_t id = fork();
8 if(id == 0)
9 {
10 //子進(jìn)程
11 printf("子進(jìn)程開始運行, pid: %d\n", getpid());
12 sleep(3);
13 }
14 else
15 {
16 //父進(jìn)程
17 printf("父進(jìn)程開始運行, pid: %d\n", getpid());
18 int status = 0;
19 pid_t id = waitpid(-1, &status, 0); //阻塞等待, 一定是子進(jìn)程先運行完畢,然后父進(jìn)程獲取之后,才退出!
20 if(id > 0)
21 {
22 printf("wait success, exit code: %d\n", WEXITSTATUS(status));
23 }
24 }
25 return 0;
26 }
運行結(jié)果
[kingxzq@localhost Documents]$ ./test1
父進(jìn)程開始運行, pid: 12554
子進(jìn)程開始運行, pid: 12555
wait success, exit code: 0
3.5 進(jìn)程的非阻塞等待方式
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 int main()
6 {
7 pid_t pid;
8 pid = fork();
9 if(pid < 0){
10 printf("%s fork error\n",__FUNCTION__);
11 return 1;
12 }
13 else if( pid == 0 ){ //child
14 printf("child is run, pid is : %d\n",getpid());
15 sleep(5);
16 exit(1);
17 }
18 else{
19 int status = 0;
20 pid_t ret = 0;
21 do
22 {
23 ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
24 if( ret == 0 ){
25 printf("child is running\n");
26 }
27 sleep(1);
28 }while(ret == 0);
29 if( WIFEXITED(status) && ret == pid ){
30 printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
31 }
32 else{
33 printf("wait child failed, return.\n");
34 return 1;
35 }
36 }
37 return 0;
38 }
運行結(jié)果
[kingxzq@localhost Documents]$ ./test
child is running
child is run, pid is : 13231
child is running
child is running
child is running
child is running
wait child 5s success, child return code is :1.
kill命令終止運行結(jié)果
[kingxzq@localhost Documents]$ ./test
child is running
child is run, pid is : 13268
child is running
child is running
child is running
wait child failed, return.
3.6 進(jìn)程的阻塞等待方式和進(jìn)程的非阻塞等待方式有什么區(qū)別
進(jìn)程的阻塞等待方式和進(jìn)程的非阻塞等待方式是兩種不同的等待子進(jìn)程狀態(tài)變化的方式:
阻塞等待:當(dāng)父進(jìn)程調(diào)用等待函數(shù)(如wait、waitpid等)等待子進(jìn)程退出時,父進(jìn)程會一直阻塞(即掛起自己的執(zhí)行),直到子進(jìn)程退出或發(fā)生其他指定的狀態(tài)變化。在等待期間,父進(jìn)程不會繼續(xù)執(zhí)行其他任務(wù)。
非阻塞等待:當(dāng)父進(jìn)程調(diào)用非阻塞等待函數(shù)(如waitpid函數(shù)的使用WNOHANG標(biāo)志)等待子進(jìn)程退出時,父進(jìn)程會繼續(xù)執(zhí)行自己的任務(wù),不會被阻塞。父進(jìn)程會立即返回等待函數(shù),無論子進(jìn)程的狀態(tài)是否發(fā)生變化。非阻塞等待允許父進(jìn)程在等待子進(jìn)程的同時繼續(xù)執(zhí)行其他任務(wù)。
總之,阻塞等待會導(dǎo)致父進(jìn)程在等待子進(jìn)程狀態(tài)變化期間被掛起,而非阻塞等待允許父進(jìn)程在等待子進(jìn)程的同時繼續(xù)執(zhí)行其他任務(wù)。選擇使用哪種等待方式取決于具體的應(yīng)用場景和需求。
進(jìn)程程序替換
1.替換原理
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)以執(zhí)行另一個程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時,該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
2.替換函數(shù)
這些函數(shù)是用于在Linux/Unix操作系統(tǒng)中執(zhí)行新的程序的系統(tǒng)調(diào)用函數(shù)。它們的作用是在一個進(jìn)程內(nèi)啟動一個新的程序執(zhí)行,取代當(dāng)前進(jìn)程的執(zhí)行。這些函數(shù)在C語言標(biāo)準(zhǔn)庫頭文件<unistd.h>中聲明。
下面是對這些函數(shù)的簡要說明:
execl:
原型:
int execl(const char *path, const char *arg, ...);
功能:用于執(zhí)行指定路徑的可執(zhí)行文件,第一個參數(shù)是要執(zhí)行的程序的路徑,后面的參數(shù)是傳遞給新程序的命令行參數(shù),以NULL為結(jié)束標(biāo)志。
示例:execl("/bin/ls", "ls", "-l", NULL);
execlp:
原型:
int execlp(const char *file, const char *arg, ...);
功能:類似于execl,但是它會在系統(tǒng)的路徑中搜索可執(zhí)行文件。
示例:execlp("ls", "ls", "-l", NULL);
execle:
原型:
int execle(const char *path, const char *arg, ..., char *const envp[]);
功能:類似于execl,但是可以指定新程序的環(huán)境變量。最后一個參數(shù)是一個指向環(huán)境變量的指針數(shù)組,以NULL為結(jié)束標(biāo)志。
示例:char *const envp[] = {"PATH=/bin", NULL}; execle("/bin/ls", "ls", "-l", NULL, envp);
execv:
原型:
int execv(const char *path, char *const argv[]);
功能:類似于execl,但是參數(shù)傳遞方式是使用一個指向參數(shù)字符串?dāng)?shù)組的指針。第一個參數(shù)是要執(zhí)行的程序的路徑,第二個參數(shù)是指向參數(shù)字符串?dāng)?shù)組的指針,以NULL為結(jié)束標(biāo)志。
示例:char *const argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv);
execvp:
原型:
int execvp(const char *file, char *const argv[]);
功能:類似于execv,但是它會在系統(tǒng)的路徑中搜索可執(zhí)行文件。
示例:char *const argv[] = {"ls", "-l", NULL}; execvp("ls", argv);
execvpe:
原型:
int execvpe(const char *path, char *const argv[], char *const envp[])
功能:類似于execv,但是可以指定新程序的環(huán)境變量,最后一個參數(shù)是一個指向環(huán)境變量的指針數(shù)組,以NULL為結(jié)束標(biāo)志。
示例:char *const argv[] = {"ls", "-l", NULL}; char *const envp[] = {"PATH=/bin", NULL}; execve("/bin/ls", argv, envp);
這些函數(shù)通常用于在一個進(jìn)程內(nèi)部啟動一個新的程序,新程序取代當(dāng)前進(jìn)程的執(zhí)行。執(zhí)行成功則不會返回,失敗則會返回-1并設(shè)置errno。它們通常用于在C程序中執(zhí)行其他程序,比如在Shell中運行命令。
看下面的兩段代碼:
mycmd.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int main(int argc, char *argv[])
6 {
7 if(argc != 2)
8 {
9 printf("can not execute!\n");
10 exit(1);
11 }
12
13 printf("獲取環(huán)境變量: MY_VAL: %s\n", getenv("MY_VAL"));
14
15 if(strcmp(argv[1], "-a") == 0)
16 {
17 printf("hello a!\n");
18 }
19 else if(strcmp(argv[1], "-b") == 0)
20 {
21 printf("hello b!\n");
22 }
23 else{
24 printf("default!\n");
25 }
26
27 return 0;
28 }
test.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/wait.h>
5
6 #define NUM 16
7
8 const char *myfile = "./mycmd";
9
10 int main(int argc, char*argv[], char *env[])
11 {
12 char *const _env[NUM] = {
13 (char *)"MY_VAL=23333333",
14 NULL
15 };
16 printf("進(jìn)程開始運行, pid: %d\n", getpid());
17 sleep(3);
18 char *const _argv[NUM] = {
19 (char*)"ls",
20 (char*)"-a",
21 (char*)"-l",
22 (char*)"-i",
23 NULL
24 };
25
26 //execl(myfile, "mycmd", "-b", NULL);//調(diào)用自己的進(jìn)程
27 //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//調(diào)用系統(tǒng)ls進(jìn)程,自己輸入字符命令
28 //execlp("./test.py", "test.py", NULL);//調(diào)用自建的python進(jìn)程
29 //execlp("python", "python", "test.py", NULL);//結(jié)果同上,調(diào)用形式不同
30 //execlp("bash", "bash", "test.sh", NULL); //調(diào)用自建的shell進(jìn)程
31 //execlp("ls", "ls", "-a", "-l", NULL); //調(diào)用系統(tǒng)ls進(jìn)程,自己輸入字符命令
32 execle(myfile, "mycmd", "-a", NULL, _env);
33
34 //execv("/usr/bin/ls", _argv); //和上面的execl只有傳參方式的區(qū)別
35 //execvp("ls", _argv);//調(diào)用系統(tǒng)ls進(jìn)程,輸入字符串?dāng)?shù)組名
36 //execvpe("/usr/bin/ls",_argv,env);//效果同execv,多了一個環(huán)境變量參數(shù)
37 return 0;
38 }
運行結(jié)果:
[kingxzq@localhost Documents]$ ./test
進(jìn)程開始運行, pid: 18076
獲取環(huán)境變量: MY_VAL: 23333333
hello a!
3.函數(shù)解釋
這些函數(shù)如果調(diào)用成功則加載新的程序從啟動代碼開始執(zhí)行,不再返回。
如果調(diào)用出錯則返回-1
所以exec函數(shù)只有出錯的返回值而沒有成功的返回值。
4.命名理解
這些函數(shù)原型看起來很容易混,但只要掌握了規(guī)律就很好記
l(list) : 表示參數(shù)采用列表
v(vector) : 參數(shù)用數(shù)組
p(path) : 有p自動搜索環(huán)境變量PATH
e(env) : 表示自己維護(hù)環(huán)境變量
下圖exec函數(shù)族 一個完整的例子:
制作簡易shell
就像系統(tǒng)中的bash(即shell),完成下面這類操作
用下圖的時間軸來表示事件的發(fā)生次序。其中時間從左向右。shell由標(biāo)識為sh的方塊代表,它隨著時間的流逝從左向右移動。shell從用戶讀入字符串"ls"。shell建立一個新的進(jìn)程,然后在那個進(jìn)程中運行l(wèi)s程序并等待那個進(jìn)程結(jié)束。
然后shell讀取新的一行輸入,建立一個新的進(jìn)程,在這個進(jìn)程中運行程序 并等待這個進(jìn)程結(jié)束。
所以要寫一個shell,需要循環(huán)以下過程:
1. 獲取命令行
2. 解析命令行
3. 建立一個子進(jìn)程(fork)
4. 替換子進(jìn)程(execvp)
5. 父進(jìn)程等待子進(jìn)程退出(wait)
代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串
char *g_argv[SIZE];
//環(huán)境變量的buffer
char g_myval[64];
// shell 運行原理 : 通過讓子進(jìn)程執(zhí)行命令,父進(jìn)程等待&&解析命令
int main()
{
extern char**environ;//獲取全局環(huán)境變量的指針
//0. 命令行解釋器,一定是一個常駐內(nèi)存的進(jìn)程,不退出
while(1)
{
//1. 打印出提示信息 [kingxzq@localhost myshell]#
printf("[kingxzq@localhost myshell]# ");
fflush(stdout);
memset(cmd_line, '\0', sizeof cmd_line);
//2. 獲取用戶的鍵盤輸入[輸入的是各種指令和選項: "ls -a -l -i"]
if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
{
continue;
}
if (strlen(cmd_line) <= 1) { // 如果只有回車換行符,長度為1
continue;//輸入為空重新輸入
}
cmd_line[strlen(cmd_line)-1] = '\0';//用\0將\n替換
//"ls -a -l -i\n\0"
//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
g_argv[0] = strtok(cmd_line, SEP); //第一次調(diào)用,要傳入原始字符串
int index = 1;
if(strcmp(g_argv[0], "ls") == 0)
{
g_argv[index++] = "--color=auto";//添加自動顏色
}
if(strcmp(g_argv[0], "ll") == 0)//ll本身為ls -l,所以單獨添加一個命令
{
g_argv[0] = "ls";
g_argv[index++] = "-l";
g_argv[index++] = "--color=auto";
}
while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果還要解析原始字符串,傳入NULL
if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)//添加環(huán)境變量
{
strcpy(g_myval, g_argv[1]);
int ret = putenv(g_myval);//輸入環(huán)境變量
if(ret == 0) printf("%s export success\n", g_argv[1]);
continue;
}
//4.內(nèi)置命令, 讓父進(jìn)程(shell)自己執(zhí)行的命令,我們叫做內(nèi)置命令,內(nèi)建命令
//內(nèi)建命令本質(zhì)其實就是shell中的一個函數(shù)調(diào)用
if(strcmp(g_argv[0], "cd") == 0) //cd命令調(diào)用
{
if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..
continue;
}
//5. fork()
pid_t id = fork();
if(id == 0) //child
{
printf("下面功能讓子進(jìn)程進(jìn)行的\n");
printf("child, MYVAL: %s\n", getenv("MYVAL"));//獲取我們輸入的環(huán)境變量MYVAL
printf("child, PATH: %s\n", getenv("PATH"));//獲取環(huán)境變量路徑
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));//退出碼接收
}
return 0;
}
測試ls命令
測試添加環(huán)境變量和cd命令
文章來源:http://www.zghlxwxcb.cn/news/detail-635124.html
結(jié)語
有興趣的小伙伴可以關(guān)注作者,如果覺得內(nèi)容不錯,請給個一鍵三連吧,蟹蟹你喲?。。?br> 制作不易,如有不正之處敬請指出
感謝大家的來訪,UU們的觀看是我堅持下去的動力
在時間的催化劑下,讓我們彼此都成為更優(yōu)秀的人吧??!!文章來源地址http://www.zghlxwxcb.cn/news/detail-635124.html
到了這里,關(guān)于一篇文章教會你什么是Linux進(jìn)程控制的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!