計算機 對于系統(tǒng)程序開發(fā)者來說,計算機三個部件最為關(guān)鍵:CPU、內(nèi)存、I/O控制芯片。 高速的北橋芯片:為了協(xié)調(diào)CPU、內(nèi)存和高速的圖形設(shè)備。此時慢速的I/O總線已經(jīng)無法滿足需求。 低速設(shè)備的南橋芯片:由于北橋芯片運行速度非常高,于是人們有設(shè)計了低速設(shè)備的南橋芯片,磁盤、usb、鍵盤、鼠標(biāo)等設(shè)備都連接在南橋芯片上,由南橋芯片將他們匯總后鏈接到北橋上。 SMP(對稱多處理器)多CPU的計算機;簡單的來講就是每個CPU在系統(tǒng)中所處的地位和發(fā)揮的功能是一樣的,是相互對稱的。使用場景:大型數(shù)據(jù)庫、網(wǎng)絡(luò)服務(wù)器上,他們要求同時處理大量的請求,而這些請求時相互獨立的,所以多處理器就可以最大效能的發(fā)揮作用。多處理器最多的應(yīng)用場合時這些商用的服務(wù)器和需要處理大量計算的環(huán)境。(CPU頻率碰到了天花板4GHZ,才出現(xiàn)了SMP). 多核處理器:實際上就是SMP的簡化版。這是由于多處理器成本高,所以廠商將多個處理器“合并在一起打包出售”,這些被打包的處理器之間共享比較昂貴的緩存部件,只保留多個核心;并且以一個處理器的外包裝進行出售,價格比單核處理器只貴一點。當(dāng)然他們在緩存共享方面有細微的差別,但是從程序員的角度來看,他們的區(qū)別很小,邏輯上看他們是完全相同的。 系統(tǒng)軟件:傳統(tǒng)意義上一般將用于管理計算機本身的軟件稱為系統(tǒng)軟件,以區(qū)別普通的應(yīng)用程序。系統(tǒng)軟件可以分為兩塊;一塊是平臺性的,比如,操作系統(tǒng)內(nèi)核、驅(qū)動程序、運行庫和數(shù)以千記得系統(tǒng)工具;另一塊是用于程序開發(fā)的,比如,編譯器、鏈接器、匯編器等開發(fā)工具和開發(fā)庫。 計算機系統(tǒng)軟件體系結(jié)構(gòu)采用一種層的結(jié)構(gòu):計算機科學(xué)領(lǐng)域的任何問題都可以通過增加一個間接的中間層來解決。 接口:每個層次之間都必須要相互通信,則需要一個通信協(xié)議,我們一般將其稱為接口。接口下面那層是提供者,上層是使用者。除了硬件和應(yīng)用程序,其他層都是中間層,每個中間層都是對它下面的那層的包裝與擴展。正是由于這些中間層的存在,使得應(yīng)用程序和硬件之間保持相對獨立。
在軟件體系結(jié)構(gòu)中:位于最高層的是應(yīng)用程序。從整個層次結(jié)構(gòu)上來看,開發(fā)工具與應(yīng)用程序?qū)儆谕粚樱驗樗麄兌际褂猛唤涌冢蔷褪遣僮飨到y(tǒng)應(yīng)用程序編程接口。應(yīng)用程序接口的提供者是運行庫。 運行庫使用操作系統(tǒng)提供的系統(tǒng)調(diào)用接口,系統(tǒng)調(diào)用接口往往以軟中斷的方式提供;比如,linux使用0x80號中斷作為系統(tǒng)調(diào)用接口。 硬件規(guī)格: 操作系統(tǒng)內(nèi)核層對于硬件層來說是硬件接口的使用者,而硬件是接口的定義者,硬件的接口的定義決定了操作系統(tǒng)的內(nèi)核,具體來講就是驅(qū)動程序如何操作硬件,如何與硬件通訊,這種接口被叫做硬件規(guī)格。
操作系統(tǒng):一個功能是提供抽象的接口,另外一個主要功能就是管理硬件資源。 多道程序的設(shè)計:編寫一個監(jiān)控程序,當(dāng)某個程序暫時無需使用CPU時,監(jiān)控程序就把另外的正在等待CPU資源的程序啟動起來,使得CPU能夠充分利用起來,這種被稱為多到程序。 分時系統(tǒng):每個程序運行一段時間后都主動讓出CPU給其他的程序,使得一段時間內(nèi)每個程序都有機會運行一小段時間。 多任務(wù)系統(tǒng):操作系統(tǒng)接管了所有的硬件資源,并且本身運行在一個受硬件保護的級別,每個進程都有自己的獨立空間,使得進程之間的地址相互隔離。CPU由操作系統(tǒng)同一分配,每個進程都會根據(jù)進程優(yōu)先級的高低都有機會得到CPU.但是,如果運行超出了一定的時間,操作系統(tǒng)會暫停該進程,將CPU資源分配給其他的等待運行的進程,這種CPU分配方式為搶占式。
驅(qū)動程序可以看做事操作系統(tǒng)的一部分,它往往和系統(tǒng)內(nèi)核一起運行在特權(quán)級別,但他與操作系統(tǒng)內(nèi)核之間有一定的獨立性,使得驅(qū)動程序有較好的靈活性。操作系統(tǒng)開發(fā)者為硬件生產(chǎn)商提供了一系列接口和框架,凡是按照這個接口和框架開發(fā)的驅(qū)動程序都可以在該操作系統(tǒng)上使用。進程的總體目標(biāo)是希望每個進程從邏輯上看都可以獨占計算機的資源。操作系統(tǒng)的多任務(wù)功能使得CPU能夠在多個進程之間共享;從進程的角度看好像是他獨占了CPU而不用考慮與其他進程分享CPU的事。操作系統(tǒng)的I/O抽象模型也很好的實現(xiàn)I/O設(shè)備的共享和抽象。內(nèi)存通過虛擬地址空間;分段分頁來實現(xiàn)共享。為什么要虛擬內(nèi)存:如果程序直接在物理內(nèi)存上使用,直接訪問物理地址將帶來很多問題:1. 地址空間不隔離:所有程序都訪問物理地址,程序所使用的內(nèi)存空間不相互隔離,惡意的程序可以很容易改寫其他程序的內(nèi)存數(shù)據(jù),以達到破壞的目的。有些非惡意、但有臭蟲的程序可能不小心修改ill其他程序的數(shù)據(jù),就會使其他程序崩潰。而使用虛擬地址空間可保證一個任務(wù)的失敗不影響其他任務(wù)的執(zhí)行。 2. 內(nèi)存使用效率低:由于沒有有效的內(nèi)存管理機制,通常需要一個程序執(zhí)行時,監(jiān)控程序就將整個程序裝載進內(nèi)存然后執(zhí)行。內(nèi)存不夠時會發(fā)生大量的數(shù)據(jù)交換,效率低下。 3. 程序運行的地址不穩(wěn)定:因為程序每次需要裝入運行時,我們都需要給他從內(nèi)存中分配一塊足夠大的內(nèi)存空間區(qū)域,這個區(qū)域位置不確定;這給程序的編寫造成一定的麻煩,因為程序在編寫時,它訪問的數(shù)據(jù)和指令跳轉(zhuǎn)時的目標(biāo)地址很多都市固定的,這涉及到重定位問題。
解決問題的思路就是增加中間層,即使用一種間接地址訪問方法。我們把程序給出的地址看作是一種虛擬地址,然后通過某種映射,將虛擬地址轉(zhuǎn)換為物理地址。這樣,只要我們能夠妥善控制這個虛擬地址到物理地址的映射過程,就可以保證任意一個程序所能夠訪問的物理內(nèi)存區(qū)域跟另一個程序相互不重疊,以達到地址空間隔離的效果。物理地址空間是真真實實存在的;虛擬地址空間是指虛擬的,而且每個進程只能訪問自己的地址空間,這樣就有效的做到了進程的隔離。
分段:解決了第一個和第三個問題。首先,它做到了地址的隔離,如果程序訪問越界,將會被硬件判斷為非法訪問,拒絕這個地址請求,并將這個請求報告給操作系統(tǒng)或者監(jiān)控程序。再者,對于每個程序員來說,無論他們被分配到物理地址的那個區(qū)域,對于程序來說都是透明的。他們不需要關(guān)心物理地址的變化。 但分段沒有解決內(nèi)存使用效率問題;分段對內(nèi)存區(qū)域的映射還是按照程序為單位;如果內(nèi)存不足,被換入換出到磁盤的都是整個程序,這樣勢必會造成大量的磁盤訪問操作,從而嚴重影響速度。這種方法換是顯得比較粗糙,粒度比較大。 事實上,根據(jù)程序的局部性原理,當(dāng)一個程序在運行時,在某個段內(nèi),它只是頻繁的用到一小部分數(shù)據(jù),也就是說,程序的很多數(shù)據(jù)其實在一段時間內(nèi)都是不會被用到的。人們很自然的想到了更小粒度的內(nèi)存分割和映射的方法,使得程序的局部性原理得到充分的利用,大大提高了內(nèi)存使用率,這種方法就是分頁。 分頁:提高了內(nèi)存的使用率,減少了內(nèi)存碎片的產(chǎn)生;保護也是頁映射的目的之一。簡單來說就是每個頁可以設(shè)置權(quán)限屬性,只有操作系統(tǒng)有權(quán)限修改這些屬性。MMU都集成在CPU的內(nèi)部了,不會以獨立的部件存在。
線程基礎(chǔ) 線程:有時被稱為輕量級的進程(LWP),是程序執(zhí)行流的最小單位。一個標(biāo)準(zhǔn)的線程是由線程ID/當(dāng)前指令指針(PC)、寄存器集合和堆棧組成。通常意義上,一個進程由一到多個線程組成,各個線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)段、堆等)以及一些進程級的資源(打開的文件和信號)。 使用多線程的原因: 1. 某個操作可能會陷入長時間的等待,等待的線程會進入睡眠狀態(tài),無法繼續(xù)執(zhí)行,多線程執(zhí)行可以有效的利用等待的時間。典型的例子是等待網(wǎng)絡(luò)響應(yīng),這可能要花費數(shù)秒。 2. 某個操作(計算)會消耗大量的時間,如果只有一個線程,程序和用戶之間的交互會中斷。多線程可以讓一個線程負責(zé)交互,另一個線程負責(zé)計算。 3. 程序本身就要求并發(fā)操作。、 4. 多CPU或多核計算機,本身具備同時執(zhí)行多個線程的能力,因此單個線程程序無法全面的發(fā)揮計算機的全部計算能力。 5. 相對于多進程應(yīng)用,多線程在數(shù)據(jù)共享方面效率要高得多。
線程的訪問權(quán)限 線程的訪問非常自由,它可以訪問進程內(nèi)存空間內(nèi)的所有數(shù)據(jù),甚至包括其他線程的堆棧(如果他知道其他線程的堆棧地址,這是很少見的情況),但實際運用中線程也擁有自己的私有存儲空間: 1. 棧:盡管并非完全無法被其他線程訪問,但一般情況下仍然可以認為是私有數(shù)據(jù)。 2. 線程局部存儲,是某些操作系統(tǒng)為線程單獨提供的私有空間,但通常只具有很有限的容量。 3. 寄存器:寄存器是執(zhí)行流的基本數(shù)據(jù),因此為線程私有。
從C程序員的角度看,數(shù)據(jù)在線程之間是否私有: 私有:局部變量、函數(shù)參數(shù)、TLS線程局部存儲數(shù)據(jù) 線程之間共享進程所有:全局變量、堆上的數(shù)據(jù)、函數(shù)里的靜態(tài)變量、程序代碼,任何程序都有權(quán)讀取并執(zhí)行任何代碼、打開的文件。
線程調(diào)度:一個不斷在處理器上切換不同線程的的行為。線程通常至少擁有三種狀態(tài):運行、就緒、等待。IO密集型線程:頻繁等待的線程;CPU密集型線程:很少等待的線程;IO密集型的線程總比cpu密集型的線程容易得到優(yōu)先級的提升。對于linux來說,線程并不是一個通用的概念;linux對多線程支持頗為貧乏;事實上,在linux內(nèi)核中并不存在真正意義上的線程的概念。Linux將所有可執(zhí)行的實體稱為任務(wù)(TASK),無論是進程換是線程。每一個任務(wù)概念上都類似于一個單進程的線程,具有內(nèi)存空間、執(zhí)行實體、文件資源。不過,linux下不同任務(wù)之間可以選擇共享內(nèi)存空間,因而在實際意義上,共享同一個內(nèi)存空間的多個任務(wù)構(gòu)成了一個進程,這些任務(wù)也就成為了這個進程里的線程。在linux下,用以下方法可以創(chuàng)建一個新的任務(wù)。系統(tǒng)調(diào)用 作用 效果 fork 復(fù)制當(dāng)前進程 新的任務(wù)啟動并和本任務(wù)一起從fork返回,本任務(wù)返回的是新任務(wù)的pid,新任務(wù)返回0。Fork速度非常快,因為采用了寫時復(fù)制。Fork只能產(chǎn)生本任務(wù)的鏡像。 exec 使用新的可執(zhí)行映像覆蓋當(dāng)前可執(zhí)行映像 Fork+exec啟動別的新任務(wù)。 Clone 創(chuàng)建子進程并從指定位置開始執(zhí)行 產(chǎn)生新的線程使用clone。從指定的位置開始執(zhí)行,并且(可選)共享當(dāng)前的內(nèi)存空間和文件等。如此就可以在實際效果上產(chǎn)生一個線程。
線程安全 同步:為了避免多個線程同時讀寫同一個數(shù)據(jù)而產(chǎn)生不可預(yù)料的結(jié)果,我們需要將各個線程對同一個數(shù)據(jù)的訪問同步。所謂的同步,既是指在一個線程訪問數(shù)據(jù)未結(jié)束的時候,其他線程不得對同一個數(shù)據(jù)進行訪問。如此,對數(shù)據(jù)的訪問被原子化了。 同步的方式: 1. 鎖、 2. 二元信號量、(多元)信號量(對于允許多個線程并發(fā)訪問的資源,它是一個很好的選擇;一個初始值為n的信號量允許N個線程并發(fā)訪問。) 3. 互斥量:和二元信號量很類似,資源進同時只能被一個線程訪問。不同的是,信號量在整個系統(tǒng)可以被任意線程獲取并釋放。而互斥量則要求那個線程獲取了互斥量,那個線程就要負責(zé)釋放這個鎖。 4. 臨界區(qū):是比互斥量更加嚴格的同步手段。把臨界區(qū)的鎖獲取稱為進入臨界區(qū),釋放稱為離開臨界區(qū)。臨界區(qū)和互斥量與信號量的區(qū)別在于,互斥量和信號量在系統(tǒng)的任何進程里都是可見的,也就是說,一個進程創(chuàng)建了一個互斥量或者信號量,另一個進程試圖獲取該鎖是合法的。然而,臨界區(qū)的作用范圍僅限于本進程,其他進程無法獲取該鎖。除此之外,臨界區(qū)胡互斥量具有相同的性質(zhì)。 5. 讀寫鎖:適用于讀頻繁寫少的情況。對于一段數(shù)據(jù),多個線程同時讀取總是沒有問題的,寫時必須上鎖。 6. 條件變量:作用類似一個柵欄。線程有兩種操作,等待與喚醒。使用條件變量可以讓許多線程一起等待某個事件的發(fā)生,當(dāng)時間發(fā)生時,條件變量被喚醒,所有的線程可以一起恢復(fù)執(zhí)行。
可重入與線程安全 一個函數(shù)要被重入只有兩種情況:一是多個線程同時執(zhí)行這個函數(shù);二是函數(shù)自身調(diào)用自身。一個函數(shù)被重入表示這個函數(shù)沒有被執(zhí)行完成,由于外部因素或者內(nèi)部調(diào)用,又一次進入函數(shù)執(zhí)行。一個函數(shù)可重入,表明函數(shù)重入后不會產(chǎn)生任何不良的后果。一個函數(shù)要成為可重入的,必須具有如下幾個特點: 1. 不使用任何(局部)靜態(tài)或全局的非const變量。 2. 不返回任何(局部)靜態(tài)或者全局的非const變量的指針。 3. 僅依賴于調(diào)用方提供的參數(shù)。 4. 不依賴任何單個資源的鎖。 5. 不調(diào)用任何可重入的函數(shù)。 可重入是并發(fā)的強力保障,一個可重入的函數(shù)可以在多線程環(huán)境下放心使用。
過度優(yōu)化; Volatile關(guān)鍵字:一是阻止編譯器為了提高速度將一個變量緩存到寄存器中而不寫回;二是阻止編譯器調(diào)整volatile變量的指令順序。 Barrier:因為即使volatile可以阻止編譯器調(diào)整順序,也無法阻止CPU動態(tài)調(diào)度換序。現(xiàn)在并不存在可移植的阻止換序的方法。通常情況下是通過調(diào)用CPU提供的一條指令,這條指令被稱為barrier。它可以阻止cpu將該指令之前的指令交換到它之后,反之亦然。Barrier指令的作用類似于一個攔水壩,阻止換序穿透這個大壩。
三種線程模型 Windows和linux,都是在內(nèi)核里提供線程的支持。內(nèi)核線程,這里的內(nèi)核線程和linux內(nèi)核里的kernel_thread并不是一回事,它和我們之前討論的一樣,由多個處理器或調(diào)度器來實現(xiàn)并發(fā)。然而用戶實際使用的的線程不是內(nèi)核線程,而是存在于用戶態(tài)的用戶線程。用戶線程并不一定在操作系統(tǒng)內(nèi)核內(nèi)對應(yīng)著同樣數(shù)量的內(nèi)核線程。 用戶態(tài)多線程庫的實現(xiàn) 一對一模型:用戶線程具有和內(nèi)核線程一致的優(yōu)點,線程之間的并發(fā),是真正的并發(fā)。一個線程阻塞,其他線程不會受到影響。一對一模型可以讓多線程程序在多處理器的系統(tǒng)上有跟好的體現(xiàn)。一般直接使用API或系統(tǒng)調(diào)用創(chuàng)建的線程均為一對一的線程。 缺點:一是由于許多操作系統(tǒng)限制了內(nèi)核線程的數(shù)量,因此一對一線程會讓用戶的線程數(shù)量受到限制。二是許多操作系統(tǒng)內(nèi)核線程調(diào)度時,上下文切換開銷大,導(dǎo)致用戶線程的執(zhí)行效率下降。 多對一模型:線程間的切換由用戶態(tài)的代碼來進行,線程切換的速度要快許多,但是一個用戶線程阻塞,那么所有線程都將無法執(zhí)行,因此此時內(nèi)核里的線程也隨之阻塞了。多對一模型的好處就是高效的上下文切換和幾乎無限制的線程數(shù)量。 多對多模型:一個用戶線程阻塞并不會使得所有用戶線程阻塞,因為此時還有別的線程可以被調(diào)度來執(zhí)行。另外,多對多模型對用戶線程的數(shù)量沒有什么限制,在多處理器上,線程也能得到一定性能的提升。
|
新聞熱點
疑難解答