Linux線程概念
1 什么是線程
? 在?個程序?的?個執?路線就叫做線程(thread)。更準確的定義是:線程是“?個進程內部
的控制序列”
? ?切進程?少都有?個執?線程
? 線程在進程內部運?,本質是在進程地址空間內運?
? 在Linux系統中,在CPU眼中,看到的PCB都要?傳統的進程更加輕量化
? 透過進程虛擬地址空間,可以看到進程的?部分資源,將進程資源合理分配給每個執?流,就形
成了線程執?流
2 分?式存儲管理
2-1 虛擬地址和?表的由來
思考?下,如果在沒有虛擬內存和分?機制的情況下,每?個??程序在物理內存上所對應的空間必
須是連續的,如下圖:
因為每?個程序的代碼、數據?度都是不?樣的,按照這樣的映射?式,物理內存將會被分割成各種
離散的、??不同的塊。經過?段運?時間之后,有些程序會退出,那么它們占據的物理內存空間可
比特就業課
以被回收,導致這些物理內存都是以很多碎?的形式存在。
怎么辦呢?我們希望操作系統提供給??的空間必須是連續的,但是物理內存最好不要連續。此時虛
擬內存和分?便出現了,如下圖所?:
把物理內存按照?個固定的?度的?框進?分割,有時叫做物理?。每個?框包含?個物理?
(page)。?個?的??等于?框的??。?多數 32位 體系結構?持 4KB 的?,? 64位 體系結
構?般會?持 8KB 的?。區分??和?個?框是很重要的:
? ?框是?個存儲區域;
? ??是?個數據塊,可以存放在任何?框或磁盤中。
有了這種機制,CPU 便并?是直接訪問物理內存地址,?是通過虛擬地址空間來間接的訪問物理內存
地址。所謂的虛擬地址空間,是操作系統為每?個正在執?的進程分配的?個邏輯地址,在32位機
上,其范圍從0 ~ 4G-1。
操作系統通過將虛擬地址空間和物理內存地址之間建?映射關系,也就是?表,這張表上記錄了每?
對?和?框的映射關系,能讓CPU間接的訪問物理內存地址。
總結?下,其思想是將虛擬內存下的邏輯地址空間分為若??,將物理內存空間分為若??框,通過
?表便能把連續的虛擬內存,映射到若?個不連續的物理內存?。這樣就解決了使?連續的物理內存
造成的碎?問題。
1-2-2 物理內存管理
假設?個可?的物理內存有 4GB 的空間。按照?個?框的?? 4KB 進?劃分, 4GB 的空間就是
4GB/4KB = 1048576 個?框。有這么多的物理?,操作系統肯定是要將其管理起來的,操作系統
需要知道哪些?正在被使?,哪些?空閑等等。
內核? struct page 結構表?系統中的每個物理?,出于節省內存的考慮, struct page 中使
?了?量的聯合體union。
/* include/linux/mm_types.h */
struct page {
/* 原?標志,有些情況下會異步更新 */unsigned long flags;
union {
struct {
/* 換出?列表,例如由zone->lru_lock保護的active_list */
struct list_head lru;
/* 如果最低為為0,則指向inode
* address_space,或為NULL
* 如果?映射為匿名內存,最低為置位
* ?且該指針指向anon_vma對象
*/
struct address_space* mapping;
/* 在映射內的偏移量 */
pgoff_t index;
/*
* 由映射私有,不透明數據
* 如果設置了PagePrivate,通常?于buffer_heads
* 如果設置了PageSwapCache,則?于swp_entry_t
* 如果設置了PG_buddy,則?于表?伙伴系統中的階
*/
unsigned long private;
};
struct { /* slab, slob and slub */
union {
struct list_head slab_list; /* uses lru */
struct { /* Partial pages */
struct page* next;
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache* slab_cache; /* not slob */
/* Double-word boundary */
void* freelist; /* first free object */
union {
void* s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct { /* SLUB */
unsigned inuse : 16; /* ?于SLUB分配器:對象的數? */
unsigned objects : 15;
unsigned frozen : 1;
};
};};
...
};
union {
/* 內存管理?系統中映射的?表項計數,?于表??是否已經映射,還?于限制逆向映射搜
索*/
atomic_t _mapcount;
unsigned int page_type;
unsigned int active; /* SLAB */
int units; /* SLOB */
};
...
#if defined(WANT_PAGE_VIRTUAL)
/* 內核虛擬地址(如果沒有映射則為NULL,即?端內存) */
void* virtual;
#endif /* WANT_PAGE_VIRTUAL */
...
}
**其中?較重要的?個參數:
- flags :?來存放?的狀態。這些狀態包括?是不是臟的,是不是被鎖定在內存中等。flag的
每?位單獨表??種狀態,所以它?少可以同時表?出32種不同的狀態。這些標志定義在
<linux/page-flags.h>中。其中?些?特位?常重要,如PG_locked?于指定?是否鎖定,
PG_uptodate?于表??的數據已經從塊設備讀取并且沒有出現錯誤。 - _mapcount :表?在?表中有多少項指向該?,也就是這??被引?了多少次。當計數值變
為-1時,就說明當前內核并沒有引?這??,于是在新的分配中就可以使?它。 - virtual :是?的虛擬地址。通常情況下,它就是?在虛擬內存中的地址。有些內存(即所謂
的?端內存)并不永久地映射到內核地址空間上。在這種情況下,這個域的值為NULL,需要的
時候,必須動態地映射這些?。
要注意的是 struct page 與物理?相關,?并?與虛擬?相關。?系統中的每個物理?都要分配?
個這樣的結構體,讓我們來算算對所有這些?都這么做,到底要消耗掉多少內存。
算 struct page 占40個字節的內存吧,假定系統的物理?為 4KB ??,系統有 4GB 物理內存。
那么系統中共有?? 1048576 個(1兆個),所以描述這么多??的page結構體消耗的內存只不過
40MB ,相對系統 4GB 內存??,僅是很?的?部分罷了。因此,要管理系統中這么多物理??,這
個代價并不算太?。
要知道的是,?的??對于內存利?和系統開銷來說?常重要,?太?,? 必然會剩余較?不能利
?的空間(?內碎?)。?太?,雖然可以減??內碎?的??,但是?太多,會使得?表太??占
?內存,同時系統頻繁地進??轉化,加重系統開銷。因此,?的??應該適中,通常為 512B -
8KB ,windows系統的?框??為4KB。**
2-3 ?表
?表中的每?個表項,指向?個物理?的開始地址。在 32 位系統中,虛擬內存的最?空間是 4GB ,
這是每?個??程序都擁有的虛擬內存空間。既然需要讓 4GB 的虛擬內存全部可?,那么?表中就需
要能夠表?這所有的 4GB 空間,那么就?共需要 4GB/4KB = 1048576 個表項。如下圖所?:
虛擬內存看上去被虛線“分割”成?個個單元,其實并不是真的分割,虛擬內存仍然是連續的。這個
虛線的單元僅僅表?它與?表中每?個表項的映射關系,并最終映射到相同??的?個物理內存?
上。
?表中的物理地址,與物理內存之間,是隨機的映射關系,哪?可?就指向哪?(物理?)。雖然最終使
?的物理內存是離散的,但是與虛擬內存對應的線性地址是連續的。處理器在訪問數據、獲取指令
時,使?的都是線性地址,只要它是連續的就可以了,最終都能夠通過?表找到實際的物理地址。
在 32 位系統中,地址的?度是 4 個字節,那么?表中的每?個表項就是占? 4 個字節。所以?表占
據的總空間??就是: 1048576*4 = 4MB 的??。也就是說映射表??本?,就要占? 4MB /
4KB = 1024 個物理?。這會存在哪些問題呢?
? 回想?下,當初為什么使??表,就是要將進程劃分為?個個?可以不?連續的存放在物理內存
中,但是此時?表就需要1024個連續的?框,似乎和當時的?標有點背道?馳了…
? 此外,根據局部性原理可知,很多時候進程在?段時間內只需要訪問某?個?就可以正常運?
了。因此也沒有必要?次讓所有的物理?都常駐內存。
解決需要?容量?表的最好?法是:把?表看成普通的?件,對它進?離散分配,即對?表再分?,
由此形成多級?表的思想。
為了解決這個問題,可以把這個單??表拆分成 1024 個體積更?的映射表。如下圖所?。這樣?
來,1024(每個表中的表項個數) * 1024(表的個數),仍然可以覆蓋 4GB 的物理內存空間。
這?的每?個表,就是真正的?表,所以?共有 1024 個?表。?個?表??占? 4KB ,那么
1024 個?表?共就占?了 4MB 的物理內存空間,和之前沒差別啊?
從總數上看是這樣,但是?個應?程序是不可能完全使?全部的 4GB 空間的,也許只要??個?表就
可以了。例如:?個??程序的代碼段、數據段、棧段,?共就需要 10 MB 的空間,那么使? 3 個
?表就?夠了。
計算過程:
每?個?表項指向?個 4KB 的物理?,那么?個?表中 1024 個?表項,?共能覆蓋 4MB 的物理內
存;
那么 10MB 的程序,向上對?取整之后(4MB 的倍數,就是 12 MB),就需要 3 個?表就可以了。
2-4 ??錄結構
到?前為?,每?個?框都被?個?表中的?個表項來指向了,那么這 1024 個?表也需要被管理起
來。管理?表的表稱之為??錄表,形成?級?表。如下圖所?:
? 所有?表的物理地址被??錄表項指向
? ??錄的物理地址被 CR3 寄存器 指向,這個寄存器中,保存了當前正在執?任務的??錄地
址。
所以操作系統在加載??程序的時候,不僅僅需要為程序內容來分配物理內存,還需要為?來保存程
序的??錄和?表分配物理內存。
2-5 兩級?表的地址轉換
**下?以?個邏輯地址為例。將邏輯地址( 0000000000,0000000001,11111111111 )轉換為物
理地址的過程:
- 在32位處理器中,采?4KB的???,則虛擬地址中低12位為?偏移,剩下?20位給?表,分成
兩級,每個級別占10個bit(10+10)。 - CR3 寄存器 讀取??錄起始地址,再根據?級?號查??錄表,找到下?級?表在物理內存中
存放位置。 - 根據?級?號查表,找到最終想要訪問的內存塊號。
- 結合?內偏移量得到物理地址。
比特就業課 - 注:?個物理?的地址?定是 4KB 對?的(最后的 12 位全部為 0 ),所以其實只需要記錄物理
?地址的? 20 位即可。 - 以上其實就是 MMU 的?作流程。MMU(Memory Manage Unit)是?種硬件電路,其速度很快,
主要?作是進?內存管理,地址轉換只是它承接的業務之?。
到這?其實還有個問題,MMU要先進?兩次?表查詢確定物理地址,在確認了權限等問題后,MMU再將這個物理地址發送到總線,內存收到之后開始讀取對應地址的數據并返回。那么當?表變為N級時,就變成了N次檢索+1次讀寫。可?,?表級數越多查詢的步驟越多,對于CPU來說等待時間越?,效率越低。讓我們現在總結?下:單級?表對連續內存要求?,于是引?了多級?表,但是多級?表也是?把雙刃劍,在減少連續存儲要求且減少存儲空間的同時降低了查詢效率。
有沒有提升效率的辦法呢?計算機科學中的所有問題,都可以通過添加?個中間層來解決。 MMU 引?了新武器,江湖?稱快表的 TLB (其實,就是緩存)
當 CPU 給 MMU 傳新虛擬地址之后, MMU 先去問 TLB 那邊有沒有,如果有就直接拿到物理地址發到總線給內存,?活。但 TLB 容量?較?,難免發? Cache Miss ,這時候 MMU 還有保底的?武器
?表,在?表中找到之后 MMU 除了把地址發到總線傳給內存,還把這條映射關系給到TLB,讓它記錄?下刷新緩存。**
2-6 缺?異常
設想,CPU 給 MMU 的虛擬地址,在 TLB 和?表都沒有找到對應的物理?,該怎么辦呢?其實這就是
缺?異常 Page Fault ,它是?個由硬件中斷觸發的可以由軟件邏輯糾正的錯誤。
假如?標內存?在物理內存中沒有對應的物理?或者存在但?對應權限,CPU 就?法獲取數據,這種
情況下CPU就會報告?個缺?錯誤。
由于 CPU 沒有數據就?法進?計算,CPU罷?了??進程也就出現了缺?中斷,進程會從??態切換
到內核態,并將缺?中斷交給內核的 Page Fault Handler 處理。
缺?中斷會交給 PageFaultHandler 處理,其根據缺?中斷的不同類型會進?不同的處理:
? Hard Page Fault 也被稱為 Major Page Fault ,翻譯為硬缺?錯誤/主要缺?錯誤,這
時物理內存中沒有對應的物理?,需要CPU打開磁盤設備讀取到物理內存中,再讓MMU建?虛擬
地址和物理地址的映射。
? Soft Page Fault 也被稱為 Minor Page Fault ,翻譯為軟缺?錯誤/次要缺?錯誤,這
時物理內存中是存在對應物理?的,只不過可能是其他進程調?的,發出缺?異常的進程不知道
?已,此時MMU只需要建?映射即可,?需從磁盤讀取寫?內存,?般出現在多進程共享內存區
域。
? Invalid Page Fault 翻譯為?效缺?錯誤,?如進程訪問的內存地址越界訪問,??如對
空指針解引?內核就會報 segment fault 錯誤中斷進程直接掛掉。
3 線程的優點
? 創建?個新線程的代價要?創建?個新進程?得多
? 與進程之間的切換相?,線程之間的切換需要操作系統做的?作要少很多
? 最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上
下?切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能
損耗是將寄存器中的內容切換出。
? 另外?個隱藏的損耗是上下?的切換會擾亂處理器的緩存機制。簡單的說,?旦去切換上下
?,處理器中所有已經緩存的內存地址?瞬間都作廢了。還有?個顯著的區別是當你改變虛
擬內存空間的時候,處理的?表緩沖 TLB (快表)會被全部刷新,這將導致內存的訪問在?
段時間內相當的低效。但是在線程的切換中,不會出現這個問題,當然還有硬件cache。
? 線程占?的資源要?進程少很
? 能充分利?多處理器的可并?數量
? 在等待慢速I/O操作結束的同時,程序可執?其他的計算任務
? 計算密集型應?,為了能在多處理器系統上運?,將計算分解到多個線程中實現
? I/O密集型應?,為了提?性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
4 線程的缺點
? 性能損失
? ?個很少被外部事件阻塞的計算密集型線程往往?法與其它線程共享同?個處理器。如果計
算密集型線程的數量?可?的處理器多,那么可能會有較?的性能損失,這?的性能損失指
的是增加了額外的同步和調度開銷,?可?的資源不變。
? 健壯性降低
? 編寫多線程需要更全?更深?的考慮,在?個多線程程序?,因時間分配上的細微偏差或者
因共享了不該共享的變量?造成不良影響的可能性是很?的,換句話說線程之間是缺乏保護
的。
? 缺乏訪問控制
? 進程是訪問控制的基本粒度,在?個線程中調?某些OS函數會對整個進程造成影響。
? 編程難度提?
? 編寫與調試?個多線程程序?單線程程序困難得多
5 線程異常
? 單個線程如果出現除零,野指針問題導致線程崩潰,進程也會隨著崩潰
? 線程是進程的執?分?,線程出異常,就類似進程出異常,進?觸發信號機制,終?進程,進程
終?,該進程內的所有線程也就隨即退出
6 線程?途
? 合理的使?多線程,能提?CPU密集型程序的執?效率
? 合理的使?多線程,能提?IO密集型程序的??體驗(如?活中我們?邊寫代碼?邊下載開發?
具,就是多線程運?的?種表現)