1.Linux線程概念
1.1什么是線程
? 在?個程序?的?個執?路線就叫做線程(thread)。更準確的定義是:線程是“?個進程內部 的控制序列”
? ?切進程?少都有?個執?線程
? 線程在進程內部運?,本質是在進程地址空間內運?
? 在Linux系統中,在CPU眼中,看到的PCB都要?傳統的進程更加輕量化
? 透過進程虛擬地址空間,可以看到進程的?部分資源,將進程資源合理分配給每個執?流,就形 成了線程執?流
1.2分頁式存儲管理
1.2.1虛擬地址和頁表
思考?下,如果在沒有虛擬內存和分?機制的情況下,每?個??程序在物理內存上所對應的空間必 須是連續的,如下圖:
為每?個程序的代碼、數據?度都是不?樣的,按照這樣的映射?式,物理內存將會被分割成各種 離散的、??不同的塊。經過?段運?時間之后,有些程序會退出,那么它們占據的物理內存空間可以被回收,導致這些物理內存都是以很多碎?的形式存在。
怎么辦呢?我們希望操作系統提供給??的空間必須是連續的,但是物理內存最好不要連續。此時虛 擬內存和分?便出現了,如下圖所?:
把物理內存按照?個固定的?度的?框進?分割,有時叫做物理?。每個?框包含?個物理? (page)。?個?的??等于?框的??。?多數 32位 體系結構?持 4KB 的?,? 64位 體系結 構?般會?持 8KB 的?。區分??和?個?框是很重要的:
? ?框是?個存儲區域;
? ??是?個數據塊,可以存放在任何?框或磁盤中。
有了這種機制,CPU便并?是直接訪問物理內存地址,?是通過虛擬地址空間來間接的訪問物理內存 地址。所謂的虛擬地址空間,是操作系統為每?個正在執?的進程分配的?個邏輯地址,在32位機 上,其范圍從0~4G。
操作系統通過將虛擬地址空間和物理內存地址之間建?映射關系,也就是?表,這張表上記錄了每? 對?和?框的映射關系,能讓CPU間接的訪問物理內存地址。
總結?下,其思想是將虛擬內存下的邏輯地址空間分為若??,將物理內存空間分為若??框,通過 ?表便能把連續的虛擬內存,映射到若?個不連續的物理內存?。這樣就解決了使?連續的物理內存 造成的碎?問題。
1.2.2頁表
?表中的每?個表項,指向?個物理?的開始地址。在 32 位系統中,虛擬內存的最?空間是 4GB , 這是每?個??程序都擁有的虛擬內存空間。既然需要讓 4GB 的虛擬內存全部可?,那么?表中就需 要能夠表?這所有的 4GB 空間,那么就?共需要 4GB/4KB = 1048576 個表項。如下圖所?:
虛擬內存看上去被虛線“分割”成?個個單元,其實并不是真的分割,虛擬內存仍然是連續的。這個 虛線的單元僅僅表?它與?表中每?個表項的映射關系,并最終映射到相同??的?個物理內存? 上。
?表中的物理地址,與物理內存之間,是隨機的映射關系,哪?可?就指向哪?(物理?)。雖然最終使 ?的物理內存是離散的,但是與虛擬內存對應的線性地址是連續的。處理器在訪問數據、獲取指令 時,使?的都是線性地址,只要它是連續的就可以了,最終都能夠通過?表找到實際的物理地址。
解決需要?容量?表的最好?法是:把?表看成普通的?件,對它進?離散分配,即對?表再分?, 由此形成多級?表的思想。
為了解決這個問題,可以把這個單??表拆分成 1024 個體積更?的映射表。如下圖所?。這樣? 來,1024(每個表中的表項個數)*1024(表的個數),仍然可以覆蓋 4GB 的物理內存空間。
1.2.3頁目錄結構
到?前為?,每?個?框都被?個?表中的?個表項來指向了,那么這 1024 個?表也需要被管理起 來。管理?表的表稱之為??錄表,形成?級?表。如下圖所?:
? 所有?表的物理地址被??錄表項指向
? ??錄的物理地址被 CR3 寄存器 指向,這個寄存器中,保存了當前正在執?任務的??錄地 址。
所以操作系統在加載??程序的時候,不僅僅需要為程序內容來分配物理內存,還需要為?來保存程 序的??錄和?表分配物理內存。
1.2.4兩級頁表的地址轉換
下?以?個邏輯地址為例。將邏輯地址( 0000000000,0000000001,11111111111 )轉換為物 理地址的過程:
1. 在32位處理器中,采?4KB的???,則虛擬地址中低12位為?偏移,剩下?20位給?表,分成 兩級,每個級別占10個bit(10+10)。
2. CR3 寄存器 讀取??錄起始地址,再根據?級?號查??錄表,找到下?級?表在物理內存中 存放位置。
3. 根據?級?號查表,找到最終想要訪問的內存塊號。
4. 結合?內偏移量得到物理地址。
5. 注:?個物理?的地址?定是 4KB 對?的(最后的 12 位全部為 0 ),所以其實只需要記錄物理 ?地址的?20位即可。
6. 以上其實就是MMU的?作流程。MMU(Memory Manage Unit)是?種硬件電路,其速度很快, 主要?作是進?內存管理,地址轉換只是它承接的業務之?。
到這?其實還有個問題,MMU要先進?兩次?表查詢確定物理地址,在確認了權限等問題后,MMU再 將這個物理地址發送到總線,內存收到之后開始讀取對應地址的數據并返回。那么當?表變為N級時, 就變成了N次檢索+1次讀寫。可?,?表級數越多查詢的步驟越多,對于CPU來說等待時間越?,效率 越低。
讓我們現在總結?下:單級?表對連續內存要求?,于是引?了多級?表,但是多級?表也是?把雙 刃劍,在減少連續存儲要求且減少存儲空間的同時降低了查詢效率。
有沒有提升效率的辦法呢?計算機科學中的所有問題,都可以通過添加?個中間層來解決。 MMU 引? 了新武器,江湖?稱快表的 TLB (其實,就是緩存)
當 CPU 給 MMU 傳新虛擬地址之后, MMU 先去問 TLB 那邊有沒有,如果有就直接拿到物理地址發到 總線給內存,?活。但 TLB 容量?較?,難免發? Cache Miss ,這時候 MMU 還有保底的?武器?表,在?表中找到之后 MMU 除了把地址發到總線傳給內存,還把這條映射關系給到TLB,讓它記錄 ?下刷新緩存。
1.2.5缺頁異常
設想,CPU給MMU的虛擬地址,在 TLB 和?表都沒有找到對應的物理?,該怎么辦呢?其實這就是 缺?異常 Page Fault ,它是?個由硬件中斷觸發的可以由軟件邏輯糾正的錯誤。 假如?標內存?在物理內存中沒有對應的物理?或者存在但?對應權限,CPU就?法獲取數據,這種 情況下CPU就會報告?個缺?錯誤。
由于CPU沒有數據就?法進?計算,CPU罷?了??進程也就出現了缺?中斷,進程會從??態切換 到內核態,并將缺?中斷交給內核的Page Fault Handler 處理。
1.3線程的優點
? 創建?個新線程的代價要?創建?個新進程?得多
? 與進程之間的切換相?,線程之間的切換需要操作系統做的?作要少很多
? 最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上 下?切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能 損耗是將寄存器中的內容切換出。
? 另外?個隱藏的損耗是上下?的切換會擾亂處理器的緩存機制。簡單的說,?旦去切換上下 ?,處理器中所有已經緩存的內存地址?瞬間都作廢了。還有?個顯著的區別是當你改變虛 擬內存空間的時候,處理的?表緩沖 TLB (快表)會被全部刷新,這將導致內存的訪問在? 段時間內相當的低效。但是在線程的切換中,不會出現這個問題,當然還有硬件cache。
? 線程占?的資源要?進程少很
? 能充分利?多處理器的可并?數量
? 在等待慢速I/O操作結束的同時,程序可執?其他的計算任務
? 計算密集型應?,為了能在多處理器系統上運?,將計算分解到多個線程中實現
? I/O密集型應?,為了提?性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
1.4線程的缺點
? 性能損失
? ?個很少被外部事件阻塞的計算密集型線程往往?法與其它線程共享同?個處理器。如果計 算密集型線程的數量?可?的處理器多,那么可能會有較?的性能損失,這?的性能損失指 的是增加了額外的同步和調度開銷,?可?的資源不變。
? 健壯性降低
? 編寫多線程需要更全?更深?的考慮,在?個多線程程序?,因時間分配上的細微偏差或者 因共享了不該共享的變量?造成不良影響的可能性是很?的,換句話說線程之間是缺乏保護 的。
? 缺乏訪問控制
? 進程是訪問控制的基本粒度,在?個線程中調?某些OS函數會對整個進程造成影響。
? 編程難度提?
? 編寫與調試?個多線程程序?單線程程序困難得多
1.5線程異常
? 單個線程如果出現除零,野指針問題導致線程崩潰,進程也會隨著崩潰
? 線程是進程的執?分?,線程出異常,就類似進程出異常,進?觸發信號機制,終?進程,進程 終?,該進程內的所有線程也就隨即退出
1.6線程用途
? 合理的使?多線程,能提?CPU密集型程序的執?效率
?? 合理的使?多線程,能提?IO密集型程序的??體驗(如?活中我們?邊寫代碼?邊下載開發? 具,就是多線程運?的?種表現)
2.Linux進程VS線程
2.1進程和線程
? 進程是資源分配的基本單位
? 線程是調度的基本單位
? 線程共享進程數據,但也擁有??的?部分數據:
? 線程ID
? ?組寄存器
? 棧
? errno
? 信號屏蔽字
? 調度優先級
2.2進程的多個線程共享
同?地址空間,因此TextSegment、DataSegment都是共享的,如果定義?個函數,在各線程中都可以調 ?,如果定義?個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:
? ?件描述符表
? 每種信號的處理?式(SIG_IGN、SIG_DFL或者?定義的信號處理函數)
? 當前?作?錄 ? ??id和組id
進程和線程的關系如下圖:
2.3關于進程線程的問題
? 如何看待之前學習的單進程?具有?個線程執?流的進程
3.Linux線程控制
3.1POSIX線程庫
? 與線程有關的函數構成了?個完整的系列,絕?多數函數的名字都是以“pthread_”打頭的
? 要使?這些函數庫,要通過引?頭?
? 鏈接這些線程函數庫時要使?編譯器命令的“-lpthread”選項
3.2創建線程
由于每個進程有??獨?的內存空間,故此“ID”的作?域是進程級??系統級(內核不認識)。
其實pthread庫也是通過內核提供的系統調?(例如clone)來創建線程的,?內核會為每個線程創建 系統全局唯?的“ID”來唯?標識這個線程。
使?PS命令查看線程信息
3.3線程終止
如果需要只終?某個線程?不終?整個進程,可以有三種?法:
1. 從線程函數return。這種?法對主線程不適?,從main函數return相當于調?exit。
?2. 線程可以調?pthread_exit終???。
3. ?個線程可以調?pthread_cancel終?同?進程中的另?個線程。
3.4線程等待
為什么需要線程等待?
? 已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
? 創建新的線程不會復?剛才退出線程的地址空間。
3.5線程分離
? 默認情況下,新創建的線程是joinable的,線程退出后,需要對其進?pthread_join操作,否則 ?法釋放資源,從?造成系統泄漏。
? 如果不關?線程的返回值,join是?種負擔,這個時候,我們可以告訴系統,當線程退出時,? 動釋放線程資源。
4.線程ID及進程地址空間布局
5.線程棧
雖然Linux將線程和進程不加區分的統?到了task_struct ,但是對待其地址空間的 stack 還是 有些區別的。