国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

一篇文章教會你什么是Linux進(jìn)程控制

這篇具有很好參考價值的文章主要介紹了一篇文章教會你什么是Linux進(jìn)程控制。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

進(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)程。
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
返回值
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

自進(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)任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。具體見下圖:
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

進(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í)行清理操作。另外,一些信號如SIGKILLSIGSTOP是無法被捕獲或忽略的,它們會立即終止進(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)程退出碼

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

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ù):

  1. _exit()函數(shù)是一個系統(tǒng)調(diào)用,用于立即終止進(jìn)程的執(zhí)行。
  2. 它不會執(zhí)行任何清理操作,包括不會刷新緩沖區(qū)、關(guān)閉文件描述符等。
  3. _exit()函數(shù)的原型為void _exit(int status),其中status參數(shù)是進(jìn)程的退出狀態(tài)碼。
  4. 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的wait()waitpid()系統(tǒng)調(diào)用來獲取。

說明:雖然status是int,但是僅有低8位可以被父進(jìn)程所用。所以_exit(-1)時,在終端執(zhí)行$?發(fā)現(xiàn)返回值是255

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
exit()函數(shù):

  1. exit()函數(shù)是一個庫函數(shù),用于正常終止進(jìn)程的執(zhí)行。
  2. 在調(diào)用exit()函數(shù)之前,會執(zhí)行一些清理操作,例如刷新緩沖區(qū)、關(guān)閉文件描述符等。
  3. exit()函數(shù)的原型為void exit(int status),其中status參數(shù)是進(jìn)程的退出狀態(tài)碼。
  4. 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的wait()waitpid()系統(tǒng)調(diào)用來獲取。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
exit最后也會調(diào)用_exit, 但在調(diào)用exit之前,還做了其他工作:

  1. 執(zhí)行用戶通過 atexiton_exit定義的清理函數(shù)。
  2. 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
  3. 調(diào)用_exit

總結(jié)

  1. _exit()函數(shù)是一個系統(tǒng)調(diào)用,立即終止進(jìn)程的執(zhí)行,不執(zhí)行清理操作。
  2. exit()函數(shù)是一個庫函數(shù),正常終止進(jìn)程的執(zhí)行,執(zhí)行清理操作后退出。
  3. 兩者都接受一個退出狀態(tài)碼作為參數(shù),用于表示進(jìn)程的退出狀態(tài)。
  4. 進(jìn)程的退出狀態(tài)碼可以通過父進(jìn)程的wait()waitpid()系統(tǒng)調(diào)用來獲取。
  5. 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()

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
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)。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

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)程返回的,則評估為非零值。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

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)程。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

如果子進(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)
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
結(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的值。

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
第一次運行讓其正常終止,所以沒有信號編號的返回,正常走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并未改變。
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

2.替換函數(shù)

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
這些函數(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)境變量

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
下圖exec函數(shù)族 一個完整的例子:
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

制作簡易shell

就像系統(tǒng)中的bash(即shell),完成下面這類操作

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
用下圖的時間軸來表示事件的發(fā)生次序。其中時間從左向右。shell由標(biāo)識為sh的方塊代表,它隨著時間的流逝從左向右移動。shell從用戶讀入字符串"ls"。shell建立一個新的進(jìn)程,然后在那個進(jìn)程中運行l(wèi)s程序并等待那個進(jìn)程結(jié)束。
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
然后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命令
一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos
測試添加環(huán)境變量和cd命令

一篇文章教會你什么是Linux進(jìn)程控制,Linux,linux,服務(wù)器,網(wǎng)絡(luò),vim,后端,centos

結(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • C++初階之一篇文章教會你list(模擬實現(xiàn))

    C++初階之一篇文章教會你list(模擬實現(xiàn))

    成員類型表 這個表中列出了C++標(biāo)準(zhǔn)庫中l(wèi)ist容器的一些成員類型定義。這些類型定義是為了使list能夠與C++標(biāo)準(zhǔn)庫的其他組件協(xié)同工作,并提供一些通用的標(biāo)準(zhǔn)接口。每個成員類型的用處: value_type : 這個成員類型代表list容器中存儲的數(shù)據(jù)類型,即模板參數(shù)T的類型。 allocator_

    2024年02月12日
    瀏覽(26)
  • C++初階之一篇文章教會你list(理解和使用)

    C++初階之一篇文章教會你list(理解和使用)

    在C++標(biāo)準(zhǔn)庫中, std::list 是一個雙向鏈表容器,用于存儲一系列元素。與 std::vector 和 std::deque 等容器不同, std::list 使用鏈表的數(shù)據(jù)結(jié)構(gòu)來組織元素,因此在某些操作上具有獨特的優(yōu)勢和性能特點。以下是關(guān)于 std::list 的詳細(xì)介紹: 雙向鏈表結(jié)構(gòu): std::list 內(nèi)部使用雙向鏈表來

    2024年02月13日
    瀏覽(29)
  • Vue中的Pinia狀態(tài)管理工具 | 一篇文章教會你全部使用細(xì)節(jié)

    Vue中的Pinia狀態(tài)管理工具 | 一篇文章教會你全部使用細(xì)節(jié)

    Pinia(發(fā)音為/pi?nj?/,如英語中的“peenya”)是最接近pi?a(西班牙語中的菠蘿)的詞 ; Pinia開始于大概2019年,最初是 作為一個實驗為Vue重新設(shè)計狀態(tài)管理 ,讓它用起來適合組合式API(Composition API)。 從那時到現(xiàn)在,最初的設(shè)計原則依然是相同的,并且目前同時兼容Vue2、

    2024年02月11日
    瀏覽(23)
  • 一篇文章教會你一個優(yōu)秀的程序員如何維護(hù)好自己的電腦

    一篇文章教會你一個優(yōu)秀的程序員如何維護(hù)好自己的電腦

    我認(rèn)為程序員的筆記本電腦可以根據(jù)不同的特點和用途分為幾類 這里介紹的都是些筆記本 以下是一些常見的分類和它們的特點: 輕薄便攜筆記本(Ultrabooks) 優(yōu)點: 便攜性 :輕薄設(shè)計和輕便重量,適合在不同地方工作。 性能 :雖然不如游戲筆記本那樣強(qiáng)大,但在性能和續(xù)

    2024年02月14日
    瀏覽(19)
  • 一篇文章教會你寫一個貪吃蛇小游戲(純C語言)

    一篇文章教會你寫一個貪吃蛇小游戲(純C語言)

    實現(xiàn)基本的功能 : ? 貪吃蛇地圖繪制 ? 蛇吃?物的功能(上、下、左、右?向鍵控制蛇的動作) ? 蛇撞墻死亡 ? 蛇撞??死亡 ? 計算得分 ? 蛇?加速、減速 ? 暫停游戲 Win32 API是一套由Microsoft提供的應(yīng)用程序編程接口,用于開發(fā)Windows平臺上的應(yīng)用程序。它包括了豐

    2024年01月22日
    瀏覽(32)
  • 數(shù)據(jù)結(jié)構(gòu)入門(C語言版)一篇文章教會你手撕八大排序

    數(shù)據(jù)結(jié)構(gòu)入門(C語言版)一篇文章教會你手撕八大排序

    排序 :所謂排序,就是使一串記錄,按照其中的某個或某些的大小,遞增或遞減的排列起來的操作。 穩(wěn)定性 :假定在待排序的記錄序列中,存在多個具有相同的的記錄,若經(jīng)過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而

    2024年02月01日
    瀏覽(23)
  • 怎樣在一臺電腦安裝多個版本的JDK并切換使用?一篇文章教會你所有細(xì)節(jié)

    怎樣在一臺電腦安裝多個版本的JDK并切換使用?一篇文章教會你所有細(xì)節(jié)

    目錄 1. 下載安裝JDK版本 2. 配置環(huán)境變量 2. 1 配置環(huán)境變量的步驟 2.2 需要注意的細(xì)節(jié)點 2.3 JDK8,11,17版本切換測試 a . JDK8 下載鏈接: Java Downloads | Oracle https://www.oracle.com/java/technologies/downloads/#java8-windows b.? 這里我先插一句,因為我們要安裝多個JDK版本,所以我們最好提前創(chuàng)

    2024年04月16日
    瀏覽(20)
  • 如何真正認(rèn)識 Linux 系統(tǒng)結(jié)構(gòu)?這篇文章告訴你

    如何真正認(rèn)識 Linux 系統(tǒng)結(jié)構(gòu)?這篇文章告訴你

    ?? ? Linux 內(nèi)核 內(nèi)核是操作系統(tǒng)的核心,具有很多最基本功能,它負(fù)責(zé)管理系統(tǒng)的進(jìn)程、內(nèi)存、設(shè)備驅(qū)動程序、文件和網(wǎng)絡(luò)系統(tǒng),決定著系統(tǒng)的性能和穩(wěn)定性。 Linux 內(nèi)核由如下幾部分組成:內(nèi)存管理、進(jìn)程管理、設(shè)備驅(qū)動程序、文件系統(tǒng)和網(wǎng)絡(luò)管理等。如圖: ? 系統(tǒng)調(diào)用接

    2024年02月01日
    瀏覽(26)
  • 通過一篇文章讓你了解Linux的重要性

    通過一篇文章讓你了解Linux的重要性

    Linux是一種自由和開放源代碼的操作系統(tǒng),由林納斯·托瓦茲于1991年首次發(fā)布。它基于Unix,具有模塊化設(shè)計,支持多任務(wù)和多用戶,能在多種硬件平臺上運行。Linux系統(tǒng)在全球范圍內(nèi)得到廣泛應(yīng)用,包括服務(wù)器、移動設(shè)備、嵌入式系統(tǒng)等領(lǐng)域。其強(qiáng)大的功能、穩(wěn)定性和安全性

    2024年04月15日
    瀏覽(25)
  • 初識Linux(上),看了這篇文章,媽媽再也不用擔(dān)心我Linux找不到門了。

    初識Linux(上),看了這篇文章,媽媽再也不用擔(dān)心我Linux找不到門了。

    “我會定期分享我的學(xué)習(xí)經(jīng)驗,也歡迎大家留言和交流,讓我們共同學(xué)習(xí)和進(jìn)步!感謝大家的支持,讓我們一起開啟這段充滿技術(shù)樂趣的旅程吧!” 系列文章 初識Linux(上).媽媽再也不用擔(dān)心我Linux找不到門了。 初識Linux(中).媽媽再也不用擔(dān)心我Linux找不到門了。 初識Linux(下

    2024年02月05日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包