Linux中的進(jìn)程之進(jìn)程基本概念

技術(shù)交流QQ群:1027579432,歡迎你的加入!

一、概念的理解

  • 首先程序與進(jìn)程是什么?程序與進(jìn)程又有什么區(qū)別?
    • 程序(procedure):不太精確地說,程序就是執(zhí)行一系列有邏輯、有順序結(jié)構(gòu)的指令,幫我們達(dá)成某個結(jié)果。就如我們?nèi)ゲ宛^,給服務(wù)員說我要牛肉蓋澆飯,她執(zhí)行了做牛肉蓋澆飯這么一個程序,最后我們得到了這么一盤牛肉蓋澆飯。它需要去執(zhí)行,不然它就像一本武功秘籍,放在那里等人翻看。
    • 進(jìn)程(process):進(jìn)程是程序在一個數(shù)據(jù)集合上的一次執(zhí)行過程,在早期的UNIX、Linux 2.4及更早的版本中,它是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨立基本單位。同上一個例子,就如我們?nèi)チ瞬宛^,給服務(wù)員說我要牛肉蓋澆飯,她執(zhí)行了做牛肉蓋澆飯這么一個程序,而里面做飯的是一個進(jìn)程,做牛肉湯汁的是一個進(jìn)程,把牛肉湯汁與飯混合在一起的是一個進(jìn)程,把飯端上桌的是一個進(jìn)程。它就像是我們在看武功秘籍這么一個過程,然后一個篇章一個篇章地去練。
  • 簡單來說,程序是為了完成某種任務(wù)而設(shè)計的軟件,比如 vim 是程序。什么是進(jìn)程呢?進(jìn)程就是運行中的程序。
  • 程序只是一些列指令的集合,是一個靜止的實體,而進(jìn)程不同,進(jìn)程有以下的特性:
    • 動態(tài)性:進(jìn)程的實質(zhì)是一次程序執(zhí)行的過程,有創(chuàng)建、撤銷等狀態(tài)的變化。而程序是一個靜態(tài)的實體。
    • 并發(fā)性:進(jìn)程可以做到在一個時間段內(nèi),有多個程序在運行中。程序只是靜態(tài)的實體,所以不存在并發(fā)性。
    • 獨立性:進(jìn)程可以獨立分配資源,獨立接受調(diào)度,獨立地運行。
    • 異步性:進(jìn)程以不可預(yù)知的速度向前推進(jìn)。
    • 結(jié)構(gòu)性:進(jìn)程擁有代碼段、數(shù)據(jù)段、PCB(進(jìn)程控制塊,進(jìn)程存在的唯一標(biāo)志)。也正是因為有結(jié)構(gòu)性,進(jìn)程才可以做到獨立地運行。
  • 并發(fā):在一個時間段內(nèi),宏觀來看有多個程序都在活動,有條不紊的執(zhí)行(每一瞬間只有一個在執(zhí)行,只是在一段時間有多個程序都執(zhí)行過)
  • 并行:在每一個瞬間,都有多個程序都在同時執(zhí)行,這個必須有多個 CPU 才行
  • 引入進(jìn)程是因為傳統(tǒng)意義上的程序已經(jīng)不足以描述 OS 中各種活動之間的動態(tài)性、并發(fā)性、獨立性還有相互制約性。程序就像一個公司,只是一些證書,文件的堆積(靜態(tài)實體)。而當(dāng)公司運作起來就有各個部門的區(qū)分,財務(wù)部,技術(shù)部,銷售部等等,就像各個進(jìn)程,各個部門之間可以獨立運做,也可以有交互(獨立性、并發(fā)性)。而隨著程序的發(fā)展越做越大,又會繼續(xù)細(xì)分,從而引入了線程的概念,當(dāng)代多數(shù)操作系統(tǒng)、Linux 2.6及更新的版本中,進(jìn)程本身不是基本運行單位,而是線程的容器。就像上述所說的,每個部門又會細(xì)分為各個工作小組(線程),而工作小組需要的資源需要向上級(進(jìn)程)申請。
  • 線程(thread)是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實際運作單位。一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。因為線程中幾乎不包含系統(tǒng)資源,所以執(zhí)行更快、更有效率。簡而言之,一個程序至少有一個進(jìn)程,一個進(jìn)程至少有一個線程。線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。另外,進(jìn)程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率。就如下圖所示:


    概念形象比喻.png

二、進(jìn)程的屬性

  • 2.1進(jìn)程的分類
    • 大概明白進(jìn)程是個什么樣的存在后,我們需要進(jìn)一步了解的就是進(jìn)程分類。可以從兩個角度來分:
      • 以進(jìn)程的功能與服務(wù)的對象來分;
      • 以應(yīng)用程序的服務(wù)類型來分;
    • 第一個角度來看,我們可以分為用戶進(jìn)程與系統(tǒng)進(jìn)程:
      • 用戶進(jìn)程:通過執(zhí)行用戶程序、應(yīng)用程序或稱之為內(nèi)核之外的系統(tǒng)程序而產(chǎn)生的進(jìn)程,此類進(jìn)程可以在用戶的控制下運行或關(guān)閉。
      • 系統(tǒng)進(jìn)程:通過執(zhí)行系統(tǒng)內(nèi)核程序而產(chǎn)生的進(jìn)程,比如可以執(zhí)行內(nèi)存資源分配和進(jìn)程切換等相對底層的工作;而且該進(jìn)程的運行不受用戶的干預(yù),即使是 root 用戶也不能干預(yù)系統(tǒng)進(jìn)程的運行。
    • 第二角度來看,我們可以將進(jìn)程分為交互進(jìn)程、批處理進(jìn)程、守護(hù)進(jìn)程
      • 交互進(jìn)程:由一個 shell 終端啟動的進(jìn)程,在執(zhí)行過程中,需要與用戶進(jìn)行交互操作,可以運行于前臺,也可以運行在后臺。
      • 批處理進(jìn)程:該進(jìn)程是一個進(jìn)程集合,負(fù)責(zé)按順序啟動其他的進(jìn)程。
      • 守護(hù)進(jìn)程:守護(hù)進(jìn)程是一直運行的一種進(jìn)程,在 Linux 系統(tǒng)啟動時啟動,在系統(tǒng)關(guān)閉時終止。它們獨立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。例如 httpd 進(jìn)程,一直處于運行狀態(tài),等待用戶的訪問。還有經(jīng)常用的 cron(在 centOS 系列為 crond)進(jìn)程,這個進(jìn)程為 crontab 的守護(hù)進(jìn)程,可以周期性的執(zhí)行用戶設(shè)定的某些任務(wù)。
  • 2.2 進(jìn)程的衍生
    • 進(jìn)程有這么多的種類,那么進(jìn)程之間定是有相關(guān)性的,而這些有關(guān)聯(lián)性的進(jìn)程又是如何產(chǎn)生的,如何衍生的?就比如我們啟動了終端,就是啟動了一個 bash 進(jìn)程,我們可以在 bash 中再輸入 bash 則會再啟動一個 bash 的進(jìn)程,此時第二個 bash 進(jìn)程就是由第一個 bash 進(jìn)程創(chuàng)建出來的,他們之間又是個什么關(guān)系?一般稱呼第一個 bash 進(jìn)程是第二 bash 進(jìn)程的父進(jìn)程,第二 bash 進(jìn)程是第一個 bash 進(jìn)程的子進(jìn)程,這層關(guān)系是如何得來的呢?關(guān)于父進(jìn)程與子進(jìn)程便會提及這兩個系統(tǒng)調(diào)用 fork() 與 exec()
      • fork-exec是由 Dennis M. Ritchie 創(chuàng)造的
      • fork() 是一個系統(tǒng)調(diào)用(system call),它的主要作用就是為當(dāng)前的進(jìn)程創(chuàng)建一個新的進(jìn)程,這個新的進(jìn)程就是它的子進(jìn)程,這個子進(jìn)程除了父進(jìn)程的返回值和 PID 以外其他的都一模一樣,如進(jìn)程的執(zhí)行代碼段,內(nèi)存信息,文件描述,寄存器狀態(tài)等等
      • exec() 也是系統(tǒng)調(diào)用,作用是切換子進(jìn)程中的執(zhí)行程序也就是替換其從父進(jìn)程復(fù)制過來的代碼段與數(shù)據(jù)段
    • 子進(jìn)程就是父進(jìn)程通過系統(tǒng)調(diào)用 fork() 而產(chǎn)生的復(fù)制品,fork() 就是把父進(jìn)程的 PCB 等進(jìn)程的數(shù)據(jù)結(jié)構(gòu)信息直接復(fù)制過來,只是修改了 PID,所以一模一樣,只有在執(zhí)行 exec() 之后才會不同,而早先的 fork() 比較消耗資源后來進(jìn)化成 vfork(),效率高了不少,下面是簡單的實現(xiàn)邏輯
      pid_t p;
      
      p = fork();
      if (p == (pid_t) -1)
              /* ERROR */
      else if (p == 0)
              /* CHILD */
      else
              /* PARENT */
      
    • 既然子進(jìn)程是通過父進(jìn)程而衍生出來的,那么子進(jìn)程的退出與資源的回收定然與父進(jìn)程有很大的相關(guān)性。當(dāng)一個子進(jìn)程要正常的終止運行時,或者該進(jìn)程結(jié)束時它的主函數(shù) main() 會執(zhí)行 exit(n); 或者 return n,這里的返回值 n 是一個信號,系統(tǒng)會把這個 SIGCHLD 信號傳給其父進(jìn)程,當(dāng)然若是異常終止也往往是因為這個信號。
    • 在將要結(jié)束時的子進(jìn)程代碼執(zhí)行部分已經(jīng)結(jié)束執(zhí)行了,系統(tǒng)的資源也基本歸還給系統(tǒng)了,但若是其進(jìn)程的進(jìn)程控制塊(PCB)仍駐留在內(nèi)存中,而它的 PCB 還在,代表這個進(jìn)程還存在(因為 PCB 就是進(jìn)程存在的唯一標(biāo)志,里面有 PID 等消息),并沒有消亡,這樣的進(jìn)程稱之為僵尸進(jìn)程(Zombie)。
    • 如下圖中第四列標(biāo)題是 S,S 表示的是進(jìn)程的狀態(tài),而在下屬的第三行的 Z 表示的是 Zombie 的意思


      僵尸進(jìn)程.png
    • 正常情況下,父進(jìn)程會收到兩個返回值:exit code(SIGCHLD 信號)與 reason for termination 。之后,父進(jìn)程會使用 wait(&status) 系統(tǒng)調(diào)用以獲取子進(jìn)程的退出狀態(tài),然后內(nèi)核就可以從內(nèi)存中釋放已結(jié)束的子進(jìn)程的 PCB;而如若父進(jìn)程沒有這么做的話,子進(jìn)程的 PCB 就會一直駐留在內(nèi)存中,一直留在系統(tǒng)中成為僵尸進(jìn)程(Zombie)。
    • 雖然僵尸進(jìn)程是已經(jīng)放棄了幾乎所有內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能被調(diào)度,在進(jìn)程列表中保留一個位置,記載該進(jìn)程的退出狀態(tài)等信息供其父進(jìn)程收集,從而釋放它。但是 Linux 系統(tǒng)中能使用的 PID 是有限的,如果系統(tǒng)中存在有大量的僵尸進(jìn)程,系統(tǒng)將會因為沒有可用的 PID 從而導(dǎo)致不能產(chǎn)生新的進(jìn)程。
    • 另外,如果父進(jìn)程結(jié)束(非正常的結(jié)束),未能及時收回子進(jìn)程,子進(jìn)程仍在運行,這樣的子進(jìn)程稱之為孤兒進(jìn)程。在 Linux 系統(tǒng)中,孤兒進(jìn)程一般會被 init 進(jìn)程所“收養(yǎng)”,成為 init 的子進(jìn)程。由 init 來做善后處理,所以它并不至于像僵尸進(jìn)程那樣無人問津,不管不顧,大量存在會有危害。
    • 進(jìn)程 0 是系統(tǒng)引導(dǎo)時創(chuàng)建的一個特殊進(jìn)程,也稱之為內(nèi)核初始化,其最后一個動作就是調(diào)用 fork() 創(chuàng)建出一個子進(jì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)程)是第一個用戶態(tài)的進(jìn)程,再由它不斷調(diào)用 fork() 來創(chuàng)建系統(tǒng)里其他的進(jìn)程,所以它是所有進(jìn)程的父進(jìn)程或者祖先進(jìn)程。同時它是一個守護(hù)程序,直到計算機關(guān)機才會停止。
    • 通過以下的命令pstree我們可以很明顯的看到這樣的結(jié)構(gòu):


      pstree命令.png
    • 從下圖可以更加形象的看清子父進(jìn)程的關(guān)系:


      子父進(jìn)程之間的關(guān)系.png
    • 通過上圖的顯示結(jié)果我們可以看的很清楚,init 為所有進(jìn)程的父進(jìn)程或者說是祖先進(jìn)程。還可以使用這樣一個命令(ps -fxo user,ppid,pid,pgid,command)來看,其中 pid 就是該進(jìn)程的一個唯一編號,ppid 就是該進(jìn)程的父進(jìn)程的 pid,command 表示的是該進(jìn)程通過執(zhí)行什么樣的命令或者腳本而產(chǎn)生的。


      子父進(jìn)程之間的詳細(xì)信息.png
    • 可以在上圖中看見我們執(zhí)行的 ps 就是由 zsh 通過 fork-exec 創(chuàng)建的子進(jìn)程而執(zhí)行的。使用這樣的一個命令我們也能清楚的看見 init 如上文所說是由進(jìn)程 0 這個初始化進(jìn)程來創(chuàng)建出來的子進(jìn)程,而其他的進(jìn)程基本是由 init 創(chuàng)建的子進(jìn)程,或者是由它的子進(jìn)程創(chuàng)建出來的子進(jìn)程。所以 init 是用戶進(jìn)程的第一個進(jìn)程也是所有用戶進(jìn)程的父進(jìn)程或者祖先進(jìn)程。就像一個樹狀圖,而 init 進(jìn)程就是這棵樹的根,其他進(jìn)程由根不斷的發(fā)散,開枝散葉
  • 2.3 進(jìn)程組與Session
    • 每一個進(jìn)程都會是一個進(jìn)程組的成員,而且這個進(jìn)程組是唯一存在的,他們是依靠 PGID(process group ID)來區(qū)別的,而每當(dāng)一個進(jìn)程被創(chuàng)建的時候,它便會成為其父進(jìn)程所在組中的一員。一般情況,進(jìn)程組的 PGID 等同于進(jìn)程組的第一個成員的 PID,并且這樣的進(jìn)程稱為該進(jìn)程組的領(lǐng)導(dǎo)者,也就是領(lǐng)導(dǎo)進(jìn)程,進(jìn)程一般通過使用getpgrp()系統(tǒng)調(diào)用來尋找其所在組的 PGID,領(lǐng)導(dǎo)進(jìn)程可以先終結(jié),此時進(jìn)程組依然存在,并持有相PGID,直到進(jìn)程組中最后一個進(jìn)程終結(jié)。與進(jìn)程組類似,每當(dāng)一個進(jìn)程被創(chuàng)建的時候,它便會成為其父進(jìn)程所在Session中的一員,每一個進(jìn)程組都會在一個Session中,并且這Session是唯一存在的,
    • Session主要是針對一個tty建立,Session中的每個進(jìn)程都稱為一個工作(job)。每個會話可以連接一個終端(control terminal)。當(dāng)控制終端有輸入輸出時,都傳遞給該會話的前臺進(jìn)程組。Session意義在于將多個jobs囊括在一個終端,并取其中的一個job作為前臺,來直接接收該終端的輸入輸出以及終端信號。其他jobs在后臺運行。
      • 前臺(foreground):在終端中運行,能與你有交互的
      • 后臺(background):在終端中運行,但是你并不能與其任何的交互,也不會顯示其執(zhí)行的過程
  • 2.4 工作管理
    • bash(Bourne-Again shell)支持工作控制(job control),而sh(Bourne shell)并不支持。并且每個終端或者說 bash 只能管理當(dāng)前終端中的job,不能管理其他終端中的job。比如:當(dāng)前存在兩個bash分別為 bash1、bash2,bash1 只能管理其自己里面的job,并不能管理bash2里面的job。
    • 我們都知道當(dāng)一個進(jìn)程在前臺運作時我們可以用Ctrl + c來終止它,但是若是在后臺的進(jìn)程就不行了。可以通過&這個符號,讓我們的命令在后臺中運行,見下圖所示


      前臺在后臺運行.png
    • 上圖中所顯示的[1] 236分別是該job的job number與該進(jìn)程的PID,而最后一行的 Done表示該命令已經(jīng)在后臺執(zhí)行完畢。
    • 還可以通過Ctrl + z使我們的當(dāng)前工作停止并丟到后臺中去,見下圖


      將當(dāng)前工作停止并丟到后臺.png
    • 被停止并放置在后臺的工作我們可以使用jobs命令來查看,見下圖


      查看被停止并放置在后臺的工作.png
    • 上圖中第一列顯示的為被放置后臺job的編號,而第二列的+表示最近(剛剛、最后)被放置后臺的job,同時也表示預(yù)設(shè)的工作,也就是若是有什么針對后臺job的操作,首先對預(yù)設(shè)的job,- 表示倒數(shù)第二(也就是在預(yù)設(shè)之前的一個被放置后臺的工作,倒數(shù)第三個(再之前的)以后都不會有這樣的符號修飾,第三列表示它們的狀態(tài),而最后一列表示該進(jìn)程執(zhí)行的命令。
    • 還可以通過命令fg [%jobnumber]將后臺的工作拿到前臺來。后面不加參數(shù)提取預(yù)設(shè)工作,加參數(shù)提取指定工作的編號,ubuntu在zsh中需要%,在bash中不需要%。


      fg命令不帶參數(shù).png

      fg命令帶參數(shù).png
    • 之前我們通過Ctrl + z使得工作停止放置在后臺,若是我們想讓其在后臺運作就使用命令bg [%jobnumber],與fg類似,加參則指定,不加參則取預(yù)設(shè)。


      bg命令.png
    • 既然有方法將被放置在后臺的工作提至前臺或者讓它從停止變成繼續(xù)運行在后臺,當(dāng)然也有方法刪除一個工作,或者重啟等,使用kill指令。


      kill命令的使用.png
      • 格式:kill -signal %jobnumber (signal從1-64個信號值可以選擇,可以這樣查看kill -l)
      • 參數(shù)說明:
        • -1:重新讀取參數(shù)運行,類似與重啟
        • -2:如同Ctrl + C
        • -3: 強制終止該任務(wù)
        • -4:正常的方式終止任務(wù)
      • 注意:若是在使用kill+信號值然后直接加pid,你將會對pid對應(yīng)的進(jìn)程進(jìn)行操作;若是在使用kill+信號值然后%jobnumber,這時所操作的對象是job,這個數(shù)字就是就當(dāng)前bash中后臺的運行的job的ID。
最后編輯于
?著作權(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)容

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