1. 程式人生 > >linux系統知識 - 進程&線程

linux系統知識 - 進程&線程

ash 解釋 nohup 即使 get 過程 area 以及 後臺任務

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

參考鏈接

http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.html

http://www.cnblogs.com/vamei/archive/2012/10/09/2715393.html

背景知識

指令:計算機能做的事情其實非常簡單,比如計算兩個數之和、尋找到內存中的某個地址。這些最基礎的計算機動作稱為指令。

程序:一系列指令所構成的集合。通過程序,我們可以讓計算機完成復雜的動作。程序大部分時候被存儲為可執行文件。

進程:進程是程序的一個具體實現,是正在執行的程序。

進程創建

計算機開機時,內核只創建了一個init進程。剩下的所有進程都是init進程通過fork機制(新進程通過老進程復制自身得到)建立的。

進程存活於內存中,每個進程在內存中都有自己的一片空間

fork

fork是一個系統調用。

當進程fork時,linux在內存中開辟出新的一片內存空間給新的進程,並將老的進程空間的內容復制到新的進程空間中,此後兩個進程同時運行。

fork通常作為一個函數被調用。這個函數會有兩次返回,將子進程的PID返回給父進程,0返回給子進程。

通常在調用fork函數之後,程序會設計一個if選擇結構。當PID等於0時,說明該進程為子進程,那麽讓它執行某些指令,比如說使用exec庫函數(library function)讀取另一個程序文件,並在當前的進程空間執行 (這實際上是我們使用fork的一大目的:為某一程序創建進程);而當PID為一個正整數時,說明為父進程,則執行另外一些指令。由此,就可以在子進程建立之後,讓它執行與父進程不同的功能。

進程運行

進程內存使用(內存地址由高到低)

Stack(堆棧)

以幀(stack frame)為單位。

幀中存儲當前激活函數的參數和局部變量,以及該函數的返回地址。

當程序調用函數時,stack向下增長一幀。

當激活函數返回時,會從棧中彈出(pop,讀取並從棧中刪除)該幀,並根據幀中記錄的返回地址,經控制權交給返回地址所指向的指令

Unused Area

Heap(堆)

Global Data

從低到高存放常量、已初始化的全局/靜態變量、未初始化的全局/靜態變量

Text(instruction codes)

存放指令(也就是代碼段)

圖示

技術分享圖片

  技術分享圖片

註意點:

1.最下方的幀和Global Data一起,構成了當前的環境。當前激活的函數可以從環境中調取需要的變量

2.Text和Global data在進程一開始的時候就確定了,並在整個進程中保持固定大小

進程附加信息

除了進程自身內存中的內容,每個進程還要包括一些附加信息,包括PID、PPID、PGID等,用來說明進程身份、進程關系以及其他統計信息。

這些信息保存在內核的內存空間中,內核為每個進程在內核的內存空間中保存一個變量(task_struct結構體)以保存上述信息。

內核可以通過查看自己空間中各個進程的附加信息就能知道進程的情況,而不用進入到進程自身的空間。

每個進程的附加信息中有位置專門用於保存接收到的信號。

進程運行的過程中,通過調用和返回函數,控制權不斷在函數間轉移。

進程在調用函數的時候,原函數的幀保留有離開原函數時的狀態,並為新的函數開辟所需的幀空間。

在調用函數返回時,該函數的幀所占據的空間隨著幀pop而清空。進程再次回到原函數的幀中所保留的狀態,並根據返回地址所指向的指令繼續執行。

上面的過程不斷繼續,棧不斷增長或減小直到main()函數返回,棧完全清空,進程結束。

當程序使用malloc時,堆會向上增長,增長的部分就是malloc從內存中申請的空間。

malloc申請的空間會一直存在,直到使用free系統調用來釋放,或者進程結束。

內存泄漏 - 沒有釋放不再使用的堆空間,導致堆不斷增大,可用內存不斷減少。

棧溢出 Stack overflow

棧和堆的大小會隨著進程的運行增大或者變小,當棧頂和堆相遇時,該進程再無可用內存。則進程棧溢出,進程中止。

如果清理及時,依然棧溢出,則需要增加物理內存。

進程組

每個進程屬於一個進程組,每個進程組可以包含多個進程。

進程組會有一個進程組領導進程(process group id),領導進程的PID成為進程組的ID,即PGID。

領導進程可以先終結,此時進程組依然存在,並持有相同的PGID,知道進程組中最後一個進程終結

進程組的重要作用是可以發信號給進程組。進程組的所有進程都會收到該信號

會話session

多個進程組構成一個會話

會話是由其中的進程建立的,該進程叫做會話的領導進程(session leader),領導進程的PID成為識別會話的SID(session id)

會話中的每一個進程組稱為一個工作(job)。

會話可以有一個進程組成為會話的前臺工作,其他進程組是後臺工作。

每個會話可以連接一個控制終端。

當控制終端有輸入輸出時,或者由終端產生的信號(ctrl+z/ctrl+c),都會傳遞給會話的前臺工作。

一個命令可以末尾加上&讓它後臺運行。

可以通過fg從後臺拿出工作,使其變為前臺工作

bash支持工作控制,sh不支持。

前臺工作

獨占stdin、(獨占命令行窗口,只有運行完了或者手動終止,才能執行其他命令)

可以stdout、stderr

後臺工作

不繼承stdin(無法輸入,如果需要讀取輸入,會halt暫停)

繼承stdout、stderr(後臺任務的輸出會同步在命令行下顯示)

SIGHUP信號

用戶退出session時,系統向該session發送SIGHUP信號

session將SIGHUP信號發送給所有子進程

子進程收到SIGHUP信號後,自動退出

前臺工作肯定會收到SIGHUP信號

後臺工作是否會收到SIGHUP信號,決定於huponexit參數($shopt | grep hupon),這個參數決定session退出時SIGHUP信號是否會發送給後臺工作

disown

disown可以將工作移出後臺工作列表,從而即使huponexit參數打開,在session結束時,系統也不會向不在後臺任務列表中的任務發送SIGHUP信號

標準I/O

標準I/O繼承於session,如果session結束,被移出的後臺任務有需要使用I/O,就會報錯終止執行

因此需要將目標任務的標準I/O進行重定向。

nohup

no hang up - 不掛起

nohup的進程不再接受SIGHUP信號

nohup的進程關閉了stdin,即使在前臺。

nohup的進程將stdout、stderr重定向到nohup.out

screen和tmux

作用:終端復用器,可以在同一個終端裏面,管理多個session

不做深入。

多線程原理

程序運行過程中只有一個控制權存在,稱為單線程

程序運行過程中有多個控制權存在,稱為多線程

單CPU的計算機,可以通過不停在不同線程的指令間切換,造成類似多線程的效果

由於一個棧只有棧頂幀可被讀寫,相應的,只有棧頂幀對應的函數處於運行中(激活狀態)

多進程的程序通過在一個進程內存空間中創建多個棧(每個棧之間以一定空白區域隔開,以備棧的增長),從而繞開棧的限制

多個棧共享進程內存中的text、heap、global data區域。

由於同一個進程空間存在多個棧,任何一個空白區域被填滿,都會導致stack overflow的問題

多線程並發

多線程相當於一個並發系統,並發系統一般同時執行多個任務

如果多個任務對共享資源同時有操作,則會導致並發問題

並發問題解決是將原先的兩個指令構成一個不可分隔的原子操作

多線程同步

同步是指一定的時間內只允許某一個線程訪問某個資源。

可以通過互斥鎖、條件變量和讀寫鎖來實現同步資源

互斥鎖(mutex):把某一段程序代碼加鎖,即表示某一時間段內只允許一個線程去訪問這一段代碼,其他的線程只能等待該線程釋放互斥鎖,才可以訪問該代碼段

條件變量:一般與互斥鎖相結合,解決每個線程需要不斷嘗試獲得互斥鎖並檢查條件是否發生時出現的資源浪費情況。

讀寫鎖:

一個unlock的RW lock可以被某個線程獲取R鎖或者W鎖;

如果被一個線程獲得R鎖,RW lock可以被其它線程繼續獲得R鎖,而不必等待該線程釋放R鎖。但是,如果此時有其它線程想要獲得W鎖,它必須等到所有持有共享讀取鎖的線程釋放掉各自的R鎖。

如果一個鎖被一個線程獲得W鎖,那麽其它線程,無論是想要獲取R鎖還是W鎖,都必須等待該線程釋放W鎖。

進程間通信

IPC( interprocess communication )

方式:文本、信號、管道、傳統IPC

進程通信 - 文本

一個進程將信息寫入到文本中,另一個進程去讀取

由於是位於磁盤,所以效率非常低

進程通信 - 信號

可以以整數的形式傳遞非常少的信息

進程通信 - 管道

可以在兩個進程之間建立通信渠道,分為匿名管道PIPE和命名管道FIFO

進程通信 - 傳統IPC

主要指消息隊列(message queue)、信號量(semaphore)、共享內存(shared memory)。

這些IPC的特點是允許多個進程之間共享資源。但是由於多進程任務具有並發性,所以也需要解決同步的問題。

傳統IPC - 消息隊列

與PIPE相似,也是建立一個隊列,先放入隊列的消息最先被取出。

不同點

消息隊列允許多個進程放入/取出消息。

每個消息可以攜帶一個整數識別符(message_type),可以通過識別符對消息分類

進程從消息隊列中取出消息時,按照先進先出的順序取出,也可以只取出某種類型的消息(也是先進先出的順序)。

消息隊列不使用文件API(即調用文件+參數)

消息隊列不會自動消失,他會一直存在於內核中,直到某個進程刪除該隊列

傳統IPC - semaphore

與mutex類似,是一個進程計數鎖,允許被N個進程取到,有更多的進程申請時,會等待

一個semaphore會一直在內核中,直到某個進程刪除它

傳統IPC - 共享內存

一個進程可以將自己內存空間中的一部分拿出來,允許其他進程讀寫。

在使用共享內存的時候,同樣要註意同步的問題。

我們可以使用semaphore同步,也可以在共享內存中建立mutex或者其他的線程同步變量來同步

進程終結

當子進程終結時候,它會通知父進程,並清空自己所占據的內存,並在內核中留下自己的退出信息(exit code,0 - 正常退出,>0 - 異常退出),在這個信息裏,會解釋該進程為什麽退出。

父進程得知子進程終結時,對該子進程使用wait系統調用,wait系統調用會取出子進程的退出信息,並清空該信息在內核中所占據的空間。

但是,如果父進程早於子進程終結時,子進程就成了一個孤兒(orphand)進程。孤兒進程會被過繼給init進程。init進程會在該子進程終結時調用wait系統調用。

一個糟糕的程序也可能造成子進程的退出信息滯留在內核中的情況(父進程不對子進程調用wait函數,內核中滯留task_struct結構體),這樣的情況下,子進程成為僵屍進程。當大量僵屍(Zombie)進程積累時,內存空間會被擠占。

linux系統知識 - 進程&線程