關于進程和線程,大家總是說的1句話是“進程是操作系統分配資源的最小單元,線程是操作系統調度的最小單元”。這句話理論上沒問題,我們來看看甚么是所謂的“資源”呢。
甚么是計算機資源
經典的馮諾依曼結構把計算機系統抽象成 CPU + 存儲器 + IO,那末計算機資源不過就兩種:
1. 計算資源
2. 存儲資源
CPU是計算單元,單純從CPU的角度來講它是1個黑盒,它只對輸入的指令和數據進行計算,然后輸出結果,它不負責管理計算哪些”指令和數據“。 換句話說CPU只提供了計算能力,但是不負責分配計算資源。
計算資源是操作系統來分配的,也就是常說的操作系統的調度模塊,由操作系統依照1定的規則來分配甚么時候由誰來取得CPU的計算資源,比如分時間片
存儲資源就是內存,磁盤這些存儲裝備的資源。在這篇計算機底層知識拾遺(1)理解虛擬內存機制 我們說了操作系統使用了虛擬內存機制來管理存儲器,從緩存原理的角度來講,把內存作為磁盤的緩存。進程是面向磁盤的,為何這么說呢,進程表示1個運行的程序,程序的代碼段,數據段這些都是寄存在磁盤中的,在運行時加載到內存中。所以虛擬內存面向的是磁盤,虛擬頁是對磁盤文件的分配,然后被緩存到物理內存的物理頁中。
所以存儲資源是操作系統由虛擬內存機制來管理和分配的。進程應當是操作系統分配存儲資源的最小單元。
再來看看線程,理論上說Linux內核是沒有線程這個概念的,只有內核調度實體(Kernal Scheduling Entry, KSE)這個概念。Linux的線程本質上是1種輕量級的進程,是通過clone系統調用來創建的。何謂“輕量級”會在后面細說。進程是1種KSE,線程也是1種KSE。所以“線程是操作系統調度的最小單元”這句話沒問題。
甚么是進程
進程是對計算機的1種抽象,
1. 進程表示1個邏輯控制流,就是1種計算進程,它造成1個假象,好像這個進程1直在獨占CPU資源
2. 進程具有1個獨立的虛擬內存地址空間,它造成1個假象,好像這個進程1致在獨占存儲器資源
這張圖是進程的虛擬內存地址空間的分配模型圖,可以看到進程的虛擬內存地址空間分為用戶空間和內核空間。用戶空間從低端地址往高端地址發展,內核空間從高端地址往低端地址發展。用戶空間寄存著這個進程的代碼段和數據段,和運行時的堆和用戶棧。堆是從低端地址往高端地址發展,棧是從高端地址往低端地址發展。
內核空間寄存著內核的代碼和數據,和內核為這個進程創建的相干數據結構,比如頁表數據結構,task數據結構,area區域數據結構等等。
從文件IO的角度來講,Linux把1切IO都抽象成了文件,比如普通文件IO,網絡IO,統統都是文件,利用open系統調用返回1個整數作為文件描寫符file descriptor,進程可以利用file descriptor作為參數在任何系統調用中表示那個打開的文件。內核為進程保護了1個文件描寫符表來保持進程所有取得的file descriptor。
每調用1次open系統調用內核會創建1個打開文件open file的數據結構來表示這個打開的文件,記錄了該文件目前讀取的位置等信息。打開文件又唯1了1個指針指向文件系統中該文件的inode結構。inode記錄了該文件的文件名,路徑,訪問權限等元數據。
操作操作系統用了3個數據結構來為每一個進程管理它打開的文件資源
fork系統調用
操作系統利用fork系統調用來創建1個子進程。fork所創建的子進程會復制父進程的虛擬地址空間。
要理解“復制”和“同享”的區分,復制的意思是會真正在物理內存復制1分內容,會真正消耗新的物理內存。同享的意思是使用指針指向同1個地址,不會真實的消耗物理內存。理解這兩個概念的區分很重要,這是進程和線程的根本區分之1。
那末有人問了如果我父進程占了1G的物理內存,那末fork會再使用1G的物理內存來復制嗎,相當于1下用了2G的物理內存?
答案是初期的操作系統的確是這么干的,但是這樣性能也太差了,所以現代操作系統使用了 寫時復制Copy on write的方式來優化fork的性能,fork剛創建的子進程采取了同享的方式,只用指針指向了父進程的物理資源。當子進程真正要對某些物理資源寫操作時,才會真實的復制1塊物理資源來供子進程使用。這樣就極大的優化了fork的性能,并且從邏輯來講子進程的確是具有了獨立的虛擬內存空間。
fork不只是復制了頁表結構,還復制了父進程的文件描寫符表,信號控制表,進程信息,寄存器資源等等。它是1個較為深入的復制。
從邏輯控制流的角度來講,fork創建的子進程開始履行的位置是fork函數返回的位置。這點和線程是不1樣的,我們知道Java中的Thread需要寫run方法,線程開始后會從run方法開始履行。
既然我們知道了內核為進程保護了這么多資源,那末當內存進行進程調度時進行的進程上下文切換就容易理解了,1個進程運行要依賴這么些資源,那末進程上下文切換就要把這些資源都保存起來寫回到內存中,等下次這個進程被調度時再把這些資源再加載到寄存器和高速緩存硬件。
進程上下文切換保存的內容有:
頁表 -- 對應虛擬內存資源
文件描寫符表/打開文件表 -- 對應打開的文件資源
寄存器 -- 對應運行時數據
信號控制信息/進程運行信息
進程間通訊
虛擬內存機制為進程管理存儲資源帶來了種種好處,但是它也給進程帶來了1些小麻煩,我們知道每一個進程具有獨立的虛擬內存地址空間,看到1樣的虛擬內地址空間視圖,所以對不同的進程來講,1個相同的虛擬地址意味著不同的物理地址。我們知道CPU履行指令時采取了虛擬地址,對應1個特定的變量來講,它對應著1個特定的虛擬地址。這樣帶來的問題就是兩個進程不能通過簡單的同享變量的方式來進行進程間通訊,也就是說進程不能通過直接同享內存的方式來進行進程間通訊,只能采取信號,管道等方式來進行進程間通訊。這樣的效力肯定比直接同享內存的方式差
甚么是線程
上面說了1堆內核為進程分配了哪些資源,我們知道進程管理了1堆資源,并且每一個進程還具有獨立的虛擬內存地址空間,會真正地具有獨立與父進程以外的物理內存。并且由于進程具有獨立的內存地址空間,致使了進程之間沒法利用直接的內存映照進行進程間通訊。
并發的本質是在時間上堆疊的多個邏輯流,也就是說同時運行的多個邏輯流。并發編程要解決的1個很重要的問題就是對資源的并發訪問的問題,也就是同享資源的問題。而兩個進程恰恰很難在邏輯上表示同享資源。
線程解決的最大問題就是它可以很簡單地表示同享資源的問題,這里說的資源指的是存儲器資源,資源最后都會加載到物理內存,1個進程的所有線程都是同享這個進程的同1個虛擬地址空間的,也就是說從線程的角度來講,它們看到的物理資源都是1樣的,這樣就能夠通過同享變量的方式來表示同享資源,也就是直接同享內存的方式解決了線程通訊的問題。而線程也表示1個獨立的邏輯流,這樣就完善解決了進程的1個大困難。
從存儲資源的角度理解了線程以后,就不難理解計算資源的分配了。從計算資源的角度來講,對內核而言,進程和線程沒有甚么區分,所之內核用內核調度實體(KSE)來表示1個調度的單元。
clone系統調用
在Linux系統中,線程是使用clone系統調用,clone是1個輕量級的fork,它提供了1系列的參數來表示線程可以同享父類的哪些資源,比如頁表,打開文件表等等。我們上面說過了同享和復制的區分,同享只是簡單地用指針指向同1個物理地址,不會在父進程以外開辟新的物理內存。
clone系統調用可以指定創建的線程開始履行代碼位置,也就是Java中的Thread類的run方法。
Linux內核只提供了clone這個系統調用來創建類似線程的輕量級進程的概念。C語言利用了Pthreads庫來真正創建了線程這個數據結構。Linux采取了1:1的模型,即C語言的Pthreads庫創建的線程實體1:1對應著內核創建的1個KSE。Pthreads運行在用戶空間,KSE運行在內核空間。
既然線程同享了進程的資源,那末線程的上下文切換就好理解了。對操作系統來講,它看到要被調度進來的線程和剛運行的線程是同1個進程的,那末線程的上下文切換只需要保存線程的1些運行時的數據,比如
線程的id
寄存器中的值
棧數據
而不需要像進程上下文切換那樣要保存頁表,文件描寫符表,信號控制數據和進程信息等數據。頁表是1個很重的資源,我們之前說過,如果采取1級頁表的結構,那末32位機器的頁表要到達4MB的物理空間。 所以線程上下文切換是很輕量級的。
進程采取父子結構,init進程是最頂真個父進程,其他進程都是從init進程派生出來的。這樣就很容易理解進程是如何同享內核的代碼和數據的了。
而線程采取對等結構,即線程沒有父子的概念,所有線程都屬于同1個線程組,線程組的組號等于第1個線程的線程號。
我們來看看Java的線程究竟是如何實現的。Java語言層面提供了java.lang.Thread這個類來表示Java語言層面的線程,并提供了run方法表示線程運行的邏輯控制流。
我們知道JVM是C++/C寫的,JVM本身利用了Pthreads庫來創建操作系統的線程。JVM還要支持Java語言創建的線程的概念。
聊聊JVM(5)從JVM角度理解線程 這篇已說了從JVM的角度如何理解線程。 JVM提供了JavaThread類來對應Java語言的Thread,即Java語言中創建1個java.lang.Thread對象,JVM會相應的在JVM中創建1個JavaThread對象。同時JVM還創建了1個OSThread類來對利用Pthreads創建的底層操作系統的線程對象。
構建并發程序可以基于進程也能夠線程,
比如Nginx就是基于進程構建并發程序的。而Java天生只支持基于線程的方式來構建并發程序。
最后再總結1下 進程VS 線程
參考資料
《深入理解計算機系統》
《Linux系統編程手冊》
下一篇 linux系統中misc子系統