前言
本文介紹了地址空間和二級頁表、Linux下的線程、線程的優(yōu)缺點以及線程與進程的關(guān)系等概念。
一、地址空間和頁表
地址空間是進程能看到的資源窗口:一個進程可以看到代碼區(qū)、堆棧區(qū)、共享區(qū)、內(nèi)核區(qū)等,大部分的資源是在地址空間上看到的。
頁表決定進程真正有用資源的情況:進程認為自己獨占系統(tǒng)的4GB資源,但實際上進程擁有多少物理資源是由頁表決定的。
合理的對地址空間和頁表進行資源劃分,我們就可以對進程所擁有的資源進行分類:通過地址空間的區(qū)域劃分,劃分為棧區(qū)、堆區(qū)……,通過頁表映射到不同的物理內(nèi)存中。
1.二級頁表
在32位平臺下,一共有2^32
個地址,這也意味著有2^32
個地址需要被映射。
地址空間有2^32
個地址,每個地址單位都是1字節(jié),頁表也要有2^32
個條目(每個地址都要經(jīng)過頁表映射,它們都是頁表的條目),包括是否命中,包括RWX權(quán)限,包括U/K權(quán)限。一個條目假設(shè)有6個字節(jié)的數(shù)據(jù),那么光保存頁表的空間就需要24GB(4GB大約40億字節(jié))。
每個表項中處理要有虛擬地址和它映射的物理地址外,時間還需要一些權(quán)限相關(guān)的信息,用戶級頁表和內(nèi)核級頁表實際就是通過權(quán)限進行區(qū)分。
虛擬地址:32位下是32位。
物理地址:被劃分為一塊塊的數(shù)據(jù)框。
OS要對物理內(nèi)存進行管理:先描述(結(jié)構(gòu)體:struct Page{//內(nèi)存的屬性——4KB}
),再組織(數(shù)組:struct Page mem[]
)。
在OS中把物理內(nèi)存一塊塊的數(shù)據(jù)框稱為頁框,磁盤上編譯形成可執(zhí)行程序的時候被劃分為一個個4KB的區(qū)域稱為頁幀。當內(nèi)存和磁盤進行數(shù)據(jù)交換時,也是以4KB大小為單位進行加載和保存的。
因此,將數(shù)據(jù)加載到內(nèi)存時,在文件系統(tǒng)級別需要按照4KB為基本單位將數(shù)據(jù)從外設(shè)搬到內(nèi)存。最后,OS系統(tǒng)想要管理內(nèi)存,除了結(jié)構(gòu)匹配還要有管理算法,Linux常見的管理算法稱為伙伴系統(tǒng)。
虛擬地址轉(zhuǎn)化為物理地址:虛擬地址形成后(以10,10,12的二進制構(gòu)成),頁表不止一張。第一級頁表頁目錄:前十個在頁目錄中查找,2^10
個指向頁表的內(nèi)容。頁表:頁表的條目項為2^10
個,條目寫的是指定頁框的起始物理地址,頁表項指向物理內(nèi)存中某一頁,剩下的12位虛擬地址剛好與頁框的大小是等價的(4KB = 2^12
B),因此,從物理地址的起始處 + 虛擬地址的低12位(2^12偏移量)作為頁內(nèi)偏移,就可以直接在某個頁內(nèi)找到某個地址。
其中的頁目錄項是一級頁表,頁表項是二級頁表。映射過程由MMU這個硬件完成(該硬件集成在CPU內(nèi)),頁表是一種軟件映射,MMU是一種硬件映射,虛擬地址轉(zhuǎn)為物理地址實際上是軟硬件結(jié)合的。
2.例子
修改常量字符串為什么會發(fā)送錯誤?
如果要修改一個常量字符串,虛擬地址需要經(jīng)過頁表映射查找到對應(yīng)的物理內(nèi)存,但是在查表的過程中會發(fā)現(xiàn)該地址的權(quán)限是只讀,對一個只讀地址進行修改會導致在MMU內(nèi)部觸發(fā)硬件錯誤,OS識別到這個錯誤會該對應(yīng)進程發(fā)送信號終止對應(yīng)進程。
二、線程
1.概念
- 在一個程序里的一個執(zhí)行路線就叫做線程(可以參考進程)。更準確的定義是:線程是一個進程內(nèi)部的控制序列。
- 一切進程都至少有一個執(zhí)行線程。
- 線程在進程內(nèi)部運行本質(zhì)是在進程的地址空間內(nèi)運行。
- Linux中,在CPU眼中看到的PCB都比傳統(tǒng)的進程更加輕量化。
- 透過進程的虛擬地址空間可以看到進程的大部分資源,將進程的資源合理分配給每個執(zhí)行流,就形成了線程執(zhí)行流。
- 不同平臺的多線程底層實現(xiàn)策略都是不同的,本文我們了解的是Linux下的多線程策略。
線程對應(yīng)的模型:進程的創(chuàng)建實際上伴隨著進程控制塊(PCB)、進程地址空間(mm_struct)以及頁表的創(chuàng)建(虛擬地址和物理地址是通過頁表建立映射的):
進程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 代碼和數(shù)據(jù)。
每個進程都有字節(jié)獨立的進程地址空間和獨立的頁表,這意味著每個進程在運行時會具有獨立性,
如果我們在創(chuàng)建進程時只創(chuàng)建進程的PCB,并要求創(chuàng)建出來的PCB不再獨立創(chuàng)建資源,而是與父進程共享資源。那么創(chuàng)建的結(jié)果就是下面這樣的:
因為我們可以通過虛擬地址空間 + 頁表的方式對進程的資源進行劃分,單個進程的執(zhí)行力度會比之前的進程更細。
上圖中每個線程都是當前進程的一個執(zhí)行流,線程在進程的內(nèi)部運行,在進程的地址空間運行,擁有該進程的一部分資源。
重新理解前面講的進程:在內(nèi)核的視角,進程是承擔分配系統(tǒng)資源的基本實體。
創(chuàng)建進程時,申請的PCB、虛擬內(nèi)存空間、頁表以及加載到物理內(nèi)存中的代碼和數(shù)據(jù):花費CPU資源創(chuàng)建進程并初始化;花費內(nèi)存資源保存進程的內(nèi)核數(shù)據(jù)結(jié)構(gòu)、代碼和數(shù)據(jù);花費CPU的IO資源從外設(shè)IO到內(nèi)存。所以承擔分配系統(tǒng)資源的基本實體是進程。
總結(jié)一下,我們創(chuàng)建進程時,OS申請一堆的內(nèi)核數(shù)據(jù)結(jié)構(gòu)占用資源,進程的代碼和數(shù)據(jù)加載到內(nèi)存中也要占用資源,以及其他部分占用的資源。因此,進程是承擔系統(tǒng)資源分配的基本實體。
我們之前討論的進程都是只有一個PCB,也就是說該進程內(nèi)部只有一個執(zhí)行流,即單執(zhí)行流,這與我們上面講的并不沖突,如果是像上面這樣的一個進程內(nèi)部由多個執(zhí)行流,那它就是多執(zhí)行流進程。
站在CPU角度,能否去識別當前調(diào)度的task_struct是進程還是線程?
不能,也不需要,CPU不關(guān)心當前調(diào)度的是進程還是線程。CPU以task_struct為單位進行調(diào)度,今天我們喂給CPU的task_struct是小于等于過去所說的task_struct的,它比之前的更輕量化。因此,在Linux中可以把進程和線程做一個統(tǒng)一,CPU看到的task_struct稱為輕量級期間進程。
在Linux中,什么是線程?——線程是CPU的基本調(diào)度單位。
Linux下并不存在真正的線程
Linux下的線程是用進程模擬的。
如果OS真正要專門設(shè)計“線程”概念,OS就要管理線程了(先描述,再組織)。
Windows下確實是為線程專門設(shè)計了數(shù)據(jù)結(jié)果表示線程對象TCB,但是線程的創(chuàng)建就是為被執(zhí)行,執(zhí)行需要被調(diào)度、存在ID/狀態(tài)、優(yōu)先級、上下文、?!葍?nèi)容,這些線程調(diào)度需要的東西與進程有很多地方是重疊的。因此,Linux下沒有為“線程”專門設(shè)計對應(yīng)的數(shù)據(jù)結(jié)構(gòu),而是直接復用了進程的PCB,用PCB來表示Linux下的“線程”。
總結(jié)
- Linux內(nèi)核中嚴格來說是沒有真正意義的線程的,Linux用進程PCB來模擬線程,它有一套完全屬于自己的線程方案。
- 站在CPU角度,每一個PCB都可以稱為輕量級進程。
- Linux下,線程是CPU調(diào)度的基本單位,進程是承擔分配系統(tǒng)資源的基本單位。
- 進程用來整體申請資源,線程是伸手向進程要資源。(所以線程在執(zhí)行時申請的資源,實際上是進程向系統(tǒng)申請的資源)
- 進程模擬線程的好處:用PCB模擬線程,則為PCB編寫的結(jié)構(gòu)和算法都可以進行復用,不用單獨再為線程創(chuàng)建結(jié)構(gòu)和調(diào)度算法,降低了系統(tǒng)的維護成本,同時復用進程的那套,可靠高效。
2.線程的優(yōu)點
- 創(chuàng)建一個線程要花費的代價比創(chuàng)建一個進程的代價要小得多,與進程切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多。
進程切換:要切換頁表、虛擬地址空間、PCB、上下文;
線程切換:切換PCB和上下文。 - 線程占用的資源要比進程占用的資源少很多。
- 線程能充分利用多處理器的可并行數(shù)量。
- 在等待慢速I/O操作結(jié)束的同時,程序可執(zhí)行其他計算任務(wù)。
- 計算密集型應(yīng)用(CPU、加密、解密、算法等),為了能在多處理器系統(tǒng)上運行,可以講計算分解到多個線程中實現(xiàn)。
- I/O密集型應(yīng)用(外設(shè)、磁盤、顯示器、網(wǎng)絡(luò)),為了提高性能,講I/O操作重疊,使線程可以同時等待不同的I/O操作。
3.線程的缺點
- 性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與其他線程共享同一個處理器。
如果計算密集型線程的數(shù)量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開銷,而可用資源是不變的。 - 健壯性降低:編寫多線程需要更全面深入的考慮。
在一個多線程程序里,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的。換而言之,線程之間是缺乏保護的。 - 缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調(diào)用某些OS函數(shù)會對整個進程造成影響。
- 編程難度提高:編寫與調(diào)試一個多線程程序比單線程程序困難的多。
健壯性降低的例子
一個線程如果出現(xiàn)了異常會影響其他線程(健壯性、魯棒性較差)
1 #include<iostream>
2 #include<string>
3 #include<unistd.h>
4 #include<pthread.h>
5 using namespace std;
6 void* start_routine(void* args)
7 {
8 string name = static_cast<const char*>(args);//安全的進行強制類型轉(zhuǎn)換
9 while(1)
10 {
11 cout<<"new thread create success, name:"<<name<<endl;
12 sleep(1);
13 int* p = nullptr;
14 *p = 0;
15 }
16 }
17 int main()
18 {
19 pthread_t id;
20 pthread_create(&id, nullptr, start_routine, (void*)"thread new");
21 while(1)
22 {
23 cout<<"new thread create success, name: main thread"<<endl;
24 sleep(1);
25 }
26 return 0;
27 }
運行:
線程出現(xiàn)異常會影響其他線程,這是因為信號是由OS發(fā)送給整個進程的,當前線程出現(xiàn)異常,那么OS識別到當前硬件報錯、地址轉(zhuǎn)化出現(xiàn)失敗、沒有權(quán)限的空間進行寫入、MMU+頁表執(zhí)行異常等問題,OS會立即識別是哪個線程/進程出錯,而所有的線程的PID是相同的,因此OS會直接給所有該PID的線程的PCB寫入11號段錯誤信號,這就終止了當前的進程執(zhí)行流,當前進程就退了,而線程所擁有的資源是進程給的,進程沒了,線程也就得退出了。
4.線程的異常
當線程如果出現(xiàn)除零、野指針問題,會導致當前線程崩潰,進程也會隨之崩潰。線程是進程的執(zhí)行分支,線程出現(xiàn)異常,就等同于進程出現(xiàn)異常,進而觸發(fā)信號機制,終止進程。進程終止了,進程內(nèi)運行的所有線程也就終止了。
5.線程的用途
- 合理使用多線程,可用提高CPU密集型程序的執(zhí)行效率;
- 合理使用多線程,可用提高IO密集型程序的用戶體驗(例如,我們一邊寫代碼,一邊下載開發(fā)工具,就是多線程運行的一種表現(xiàn))
三、Linux下的進程與線程
進程是承擔分配系統(tǒng)資源的基本實體,線程是系統(tǒng)調(diào)度的基本單位。
進程的多個線程共享的資源
- 因為這些線程在同一個地址空間,所以代碼段(Text Segment)、數(shù)據(jù)段(Data Segment)都是共享的。
- 如果是函數(shù),那么在各個線程內(nèi)都是可用調(diào)用的;如果是變量,那么在各個線程中都可以訪問到。
- 線程還貢獻一下進程資源和環(huán)境:
文件描述符表、每種信號的處理方式(SIG_IGN、SIG_DFL或者自定義的信號處理函數(shù))、當前的工作目錄、用戶id和組id。
線程獨立的數(shù)據(jù)
進程內(nèi)的線程共享進程的數(shù)據(jù),但是也擁有自己獨立的一部分數(shù)據(jù)。
線程ID、一組寄存器:存儲線程的上下文信息、棧:線程的臨時數(shù)據(jù)、errno、信號屏蔽字、調(diào)度優(yōu)先級。
進程與線程的關(guān)系
我們之前接觸到的只有一個線程執(zhí)行流的進程,就是單線程進程。文章來源:http://www.zghlxwxcb.cn/news/detail-483798.html
總結(jié)
以上就是今天要講的內(nèi)容,本文介紹了本文介紹了地址空間和二級頁表、Linux下的線程、線程的優(yōu)缺點以及線程與進程的關(guān)系等概念。本文作者目前也是正在學習Linux相關(guān)的知識,如果文章中的內(nèi)容有錯誤或者不嚴謹?shù)牟糠?,歡迎大家在評論區(qū)指出,也歡迎大家在評論區(qū)提問、交流。
最后,如果本篇文章對你有所啟發(fā)的話,希望可以多多支持作者,謝謝大家!文章來源地址http://www.zghlxwxcb.cn/news/detail-483798.html
到了這里,關(guān)于Linux之多線程(上)——Linux下的線程概念的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!