Linux/UNIX系統(tǒng)編程手冊(cè)-進(jìn)程

Linux/UNIX系統(tǒng)編程手冊(cè)

[德] Michael Kerrisk

第6章 進(jìn)程
第24章 進(jìn)程的創(chuàng)建
第25章 進(jìn)程的終止
第26章 監(jiān)控子進(jìn)程

進(jìn)程

進(jìn)程和程序(Processes and Programs)

進(jìn)程是一個(gè)可執(zhí)行程序的實(shí)例(A process is an instance of an executing program).

程序(program)是包含了一系列信息的文件女嘲,這些信息描述了如何在運(yùn)行時(shí)創(chuàng)建一個(gè)進(jìn)程,所包含的信息有:

  • 二進(jìn)制格式標(biāo)識(shí)(Binary format identification): 每個(gè)程序文件都包含用于描述可執(zhí)行文件格式的元信息(metainformation)。內(nèi)核利用此信息來(lái)解釋文件中的其他信息。大多數(shù)UNIX(包括Linux)采用Executable
    and Linking Format (ELF).
  • 機(jī)器語(yǔ)言指令(Machine-language instructions): 對(duì)程序算法進(jìn)行編碼
  • 程序入口地址(Program entry-point address):標(biāo)識(shí)程序開(kāi)始執(zhí)行時(shí)起始指令位置
  • 數(shù)據(jù)(Data): 變量初始值和程序使用的字面常量(literal constant)
  • 符號(hào)表及重定位表(Symbol and relocation tables): 描述程序中函數(shù)和變量的位置及名稱(chēng)蜕提。
  • 共享庫(kù)和動(dòng)態(tài)鏈接信息(Shared-library and dynamic-linking information): 程序運(yùn)行需要的共享庫(kù)霎匈,以及加載共享庫(kù)的動(dòng)態(tài)鏈接器的路徑名
  • 其他信息: 描述如何創(chuàng)建進(jìn)程

可以用一個(gè)程序創(chuàng)建多個(gè)進(jìn)程茴迁。

進(jìn)程是由內(nèi)核定義的抽象的實(shí)體夸盟,并為該實(shí)體分配用以執(zhí)行程序的各項(xiàng)系統(tǒng)資源秽荞。

進(jìn)程號(hào)和父進(jìn)程號(hào)

進(jìn)程號(hào)(PID)骤公,是用來(lái)唯一標(biāo)識(shí)系統(tǒng)中某個(gè)進(jìn)程的一個(gè)整數(shù)。對(duì)系統(tǒng)調(diào)用來(lái)說(shuō)扬跋,進(jìn)程號(hào)可以作為參數(shù)傳入阶捆,kill()系統(tǒng)調(diào)用;也可以作為返回值,比如getpid()系統(tǒng)調(diào)用洒试。

Linux內(nèi)核限制進(jìn)程號(hào)需要<=32767, 可以調(diào)整倍奢。

$ cat /proc/sys/kernel/pid_max
32768

每個(gè)進(jìn)程都有一個(gè)創(chuàng)建自己的父進(jìn)程,使用系統(tǒng)調(diào)用getppid()獲取父進(jìn)程的進(jìn)程號(hào)垒棋。

使用pstree命令可以查看進(jìn)程樹(shù)卒煞。

進(jìn)程內(nèi)存布局(Memory Layout of a Process)

每個(gè)進(jìn)程所分配的內(nèi)存由很多部分組成,通常稱(chēng)為“段(segments)”叼架,或者“區(qū)(section)”:

  • 文本段(text segment): 包含進(jìn)程運(yùn)行的機(jī)器語(yǔ)言指令畔裕。文本段具有只讀屬性,同時(shí)可共享乖订,使多個(gè)進(jìn)程使用同一份程序代碼拷貝柴钻。
  • 初始化的數(shù)據(jù)段(initialized data segment): 包含顯示初始化的全局變量和靜態(tài)變量。
  • 未初始化數(shù)據(jù)段(uninitialized data segment):也被稱(chēng)為BSS(block started by symbol)垢粮。 包含未進(jìn)行顯式初始化的全局變量和靜態(tài)變量贴届。程序啟動(dòng)之前系統(tǒng)將本段內(nèi)所有內(nèi)存初始化為0。
  • 棧(stack): 動(dòng)態(tài)變化的segment蜡吧,由棧幀(stack frames)組成毫蚓。系統(tǒng)會(huì)為每個(gè)當(dāng)前調(diào)用的函數(shù)分配一個(gè)棧幀,其中存儲(chǔ)函數(shù)的局部變量昔善,實(shí)參和返回值元潘。
  • 堆(heap): 在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的區(qū)域。堆頂端被稱(chēng)為program break君仆。

size命令可顯示文本段翩概,初始化和未初始化數(shù)據(jù)段(bss)的大小

> $ man size
NAME
       size - list section sizes and total size.
...
> $ size hello
   text    data     bss     dec     hex filename
   1230     548       4    1782     6f6 hello

虛擬內(nèi)存管理(Virtual Memory Management)

進(jìn)程的內(nèi)存布局存在與虛擬內(nèi)存(Virtual Memory)中。虛擬內(nèi)存的規(guī)劃之一就是將每個(gè)程序使用的內(nèi)存切割成小型的返咱、固定大小的“頁(yè)”(page)單元钥庇。相應(yīng)地,將RAM換成一系列與“頁(yè)”大小相同的頁(yè)幀咖摹。

為支持這一組織形式评姨,內(nèi)核為每個(gè)進(jìn)程維護(hù)一張頁(yè)表(page table),用于記錄每頁(yè)在進(jìn)程虛擬地址空間的位置萤晴。

虛擬內(nèi)存管理使進(jìn)程的虛擬地址空間與RAM物理地址空間隔離開(kāi)來(lái)吐句。

棧和棧幀(the stack and stack frame)

函數(shù)的調(diào)用和返回使棧的增長(zhǎng)和收縮呈線(xiàn)性。棧駐留在內(nèi)存的高端比你高向下增長(zhǎng)(朝堆的方向)店读。專(zhuān)用寄存器--棧指針(stack pointer)嗦枢,用于跟蹤當(dāng)前棧頂。每次調(diào)用函數(shù)和返回函數(shù)時(shí)屯断,都是在棧上新增和移去棧幀文虏。

棧幀(user stack), 一般指用戶(hù)棧侣诺,區(qū)分與內(nèi)核棧,包含一下信息:

  • 函數(shù)實(shí)參和局部變量
  • (函數(shù))調(diào)用的鏈接信息:每個(gè)函數(shù)都會(huì)用到一些CPU寄存器择葡,比如程序計(jì)數(shù)器紧武。比如調(diào)用另一個(gè)函數(shù)時(shí)剃氧,保存當(dāng)前寄存器狀態(tài)敏储,以便返回時(shí)恢復(fù)。

命令行參數(shù)(command-line argument)朋鞍,argc, argv

  • int argc:命令行參數(shù)的個(gè)數(shù)
  • char *argv[]: 指向命令行參數(shù)的指針數(shù)組已添,每一參數(shù)都是以空字符('\0')結(jié)尾的字符串.

程序可以通過(guò)/proc/PID/cmdline文件訪(fǎng)問(wèn)任一進(jìn)程的命令行參數(shù),每個(gè)參數(shù)都以空(NULL)字節(jié)終止滥酥。

argv和environ(環(huán)境變量)數(shù)組更舞,以及這些參數(shù)最初只想的字符串,都主流在進(jìn)程棧上的一個(gè)單一坎吻、連續(xù)的內(nèi)存區(qū)域缆蝉。

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

創(chuàng)建新進(jìn)程: fork()系統(tǒng)調(diào)用

fork()創(chuàng)建一個(gè)新進(jìn)程(child),幾近于對(duì)調(diào)用進(jìn)程(parent)的翻版

#include <unistd.h>
pid_t fork(void);
In parent: returns process ID of child on success, or –1 on error;
in successfully created child: always returns 0

完成對(duì)其調(diào)用后將存在兩個(gè)進(jìn)程瘦真,每個(gè)進(jìn)程都會(huì)從fork()的返回處繼續(xù)執(zhí)行刊头,程序代碼可通過(guò)fork()的返回值來(lái)區(qū)分父子進(jìn)程。在父進(jìn)程中诸尽,fork()將返回新創(chuàng)建子進(jìn)程的進(jìn)程ID原杂,在子進(jìn)程中則返回0。
子進(jìn)程也可調(diào)用getpid(), getppid()分別獲得自身進(jìn)程以及父進(jìn)程的ID您机。

執(zhí)行fork()時(shí)候穿肄,子進(jìn)程會(huì)獲得父進(jìn)程所有文件描述符的副本,也即父子進(jìn)程共享打開(kāi)的文件及其屬性际看。

從概念上講咸产,fork()認(rèn)作對(duì)父進(jìn)程程序代碼段,數(shù)據(jù)段仲闽,堆棧的拷貝锐朴,實(shí)際上,子進(jìn)程一般會(huì)替換代碼段蔼囊,并重新初始化數(shù)據(jù)焚志,堆棧,全拷貝就造成了浪費(fèi)畏鼓。因此UNIX采用兩種技術(shù)來(lái)避免這種浪費(fèi):

  • 內(nèi)核將每一進(jìn)程的代碼段標(biāo)記為只讀酱酬,父子進(jìn)程共享該代碼段。
  • 對(duì)于數(shù)據(jù)段云矫,堆棧中各頁(yè)膳沽,內(nèi)核采用寫(xiě)時(shí)復(fù)制技術(shù)(copy-on-write)。即內(nèi)核會(huì)捕捉進(jìn)程中針對(duì)頁(yè)的修改企圖,并為將要修改的頁(yè)創(chuàng)建拷貝挑社。

fork()之后的競(jìng)爭(zhēng)條件(race condition)

競(jìng)爭(zhēng)表現(xiàn)在調(diào)用fork()后陨界,無(wú)法確定父、子進(jìn)程誰(shuí)將率先訪(fǎng)問(wèn)CPU痛阻。Linux在版本升級(jí)中菌瘪,多次調(diào)整默認(rèn)優(yōu)先的進(jìn)程。
由于會(huì)產(chǎn)生所謂“競(jìng)爭(zhēng)條件”的錯(cuò)誤阱当,不應(yīng)對(duì)fork()之后執(zhí)行父俏扩、子進(jìn)程的特定順序做任何假設(shè)。如若需要保證執(zhí)行順序弊添,需要采用同步技術(shù)录淡,包括信號(hào)量(semaphore)、文件鎖(file lock)以及進(jìn)程間經(jīng)由管道(pipe)的消息發(fā)送油坝。

進(jìn)程的終止

_exit()和exit()

進(jìn)程可能通過(guò)兩種方式終止:

  • 異常(abnormal)終止:接受到終止信號(hào)(signal)嫉戚,可能產(chǎn)生核心轉(zhuǎn)儲(chǔ)(core dump)
  • 進(jìn)程使用系統(tǒng)調(diào)用_exit()自主終止
#include <unistd.h>
void _exit(int status);

_exit()的status參數(shù)定義了進(jìn)程的終止?fàn)顟B(tài),父進(jìn)程可以調(diào)用wait()獲取該狀態(tài)澈圈。雖然定義為int類(lèi)型彬檀,但僅有低8位可以被父進(jìn)程使用。調(diào)用_exit()的程序總會(huì)成功終止极舔,即使從不返回凤覆。

一般使用庫(kù)函數(shù)exit()來(lái)終止進(jìn)程,它會(huì)在調(diào)用_exit()前執(zhí)行各種動(dòng)作:

  • 調(diào)用退出處理程序(通過(guò)atexit()和on_exit()注冊(cè)的函數(shù))
  • 刷新stdio流緩沖區(qū)
  • 使用由status提供的值執(zhí)行_exit()系統(tǒng)調(diào)用

監(jiān)控子進(jìn)程

父進(jìn)程需要了解其某個(gè)子進(jìn)程何時(shí)改變了狀態(tài)拆魏,用于監(jiān)控子進(jìn)程有兩種方式:

  • 系統(tǒng)調(diào)用wait()
  • 信號(hào)SIGCHLD

等待子進(jìn)程

系統(tǒng)調(diào)用wait()

wait()等待進(jìn)程的任一子進(jìn)程終止盯桦,同時(shí)在參數(shù)status所指向的緩沖區(qū)中返回該子進(jìn)程的終止?fàn)顟B(tài)

#include <sys/wait.h>
pid_t wait(int *status);
    Returns process ID of terminated child, or –1 on error

wait()執(zhí)行一下動(dòng)作:

  • 如果調(diào)用之前還沒(méi)有子進(jìn)程終止,則一直阻塞渤刃,如果有拥峦,則立即返回
  • 如果status非空,那么關(guān)于子進(jìn)程如何終止的信息則會(huì)通過(guò)status指向的整型變量返回
  • 內(nèi)核將會(huì)為父進(jìn)程下所有子進(jìn)程的運(yùn)行總量追加進(jìn)程CPU時(shí)間以及資源使用數(shù)據(jù)
  • 將終止子進(jìn)程的ID作為wait()的結(jié)果返回

系統(tǒng)調(diào)用waitpid()

wait()存在諸多限制卖子,而waitpid()則意在突破這些限制

  • 無(wú)法指定某個(gè)特定的子進(jìn)程略号,只能按循序等待下一個(gè)子進(jìn)程終止
  • 沒(méi)有進(jìn)程退出,則wait()總是阻塞
  • wait()只能發(fā)現(xiàn)終止的子進(jìn)程洋闽,而對(duì)終止原因及恢復(fù)執(zhí)行情況無(wú)能為力
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
    Returns process ID of child, 0 (see text), or –1 on error

等待狀態(tài)值

wait()和waitpid()返回的status值玄柠,可用來(lái)區(qū)分一下子進(jìn)程事件:

  • 子進(jìn)程調(diào)用_exit()或exit()而終止,并指定一個(gè)整型值作為退出狀態(tài)
  • 子進(jìn)程收到未處理信號(hào)終止
  • 子進(jìn)程因?yàn)樾盘?hào)而暫停诫舅,并以WUNTRACED標(biāo)志調(diào)用waitpid()
  • 子進(jìn)程因收到信號(hào)SIGCONT而恢復(fù)羽利,并以WCONTINUED標(biāo)志調(diào)用waitpid()

同時(shí)還有waitid(), wait3()和wait4()

孤兒進(jìn)程和僵尸進(jìn)程(Orphan and ZOmbie)

  • 孤兒進(jìn)程:父進(jìn)程先于子進(jìn)程終止,此時(shí)init會(huì)接管該進(jìn)程刊懈,對(duì)getppid()的調(diào)用將返回1
  • 僵尸進(jìn)程这弧,父進(jìn)程在wait()之前娃闲,子進(jìn)程就已終止,內(nèi)核會(huì)將子進(jìn)程轉(zhuǎn)為僵尸進(jìn)程匾浪,即釋放資源皇帮,但是保留該進(jìn)程在進(jìn)程表里的一條記錄,包含進(jìn)程id蛋辈,終止?fàn)顟B(tài)属拾,資源使用數(shù)據(jù)等信息。

父進(jìn)程應(yīng)執(zhí)行wait()方法梯浪,以確保系統(tǒng)中總是能夠清理那些死去的子進(jìn)程捌年。

SIGCHLD信號(hào)

無(wú)論一個(gè)子進(jìn)程何時(shí)終止瓢娜,系統(tǒng)都會(huì)向其父進(jìn)程發(fā)送SIGCHLD信號(hào)挂洛。
對(duì)該信號(hào)的默認(rèn)處理時(shí)將其忽略,可以通過(guò)設(shè)置信號(hào)處理程序signal()或sigaction()來(lái)捕獲眠砾,同時(shí)編寫(xiě)信號(hào)處理函數(shù)使用wait()來(lái)處理僵尸進(jìn)程虏劲。

原文鏈接
https://sun2y.me

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褒颈,隨后出現(xiàn)的幾起案子柒巫,更是在濱河造成了極大的恐慌,老刑警劉巖谷丸,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堡掏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡刨疼,警方通過(guò)查閱死者的電腦和手機(jī)泉唁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揩慕,“玉大人亭畜,你說(shuō)我怎么就攤上這事∮保” “怎么了拴鸵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)蜗搔。 經(jīng)常有香客問(wèn)我劲藐,道長(zhǎng),這世上最難降的妖魔是什么樟凄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任聘芜,我火速辦了婚禮,結(jié)果婚禮上不同,老公的妹妹穿的比我還像新娘厉膀。我一直安慰自己溶耘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布服鹅。 她就那樣靜靜地躺著凳兵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪企软。 梳的紋絲不亂的頭發(fā)上庐扫,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音仗哨,去河邊找鬼形庭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厌漂,可吹牛的內(nèi)容都是我干的萨醒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼苇倡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼富纸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旨椒,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晓褪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后综慎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涣仿,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年示惊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了好港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涝涤,死狀恐怖媚狰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阔拳,我是刑警寧澤崭孤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站糊肠,受9級(jí)特大地震影響辨宠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜货裹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一嗤形、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弧圆,春花似錦赋兵、人聲如沸笔咽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叶组。三九已至,卻和暖如春历造,著一層夾襖步出監(jiān)牢的瞬間甩十,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工吭产, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侣监,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓臣淤,卻偏偏與公主長(zhǎng)得像橄霉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荒典,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容