1. 程式人生 > >程序員的自我修養-第一章 筆記

程序員的自我修養-第一章 筆記

筆記 cif lwp 訪問 依靠 另一個 來講 避免 虛擬地址

1.計算機硬件的三個部件最為重要:中央處理器CPU,內存和IO控制芯片。

2.主板上北橋芯片處理高速設備。南橋芯片處理低速設備,然後匯總後連接到北橋上。

3.多個處理之間共享比較昂貴的緩沖部件,只保留報個核心,並且以一個處理器的外包裝出售,售價比單核心的處理器只貴了一點,這就是多核處理器(Multi-core Processor)的基本想法。

4.操作系統內核層對於硬件層來說是硬件接口的使用者,而硬件是接口的定義者,硬件的接口定義決定了操作系統內核,具體來講就是驅動程序如何操作硬件,如何與硬件進行通信。這種接口往往被叫做硬件規格(HardWare Specification),硬件的生產廠商負責提供硬件規格,操作系統和驅動程序的開發者通過閱讀硬件規格所規定的各種硬件編程接口標準來編寫操作系統和驅動程序。

5.早期的計算機中,程序是直接運行在物理內存上的,多個程序運行時也都被分配在物理內存上,這樣的分配策略引發了3個重要問題:程序之間的地址空間不隔離,內存使用效率低,程序運行的地址不確定。解決這幾個問題的思路就是增加中間層,即使用一種間接的地址訪問方法。整個想法是這樣的,我們把程序給出的地址看作是一種虛擬地址(Virtual Address),然後通過某些映射的方法,將這個虛擬地址轉換成實際的物理地址。這樣,只要我們能夠妥善地控制這個虛擬地址到物理地址的映射過程,就可以保證任意一個程序所能夠訪問的物理內存區域跟另外一個程序相互不重疊,以達到地址空間隔離的效果,同時也可以做到每個程序的地址是相同的。而內存使用效率低這個問題人,們很自然地想到了更小粒度的內存分割和映射的方法,使得程序的局部性原理得到充分的利用,大大提高了內存的使用率。這種方法就是分頁(Paging)。

6.虛擬存儲的實現需要依靠硬件的支持,對於不同的CPU來說是不同的,但是幾乎所有的硬件都采用一個叫做MMU(Memory Management Unit)的部件來進行頁映射。一般MMU都集成在CPU內部了,不會以獨立的部件存在。

7.線程(Thread),有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID、當前指令指針(PC),寄存器集合和堆棧組成。通常意義上,一個進程由一個到多個線程組成。各個線程之間共享程序的內存空間(包括代碼段,數據段,堆等)及一些進程級的資源(如打開文件和信號)。

8.一般把頻繁等待的線程稱之為IO密集型線程(IO Bound Thread),把很少等待的線程成為CPU密集型線程(CPU Bound Thread)。IO密集型線程總是比CPU密集型線程容易得到優先級的提升。在線程優先級調度下,存在一種餓死(Starvation)的現象,一個線程被餓死,是說它的優先級較低,在它執行之前,總是有較高優先級的線程試圖執行,因此這個低優先級線程始終無法執行。當一個CPU密集型的線程獲得較高的優先級時,許多低優先級的線程很可能餓死。而一個高優先級的IO密集型線程由於大部分時間都處於等待狀態,因此相對不容易造成其他線程餓死。為了避免餓死現象,調度系統常常會逐步提升那些等待了過長時間的得不到執行的線程的優先級。在這樣的手段下,一個線程只要等待足夠長的時間,其優先級一定會提高到足夠讓它執行的程序。

9.fork產生新任務的速度非常快,因為fork並不復制原任務的內存空間,而是和原任務一起共享一個寫時復制(Copy On Write,COW)的內存空間。所謂寫時復制,指的是兩個任務可以同時自由地讀取內存,單任意一個任務試圖對內存進行修改時,內存就會復制一份提供給修改方單獨使用,以免影響到其他的任務使用。

10.互斥量和二元信號量很類似,資源僅同時允許一個線程訪問,但和信號量不同的是,信號量在整個系統可以被任意線程獲取並釋放,也就是說,同一個信號量可以被系統中的一個線程獲取之後由另一個線程釋放。而互斥量則要求那個線程獲取了互斥量,哪個線程就要負責釋放這個鎖,其他線程越俎代庖去釋放互斥量是無效的。

11.臨界區是比互斥量更加嚴格的同步手段。互斥量和信號量在系統的任何進程都是可見的,也就是說,一個進程創建了一個互斥量或信號量,另一個進程試圖去獲取該鎖是合法的。然而,臨界區的作用範圍僅限於本進程,其他的進程無法獲取該鎖,除此之外,臨界區具有和互斥量相同的性質。

12.一個函數要成為可重入的,必須具有如下幾個特點:
  1.不使用任何(局部)靜態或全局的非const變量;
  2.不返回任何(局部)靜態或全局的非const變量的指針;
  3.僅依賴於調用方提供的參數;
  4.不依賴任何單個資源的鎖(mutex等);
  5.不調用任何不可重入的函數。
  可重入是並發安全的強力保障,一個可重入的函數可以在多線程環境下放心使用。

13.volatile關鍵字試圖阻止過度優化,volatile基本可以做到兩件事情:
  1.阻止編譯器為了提高速度將一個變量緩存到寄存器內而不寫回;
  2.阻止編譯器調整操作volatile變量的指令順序(即使volatile能夠阻止編譯器調整順序,也無法阻止CPU動態調度換序);

14.CPU的亂序執行能力讓我們對多線程的安全保障的努力變得異常困難。因此要保證線程安全,阻止CPU換序是必需的。遺憾的是,現在並不存在可移植的阻止換序的方法。通常情況下是調用CPU提供的一條指令,這條指令常常被稱為barrier。一條barrier指令會阻止CPU將該指令之前的指令交換到barrier之後,反之亦然。換句話說,barrier指令的作用類似於一個攔水壩,阻止換序“穿透”這個大壩。
  關於barrier()宏實際上也是優化屏障:

  #define barrier() __asm__ volatile (”lwsync”)

  CPU越過內存屏障後,將刷新自己對存儲器的緩沖狀態。這條語句實際上不生成任何代碼,但可使gcc在barrier()之後刷新寄存器對變量的分配。

  1.set_mb(),mb(),barrier()函數追蹤到底,就是__asm__ __volatile__("":::"memory"),而這行代碼就是內存屏障;
  2.__asm__用於指示編譯器在此插入匯編語句;
  3.__volatile__用於告訴編譯器,嚴禁將此處的匯編語句與其它的語句重組合優化。

程序員的自我修養-第一章 筆記