技術(shù)交流QQ群:1027579432,歡迎你的加入!
一、概念的理解
- 首先程序與進(jìn)程是什么?程序與進(jìn)程又有什么區(qū)別?
- 程序(procedure):不太精確地說(shuō),程序就是執(zhí)行一系列有邏輯、有順序結(jié)構(gòu)的指令,幫我們達(dá)成某個(gè)結(jié)果。就如我們?nèi)ゲ宛^,給服務(wù)員說(shuō)我要牛肉蓋澆飯,她執(zhí)行了做牛肉蓋澆飯這么一個(gè)程序,最后我們得到了這么一盤牛肉蓋澆飯。它需要去執(zhí)行,不然它就像一本武功秘籍,放在那里等人翻看。
- 進(jìn)程(process):進(jìn)程是程序在一個(gè)數(shù)據(jù)集合上的一次執(zhí)行過(guò)程,在早期的UNIX、Linux 2.4及更早的版本中,它是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立基本單位。同上一個(gè)例子,就如我們?nèi)チ瞬宛^,給服務(wù)員說(shuō)我要牛肉蓋澆飯,她執(zhí)行了做牛肉蓋澆飯這么一個(gè)程序,而里面做飯的是一個(gè)進(jìn)程,做牛肉湯汁的是一個(gè)進(jìn)程,把牛肉湯汁與飯混合在一起的是一個(gè)進(jìn)程,把飯端上桌的是一個(gè)進(jìn)程。它就像是我們?cè)诳次涔γ丶@么一個(gè)過(guò)程,然后一個(gè)篇章一個(gè)篇章地去練。
- 簡(jiǎn)單來(lái)說(shuō),程序是為了完成某種任務(wù)而設(shè)計(jì)的軟件,比如 vim 是程序。什么是進(jìn)程呢?進(jìn)程就是運(yùn)行中的程序。
- 程序只是一些列指令的集合,是一個(gè)靜止的實(shí)體,而進(jìn)程不同,進(jìn)程有以下的特性:
- 動(dòng)態(tài)性:進(jìn)程的實(shí)質(zhì)是一次程序執(zhí)行的過(guò)程,有創(chuàng)建、撤銷等狀態(tài)的變化。而程序是一個(gè)靜態(tài)的實(shí)體。
- 并發(fā)性:進(jìn)程可以做到在一個(gè)時(shí)間段內(nèi),有多個(gè)程序在運(yùn)行中。程序只是靜態(tài)的實(shí)體,所以不存在并發(fā)性。
- 獨(dú)立性:進(jìn)程可以獨(dú)立分配資源,獨(dú)立接受調(diào)度,獨(dú)立地運(yùn)行。
- 異步性:進(jìn)程以不可預(yù)知的速度向前推進(jìn)。
- 結(jié)構(gòu)性:進(jìn)程擁有代碼段、數(shù)據(jù)段、PCB(進(jìn)程控制塊,進(jìn)程存在的唯一標(biāo)志)。也正是因?yàn)橛薪Y(jié)構(gòu)性,進(jìn)程才可以做到獨(dú)立地運(yùn)行。
- 并發(fā):在一個(gè)時(shí)間段內(nèi),宏觀來(lái)看有多個(gè)程序都在活動(dòng),有條不紊的執(zhí)行(每一瞬間只有一個(gè)在執(zhí)行,只是在一段時(shí)間有多個(gè)程序都執(zhí)行過(guò))
- 并行:在每一個(gè)瞬間,都有多個(gè)程序都在同時(shí)執(zhí)行,這個(gè)必須有多個(gè) CPU 才行
- 引入進(jìn)程是因?yàn)閭鹘y(tǒng)意義上的程序已經(jīng)不足以描述 OS 中各種活動(dòng)之間的動(dòng)態(tài)性、并發(fā)性、獨(dú)立性還有相互制約性。程序就像一個(gè)公司,只是一些證書,文件的堆積(靜態(tài)實(shí)體)。而當(dāng)公司運(yùn)作起來(lái)就有各個(gè)部門的區(qū)分,財(cái)務(wù)部,技術(shù)部,銷售部等等,就像各個(gè)進(jìn)程,各個(gè)部門之間可以獨(dú)立運(yùn)做,也可以有交互(獨(dú)立性、并發(fā)性)。而隨著程序的發(fā)展越做越大,又會(huì)繼續(xù)細(xì)分,從而引入了線程的概念,當(dāng)代多數(shù)操作系統(tǒng)、Linux 2.6及更新的版本中,進(jìn)程本身不是基本運(yùn)行單位,而是線程的容器。就像上述所說(shuō)的,每個(gè)部門又會(huì)細(xì)分為各個(gè)工作小組(線程),而工作小組需要的資源需要向上級(jí)(進(jìn)程)申請(qǐng)。
-
線程(thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。因?yàn)榫€程中幾乎不包含系統(tǒng)資源,所以執(zhí)行更快、更有效率。簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。另外,進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。就如下圖所示:
概念形象比喻.png
二、進(jìn)程的屬性
- 2.1進(jìn)程的分類
- 大概明白進(jìn)程是個(gè)什么樣的存在后,我們需要進(jìn)一步了解的就是進(jìn)程分類。可以從兩個(gè)角度來(lái)分:
- 以進(jìn)程的功能與服務(wù)的對(duì)象來(lái)分;
- 以應(yīng)用程序的服務(wù)類型來(lái)分;
- 第一個(gè)角度來(lái)看,我們可以分為用戶進(jìn)程與系統(tǒng)進(jìn)程:
- 用戶進(jìn)程:通過(guò)執(zhí)行用戶程序、應(yīng)用程序或稱之為內(nèi)核之外的系統(tǒng)程序而產(chǎn)生的進(jìn)程,此類進(jìn)程可以在用戶的控制下運(yùn)行或關(guān)閉。
- 系統(tǒng)進(jìn)程:通過(guò)執(zhí)行系統(tǒng)內(nèi)核程序而產(chǎn)生的進(jìn)程,比如可以執(zhí)行內(nèi)存資源分配和進(jìn)程切換等相對(duì)底層的工作;而且該進(jìn)程的運(yùn)行不受用戶的干預(yù),即使是 root 用戶也不能干預(yù)系統(tǒng)進(jìn)程的運(yùn)行。
- 第二角度來(lái)看,我們可以將進(jìn)程分為交互進(jìn)程、批處理進(jìn)程、守護(hù)進(jìn)程
- 交互進(jìn)程:由一個(gè) shell 終端啟動(dòng)的進(jìn)程,在執(zhí)行過(guò)程中,需要與用戶進(jìn)行交互操作,可以運(yùn)行于前臺(tái),也可以運(yùn)行在后臺(tái)。
- 批處理進(jìn)程:該進(jìn)程是一個(gè)進(jìn)程集合,負(fù)責(zé)按順序啟動(dòng)其他的進(jìn)程。
- 守護(hù)進(jìn)程:守護(hù)進(jìn)程是一直運(yùn)行的一種進(jìn)程,在 Linux 系統(tǒng)啟動(dòng)時(shí)啟動(dòng),在系統(tǒng)關(guān)閉時(shí)終止。它們獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。例如 httpd 進(jìn)程,一直處于運(yùn)行狀態(tài),等待用戶的訪問(wèn)。還有經(jīng)常用的 cron(在 centOS 系列為 crond)進(jìn)程,這個(gè)進(jìn)程為 crontab 的守護(hù)進(jìn)程,可以周期性的執(zhí)行用戶設(shè)定的某些任務(wù)。
- 大概明白進(jìn)程是個(gè)什么樣的存在后,我們需要進(jìn)一步了解的就是進(jìn)程分類。可以從兩個(gè)角度來(lái)分:
- 2.2 進(jìn)程的衍生
- 進(jìn)程有這么多的種類,那么進(jìn)程之間定是有相關(guān)性的,而這些有關(guān)聯(lián)性的進(jìn)程又是如何產(chǎn)生的,如何衍生的?就比如我們啟動(dòng)了終端,就是啟動(dòng)了一個(gè) bash 進(jìn)程,我們可以在 bash 中再輸入 bash 則會(huì)再啟動(dòng)一個(gè) bash 的進(jìn)程,此時(shí)第二個(gè) bash 進(jìn)程就是由第一個(gè) bash 進(jìn)程創(chuàng)建出來(lái)的,他們之間又是個(gè)什么關(guān)系?一般稱呼第一個(gè) bash 進(jìn)程是第二 bash 進(jìn)程的父進(jìn)程,第二 bash 進(jìn)程是第一個(gè) bash 進(jìn)程的子進(jìn)程,這層關(guān)系是如何得來(lái)的呢?關(guān)于父進(jìn)程與子進(jìn)程便會(huì)提及這兩個(gè)系統(tǒng)調(diào)用 fork() 與 exec()
- fork-exec是由 Dennis M. Ritchie 創(chuàng)造的
- fork() 是一個(gè)系統(tǒng)調(diào)用(system call),它的主要作用就是為當(dāng)前的進(jìn)程創(chuàng)建一個(gè)新的進(jìn)程,這個(gè)新的進(jìn)程就是它的子進(jìn)程,這個(gè)子進(jìn)程除了父進(jìn)程的返回值和 PID 以外其他的都一模一樣,如進(jìn)程的執(zhí)行代碼段,內(nèi)存信息,文件描述,寄存器狀態(tài)等等
- exec() 也是系統(tǒng)調(diào)用,作用是切換子進(jìn)程中的執(zhí)行程序也就是替換其從父進(jìn)程復(fù)制過(guò)來(lái)的代碼段與數(shù)據(jù)段
- 子進(jìn)程就是父進(jìn)程通過(guò)系統(tǒng)調(diào)用 fork() 而產(chǎn)生的復(fù)制品,fork() 就是把父進(jìn)程的 PCB 等進(jìn)程的數(shù)據(jù)結(jié)構(gòu)信息直接復(fù)制過(guò)來(lái),只是修改了 PID,所以一模一樣,只有在執(zhí)行 exec() 之后才會(huì)不同,而早先的 fork() 比較消耗資源后來(lái)進(jìn)化成 vfork(),效率高了不少,下面是簡(jiǎn)單的實(shí)現(xiàn)邏輯
pid_t p; p = fork(); if (p == (pid_t) -1) /* ERROR */ else if (p == 0) /* CHILD */ else /* PARENT */
- 既然子進(jìn)程是通過(guò)父進(jìn)程而衍生出來(lái)的,那么子進(jìn)程的退出與資源的回收定然與父進(jìn)程有很大的相關(guān)性。當(dāng)一個(gè)子進(jìn)程要正常的終止運(yùn)行時(shí),或者該進(jìn)程結(jié)束時(shí)它的主函數(shù) main() 會(huì)執(zhí)行 exit(n); 或者 return n,這里的返回值 n 是一個(gè)信號(hào),系統(tǒng)會(huì)把這個(gè) SIGCHLD 信號(hào)傳給其父進(jìn)程,當(dāng)然若是異常終止也往往是因?yàn)檫@個(gè)信號(hào)。
- 在將要結(jié)束時(shí)的子進(jìn)程代碼執(zhí)行部分已經(jīng)結(jié)束執(zhí)行了,系統(tǒng)的資源也基本歸還給系統(tǒng)了,但若是其進(jìn)程的進(jìn)程控制塊(PCB)仍駐留在內(nèi)存中,而它的 PCB 還在,代表這個(gè)進(jìn)程還存在(因?yàn)?PCB 就是進(jìn)程存在的唯一標(biāo)志,里面有 PID 等消息),并沒(méi)有消亡,這樣的進(jìn)程稱之為僵尸進(jìn)程(Zombie)。
-
如下圖中第四列標(biāo)題是 S,S 表示的是進(jìn)程的狀態(tài),而在下屬的第三行的 Z 表示的是 Zombie 的意思
僵尸進(jìn)程.png - 正常情況下,父進(jìn)程會(huì)收到兩個(gè)返回值:exit code(SIGCHLD 信號(hào))與 reason for termination 。之后,父進(jìn)程會(huì)使用 wait(&status) 系統(tǒng)調(diào)用以獲取子進(jìn)程的退出狀態(tài),然后內(nèi)核就可以從內(nèi)存中釋放已結(jié)束的子進(jìn)程的 PCB;而如若父進(jìn)程沒(méi)有這么做的話,子進(jìn)程的 PCB 就會(huì)一直駐留在內(nèi)存中,一直留在系統(tǒng)中成為僵尸進(jìn)程(Zombie)。
- 雖然僵尸進(jìn)程是已經(jīng)放棄了幾乎所有內(nèi)存空間,沒(méi)有任何可執(zhí)行代碼,也不能被調(diào)度,在進(jìn)程列表中保留一個(gè)位置,記載該進(jìn)程的退出狀態(tài)等信息供其父進(jìn)程收集,從而釋放它。但是 Linux 系統(tǒng)中能使用的 PID 是有限的,如果系統(tǒng)中存在有大量的僵尸進(jìn)程,系統(tǒng)將會(huì)因?yàn)闆](méi)有可用的 PID 從而導(dǎo)致不能產(chǎn)生新的進(jìn)程。
- 另外,如果父進(jìn)程結(jié)束(非正常的結(jié)束),未能及時(shí)收回子進(jìn)程,子進(jìn)程仍在運(yùn)行,這樣的子進(jìn)程稱之為孤兒進(jìn)程。在 Linux 系統(tǒng)中,孤兒進(jìn)程一般會(huì)被 init 進(jìn)程所“收養(yǎng)”,成為 init 的子進(jìn)程。由 init 來(lái)做善后處理,所以它并不至于像僵尸進(jìn)程那樣無(wú)人問(wèn)津,不管不顧,大量存在會(huì)有危害。
- 進(jìn)程 0 是系統(tǒng)引導(dǎo)時(shí)創(chuàng)建的一個(gè)特殊進(jìn)程,也稱之為內(nèi)核初始化,其最后一個(gè)動(dòng)作就是調(diào)用 fork() 創(chuàng)建出一個(gè)子進(jìn)程運(yùn)行 /sbin/init 可執(zhí)行文件,而該進(jìn)程就是 PID=1 的進(jìn)程 1,而進(jìn)程 0 就轉(zhuǎn)為交換進(jìn)程(也被稱為空閑進(jìn)程),進(jìn)程 1 (init 進(jìn)程)是第一個(gè)用戶態(tài)的進(jìn)程,再由它不斷調(diào)用 fork() 來(lái)創(chuàng)建系統(tǒng)里其他的進(jìn)程,所以它是所有進(jìn)程的父進(jìn)程或者祖先進(jìn)程。同時(shí)它是一個(gè)守護(hù)程序,直到計(jì)算機(jī)關(guān)機(jī)才會(huì)停止。
-
通過(guò)以下的命令pstree我們可以很明顯的看到這樣的結(jié)構(gòu):
pstree命令.png -
從下圖可以更加形象的看清子父進(jìn)程的關(guān)系:
子父進(jìn)程之間的關(guān)系.png -
通過(guò)上圖的顯示結(jié)果我們可以看的很清楚,init 為所有進(jìn)程的父進(jìn)程或者說(shuō)是祖先進(jìn)程。還可以使用這樣一個(gè)命令(ps -fxo user,ppid,pid,pgid,command)來(lái)看,其中 pid 就是該進(jìn)程的一個(gè)唯一編號(hào),ppid 就是該進(jìn)程的父進(jìn)程的 pid,command 表示的是該進(jìn)程通過(guò)執(zhí)行什么樣的命令或者腳本而產(chǎn)生的。
子父進(jìn)程之間的詳細(xì)信息.png - 可以在上圖中看見(jiàn)我們執(zhí)行的 ps 就是由 zsh 通過(guò) fork-exec 創(chuàng)建的子進(jìn)程而執(zhí)行的。使用這樣的一個(gè)命令我們也能清楚的看見(jiàn) init 如上文所說(shuō)是由進(jìn)程 0 這個(gè)初始化進(jìn)程來(lái)創(chuàng)建出來(lái)的子進(jìn)程,而其他的進(jìn)程基本是由 init 創(chuàng)建的子進(jìn)程,或者是由它的子進(jìn)程創(chuàng)建出來(lái)的子進(jìn)程。所以 init 是用戶進(jìn)程的第一個(gè)進(jìn)程也是所有用戶進(jìn)程的父進(jìn)程或者祖先進(jìn)程。就像一個(gè)樹狀圖,而 init 進(jìn)程就是這棵樹的根,其他進(jìn)程由根不斷的發(fā)散,開枝散葉
- 進(jìn)程有這么多的種類,那么進(jìn)程之間定是有相關(guān)性的,而這些有關(guān)聯(lián)性的進(jìn)程又是如何產(chǎn)生的,如何衍生的?就比如我們啟動(dòng)了終端,就是啟動(dòng)了一個(gè) bash 進(jìn)程,我們可以在 bash 中再輸入 bash 則會(huì)再啟動(dòng)一個(gè) bash 的進(jìn)程,此時(shí)第二個(gè) bash 進(jìn)程就是由第一個(gè) bash 進(jìn)程創(chuàng)建出來(lái)的,他們之間又是個(gè)什么關(guān)系?一般稱呼第一個(gè) bash 進(jìn)程是第二 bash 進(jìn)程的父進(jìn)程,第二 bash 進(jìn)程是第一個(gè) bash 進(jìn)程的子進(jìn)程,這層關(guān)系是如何得來(lái)的呢?關(guān)于父進(jìn)程與子進(jìn)程便會(huì)提及這兩個(gè)系統(tǒng)調(diào)用 fork() 與 exec()
- 2.3 進(jìn)程組與Session
- 每一個(gè)進(jìn)程都會(huì)是一個(gè)進(jìn)程組的成員,而且這個(gè)進(jìn)程組是唯一存在的,他們是依靠 PGID(process group ID)來(lái)區(qū)別的,而每當(dāng)一個(gè)進(jìn)程被創(chuàng)建的時(shí)候,它便會(huì)成為其父進(jìn)程所在組中的一員。一般情況,進(jìn)程組的 PGID 等同于進(jìn)程組的第一個(gè)成員的 PID,并且這樣的進(jìn)程稱為該進(jìn)程組的領(lǐng)導(dǎo)者,也就是領(lǐng)導(dǎo)進(jìn)程,進(jìn)程一般通過(guò)使用getpgrp()系統(tǒng)調(diào)用來(lái)尋找其所在組的 PGID,領(lǐng)導(dǎo)進(jìn)程可以先終結(jié),此時(shí)進(jìn)程組依然存在,并持有相PGID,直到進(jìn)程組中最后一個(gè)進(jìn)程終結(jié)。與進(jìn)程組類似,每當(dāng)一個(gè)進(jìn)程被創(chuàng)建的時(shí)候,它便會(huì)成為其父進(jìn)程所在Session中的一員,每一個(gè)進(jìn)程組都會(huì)在一個(gè)Session中,并且這Session是唯一存在的,
- Session主要是針對(duì)一個(gè)tty建立,Session中的每個(gè)進(jìn)程都稱為一個(gè)工作(job)。每個(gè)會(huì)話可以連接一個(gè)終端(control terminal)。當(dāng)控制終端有輸入輸出時(shí),都傳遞給該會(huì)話的前臺(tái)進(jìn)程組。Session意義在于將多個(gè)jobs囊括在一個(gè)終端,并取其中的一個(gè)job作為前臺(tái),來(lái)直接接收該終端的輸入輸出以及終端信號(hào)。其他jobs在后臺(tái)運(yùn)行。
- 前臺(tái)(foreground):在終端中運(yùn)行,能與你有交互的
- 后臺(tái)(background):在終端中運(yùn)行,但是你并不能與其任何的交互,也不會(huì)顯示其執(zhí)行的過(guò)程
- 2.4 工作管理
- bash(Bourne-Again shell)支持工作控制(job control),而sh(Bourne shell)并不支持。并且每個(gè)終端或者說(shuō) bash 只能管理當(dāng)前終端中的job,不能管理其他終端中的job。比如:當(dāng)前存在兩個(gè)bash分別為 bash1、bash2,bash1 只能管理其自己里面的job,并不能管理bash2里面的job。
-
我們都知道當(dāng)一個(gè)進(jìn)程在前臺(tái)運(yùn)作時(shí)我們可以用Ctrl + c來(lái)終止它,但是若是在后臺(tái)的進(jìn)程就不行了。可以通過(guò)&這個(gè)符號(hào),讓我們的命令在后臺(tái)中運(yùn)行,見(jiàn)下圖所示
前臺(tái)在后臺(tái)運(yùn)行.png - 上圖中所顯示的[1] 236分別是該job的job number與該進(jìn)程的PID,而最后一行的 Done表示該命令已經(jīng)在后臺(tái)執(zhí)行完畢。
-
還可以通過(guò)Ctrl + z使我們的當(dāng)前工作停止并丟到后臺(tái)中去,見(jiàn)下圖
將當(dāng)前工作停止并丟到后臺(tái).png -
被停止并放置在后臺(tái)的工作我們可以使用jobs命令來(lái)查看,見(jiàn)下圖
查看被停止并放置在后臺(tái)的工作.png - 上圖中第一列顯示的為被放置后臺(tái)job的編號(hào),而第二列的+表示最近(剛剛、最后)被放置后臺(tái)的job,同時(shí)也表示預(yù)設(shè)的工作,也就是若是有什么針對(duì)后臺(tái)job的操作,首先對(duì)預(yù)設(shè)的job,- 表示倒數(shù)第二(也就是在預(yù)設(shè)之前的一個(gè)被放置后臺(tái)的工作,倒數(shù)第三個(gè)(再之前的)以后都不會(huì)有這樣的符號(hào)修飾,第三列表示它們的狀態(tài),而最后一列表示該進(jìn)程執(zhí)行的命令。
-
還可以通過(guò)命令fg [%jobnumber]將后臺(tái)的工作拿到前臺(tái)來(lái)。后面不加參數(shù)提取預(yù)設(shè)工作,加參數(shù)提取指定工作的編號(hào),ubuntu在zsh中需要%,在bash中不需要%。
fg命令不帶參數(shù).png
fg命令帶參數(shù).png -
之前我們通過(guò)Ctrl + z使得工作停止放置在后臺(tái),若是我們想讓其在后臺(tái)運(yùn)作就使用命令bg [%jobnumber],與fg類似,加參則指定,不加參則取預(yù)設(shè)。
bg命令.png -
既然有方法將被放置在后臺(tái)的工作提至前臺(tái)或者讓它從停止變成繼續(xù)運(yùn)行在后臺(tái),當(dāng)然也有方法刪除一個(gè)工作,或者重啟等,使用kill指令。
kill命令的使用.png- 格式:kill -signal %jobnumber (signal從1-64個(gè)信號(hào)值可以選擇,可以這樣查看kill -l)
- 參數(shù)說(shuō)明:
- -1:重新讀取參數(shù)運(yùn)行,類似與重啟
- -2:如同Ctrl + C
- -3: 強(qiáng)制終止該任務(wù)
- -4:正常的方式終止任務(wù)
- 注意:若是在使用kill+信號(hào)值然后直接加pid,你將會(huì)對(duì)pid對(duì)應(yīng)的進(jìn)程進(jìn)行操作;若是在使用kill+信號(hào)值然后%jobnumber,這時(shí)所操作的對(duì)象是job,這個(gè)數(shù)字就是就當(dāng)前bash中后臺(tái)的運(yùn)行的job的ID。