1. 程式人生 > >CSAPP之一:程式生命週期漫談

CSAPP之一:程式生命週期漫談

以下內容是基於《深入理解計算機系統》的讀書筆記(第一章 計算機系統漫遊),另有PPT版(有巨集請放心啟用,本博截圖都來自於此),歡迎下載交流。

  這一章通過跟蹤hello程式的生命週期來開始對系統的學習——從它被程式設計師建立,到在系統上執行,輸出訊息,然後終止。先留幾個問題:

  • 編寫的程式經過了怎樣的過程才得以執行?
  • 程式是如何儲存的?
  • 編譯程式有哪些步驟?
  • 程式執行是什麼樣的過程?計算機的各硬體都是什麼角色?
  • 作業系統扮演了什麼樣的角色?

1. 程式表示 資訊就是“位+上下文”

首先隆重請出本章主人公——hello程式,它是以原始碼的形式出場的:

1    #include <stdio.h>
2 3 int main() 4 { 5 printf(“hello, world\n”); 6 }

那麼它儲存到檔案的時候是什麼樣子的呢:

這裡寫圖片描述

如果你手頭上有一個高階文字編輯器,比如notepad++,就可以將這段文字轉換為十六進位制的格式。

源程式實際上是位(bit)序列,每個位要麼是0,要麼是1。8個位組成1個位元組(byte)。每個位元組可以表示一個整數,該整數對應ASCII標準的一個字元,從而構成一個“文字檔案”。

空格和換行符分別用整數32和10表示,十六進位制表示就是20和0A。
  
ASCII碼錶
  
系統中所有的資訊——包括磁碟檔案、儲存器中的程式、儲存器中存放的使用者資料以及網路上傳送的資料,都是由一串位表示的。區分不同資料物件的唯一方法是我們讀到的這些資料物件時的上下文。

比如:位序列00100011,既可以表示數字35,也可以表示字元 ’#’,具體看所處的上下文如何解釋它。

2. 編譯程式 原始碼需要被“翻譯”成計算機能夠執行的指令格式

進入Linux,開啟Terminal,活動活動手指頭吧:

這裡寫圖片描述
  
話說, gcc –o hello hello.c這句話到底做了什麼?

這裡寫圖片描述
   

  1. 預處理階段
    • 前處理器根據已字元#開頭的命令,修改原始的C程式。
    • hello.c的第一行#include <stdio.h>告訴前處理器讀取系統標頭檔案stdio.h的內容,並把它直接插入到程式文字中,形成.i結尾的檔案。
  2. 編譯階段
    • 編譯器將文字檔案hello.i編譯成文字檔案hello.s,它包含一個組合語言程式,每條語句都以一種標準的文字格式確切地描述了一條低階機器語言指令。
    • 組合語言為不同高階語言的不同編譯器提供了通用的輸出語言,例如C編譯器和Fortran編譯器產生的都是同樣的組合語言。
  3. 彙編階段
    • 彙編器將hello.s翻譯成機器語言指令,把這些指令打包成可重定位目標程式,並儲存在目標檔案hello.o中。
  4. 連結階段
    • hello程式呼叫了printf函式,printf函式存在於一個名為printf.o的與編譯好了的目標檔案中,連結器負責將這個檔案以某種形式合併到hello.o程式中,最終形成可執行目標檔案,從而可以被載入到記憶體中由系統執行。

為什麼要了解編譯系統是如何工作的?
1. 優化程式效能
  一個switch語句是否總是比一系列的if-then-else語句高效的多?
  一個函式呼叫的開銷多大?
  while迴圈比for迴圈更有效嗎?
  指標引用比陣列引用更有效嗎?
  為什麼只是簡單地重新排列一下一個算數表示式中的括號就能讓函式執行地更快?
  ……
2. 理解連結時出現的錯誤
  連結器報告它無法解析一個引用是什麼意思?
  靜態變數和全域性變數的區別是什麼?
  在命令列上排列庫的順序有什麼影響?
  為什麼有些連結錯誤到執行時才出現?
  ……
3. 避免安全漏洞
  緩衝區溢位錯誤是如何出現的?
  ……

3. 程式執行處理器讀取並解釋儲存在儲存器中的指令

編譯好的程式執行一下吧:

這裡寫圖片描述

這個過程“hello”君都經歷了什麼?

1. 輸入字串“./hello”後,shell將字元逐一讀入暫存器,再存放到儲存器中。

PPT裡是有動畫的,這裡沒辦法了
  
2. 敲入回車時,shell執行一系列指令來載入可執行檔案hello,將程式碼複製到主存。

利用直接儲存器存取(DMA)技術,資料可以不通過CPU而直接從磁碟到記憶體
  
3. 處理器開始執行hello程式的main主方法中的機器語言指令,這些指令將“hello, world!\n”字串中的位元組從主存複製到暫存器檔案,再從暫存器檔案中複製到顯示裝置。

4. 儲存層次 儲存裝置形成層次結構

快取記憶體至關重要。意識到快取記憶體存在的程式設計師甚至可以利用快取記憶體將他們程式的效能提高一個數量級。

這裡寫圖片描述
  
根據機械原理,較大的儲存裝置要比較小的儲存裝置執行得慢,而快速裝置的造價遠高於低速裝置:

  • 論大小:磁碟比主存大1000倍;論速度:CPU讀取一個字的時間開銷磁碟比主存大1000萬倍。

CPU中的快取記憶體:

  • L1快取記憶體的容量約數十kB(位元組),訪問速度與暫存器檔案一樣快。
  • L2快取記憶體的容量約幾百kB~幾MB,訪問時間比L1約慢5倍,比記憶體快約5~10倍,L2通過一條特殊的匯流排連線到處理器。
  • L1、L2甚至L3是用一種叫做靜態隨機訪問儲存器(SRAM)的硬體技術實現的。

計算機把大量的時間用於不同儲存裝置間的資料複製,因此將各儲存裝置劃分為不同的層次,各層次的資料相應速度和容量大小不同,將資料根據資料量和執行需求置於不同的層次,從而大幅減少時間開銷。
  
這裡寫圖片描述

5. 硬體管理 程式執行過程中作業系統是如何管理硬體的

  作業系統可以看成是應用程式和硬體之間插入的一層軟體,提供程序虛擬儲存器檔案等抽象概念。
  
這裡寫圖片描述

作業系統有兩個基本功能:

  • 防止硬體被失控的應用程式濫用;
  • 嚮應用程式提供簡單一致的機制來控制複雜而又通常大相徑庭的低階硬體裝置。

當Shell載入和執行hello程式,以及hello程式輸出自己的訊息的時候,Shell和hello都沒有直接訪問硬體裝置,而是依靠作業系統提供的服務。

1. 程序

在你邊用Eclipse開發,邊聽歌的時候,作業系統會提供一種假象,好像Eclipse和聽歌軟體都是同時獨立使用硬體一樣。再開啟office、記事本、炒個股,電腦真像三頭六臂一樣。

但是!~~~對於CPU的一個獨立的核心來說,所有的程式都是流水處理的,然後通過不停的切換讓你有了上邊的錯覺。就像小吃攤老闆同時做6份炒餅一個道理。

每個正在執行的程式都抽象為一個程序。同時做6份炒餅的概念叫做併發,過程就像下圖:

這裡寫圖片描述

  作業系統保持跟蹤程序執行所需的所有狀態資訊,這些狀態資訊就是“上下文” (上下文的英文是Context,鑑於實在沒有合適的中文來表述這個詞,建議直接記住英文,只可意會不可言傳,見幾次就知道啥意思了)。
  還是說那做炒餅的師傅,同時做了6份炒餅,有的剛放上油等熗鍋、有的已經9成熟馬上可以出鍋、有的正在燜、有的該放鹽了……他每次從這個鍋到另一個鍋的時候,需要先記下這個鍋的進度,然後到另一個鍋想一想之前該做哪一步了,然後繼續……這就是程序切換,進度其實就是上下文。計算機中程序的切換是一樣道理。

2. 虛擬儲存器

虛擬儲存器是一個抽象的概念,它讓每個程序都覺得是獨佔地使用主存,每個程序看到的是一致的虛擬地址空間,如圖(地址是從下往上增大的):

虛擬儲存器

1. 程式程式碼和資料

對所有的程序來說,程式碼是從同一個固定地址開始的,緊接著是和C全域性變數相對應的資料位置。程式碼和資料區是直接按照可執行目標檔案的內容初始化的。程式碼和資料區是在程序一開始執行時就被規定了大小的。

起始位置:0x08048000(32位) / 0x00400000(64位)

2. 堆

當呼叫malloc和free這樣的函式時,堆可以在執行時動態地擴充套件和收縮。
在學習到如何管理虛擬儲存器時候會詳細研究堆。

3. 共享庫

大約在地址空間的中間位置是一塊用來存放像C標準庫和數學庫這樣共享庫的程式碼和資料的區域。共享庫的概念很強大也很難懂。

在第7章介紹動態連結時會詳細研究。

4. 棧

位於使用者虛擬地址空間的頂部,通常用來實現函式呼叫。在程式執行期間,棧的大小是動態變化的,一般呼叫一個函式時,棧就會增長,從一個函式返回時,棧就會收縮。

在第3章介紹編譯器是如何使用棧的。

虛擬儲存器的運作需要硬體和作業系統軟體之間精密複雜的互動,包括對處理器生成的每個地址的硬體翻譯。其基本思想是把一個程序虛擬儲存器的內容儲存在磁碟上,然後用主存作為磁碟的快取記憶體。

3. 檔案

檔案就是位元組序列,僅此而已。

每個I/O裝置,包括磁碟、鍵盤、顯示器,甚至網路,都可以視為檔案。系統中所有的輸入輸出都是通過使用一小組稱為Unix I/O的系統函式呼叫讀寫檔案來實現的。

它嚮應用程式提供了一個統一的視角,來看待系統中各式各樣的I/O裝置,因此檔案這個簡單而精緻的概念其內涵是非常豐富的。

6. 併發與並行

我們對於計算機的需求無非兩點:做得更多,跑的更快。一個系統如果能夠處理多個活動或請求,這種能力就叫併發,併發通常是“看起來”或“感覺上”同時處理,如果是更加嚴格意義上的“同時”,這種效果就是並行。

1. 執行緒級併發

多處理器/多核:面對現代系統的程式設計師,通常是針對多處理器系統進行開發,多處理器系統就是我們常說的“N核”的系統,每個核有自己的L1和L2快取記憶體,並共享更高層次的儲存,如圖。

這裡寫圖片描述

超執行緒:這是一項允許一個CPU執行多個控制流的技術。超執行緒技術就是利用特殊的硬體指令,把兩個邏輯核心模擬成兩個物理晶片,讓單個處理器都能使用執行緒級平行計算,從而相容多執行緒作業系統和軟體,提高處理器的效能。

舉個栗子:當一個邏輯處理器在執行浮點運算(使用處理器的浮點運算單元)時,另一個邏輯處理器可以執行加法運算(使用處理器的整數運算單元)。這樣做,無疑大大提高了處理器內部處理單元的利用率和相應的資料、指令處吞吐能力。

2. 指令級並行

現代處理器可以同時執行多條指令的屬性就是“指令級並行”。

先舉個栗子:流水線的概念您應該瞭解吧,製造業和服務業中經常見到,比如洗車這件事,通常要經過噴水、打皁、擦洗、上蠟、烘乾這幾個步驟,對於一輛車來說,各個步驟是有先後順序的,而對於不同的待洗車來說,互相是無關的,因此,假設客流比較大,洗車店完全可以同時對不同的車進行不同步驟的服務,從而同時清洗5輛車,這樣就能大大提高效率。

這裡寫圖片描述

再來看處理器的指令級並行,類似地,其實每條指令從開始到結束需要大約20個或更長的週期,但是處理器可以使用非常多的聰明技巧來同時處理多達100條指令。

1  Load  C1 ← 23(R2) 
2  Add   R3 ← R3+1
3  FPAdd C4 ← C4+C3
1  Add   R3 ← R3+1 
2  Add   R4 ← R3+R2 
3  Store R0 ← R4

前三條指令由於互相之間沒有關係,因此並行度為3,也就是可以並行執行;
後三條指令由於依次依賴前一條指令產生的結果,因此並行度為1,也就是必須依次執行。

處理器可以挖掘出並行度較高的指令,並行執行,從而提高吞吐率。

3. 單指令、多資料並行

許多現代處理器擁有特殊的硬體,允許一條指令產生多個可以並行執行的操作,這種方式成為單指令、多資料並行(SIMD)

說白了,就是一箭多雕、一石多鳥。

比如,現在的處理器一般都具有並行地對4對單精度浮點數(float)做加法的指令。

這裡寫圖片描述

7. 總結

計算機系統是由硬體和軟體組成的,它們共同協作以執行應用程式。

計算機內部的資訊被表示為一組一組的位,它們根據上下文有不同的解釋。

程式被編譯系統翻譯成可執行檔案,載入到記憶體後就可以供處理器解釋和執行。

計算機把大量的時間用於不同儲存裝置間的資料複製,因此將各儲存裝置劃分為不同的層次,各層次的資料相應速度和容量大小不同,將資料根據資料量和執行需求置於不同的層次,從而大幅減少時間開銷。

作業系統核心是應用程式和硬體之間的媒介,它提供三個基本的抽象:1)檔案是對I/O裝置的抽象;2)虛擬儲存器是對主存和磁碟的抽象;3)程序是對處理器、主存和I/O裝置的抽象。

現代計算機體系中,併發和並行相關技術將發揮越來越大的作用,能夠基於此進行程式設計和優化的程式設計師將掌握更多主動。

get-set

以上內容是基於《深入理解計算機系統》的讀書筆記(第一章 計算機系統漫遊),另有PPT版(有巨集請放心啟用,本博截圖都來自於此),歡迎下載交流。

相關推薦

CSAPP之一程式生命週期漫談

以下內容是基於《深入理解計算機系統》的讀書筆記(第一章 計算機系統漫遊),另有PPT版(有巨集請放心啟用,本博截圖都來自於此),歡迎下載交流。   這一章通過跟蹤hello程式的生命週期來開始對系統的學習——從它被程式設計師建立,到在系統上執行,輸出訊

淺談小程式生命週期

馬上要做小程式的開發啦,看了一波文件,總結一下。 小程式框架 小程式的框架分為檢視層和邏輯層。邏輯層由js 編寫,檢視層由WXML和WXSS編寫。WXML 用來描述頁面結構,相當於HTML;WXSS用來用來描述頁面樣式,相當於CSS。 小程式啟動後,每個頁面的資料放在data(這個

IIS 7.0 的 ASP.NET 應用程式生命週期概述

    文章:IIS 7.0 的 ASP.NET 應用程式生命週期概述 地址:https://msdn.microsoft.com/zh-cn/library/bb470252(v=vs.100).aspx 本主題介紹在 IIS 7.0 整合模式下執行以及與 IIS 7.0 或更高

程式生命週期分析與註冊流程回撥

從小程式釋出到現在,官方api 變動了好幾個版本 首先我們先看一下小程式的生命週期 app.js 為小程式的啟動入口檔案 onLauch: 小程式初始化回掉,生命週期內只執行一次 onShow: 小程式開啟或者從後臺喚起時的回撥 onHide: 小程式從前臺進入後臺時

Gradle基礎3:生命週期管理

Maven中的生命週期的管理使用了COC,以此為中心的pom.xml檔案成為了重中之重,優點是不同專案之間的經驗共享變得更加容易,大家大部分都是可以使用類似的套路,缺點則是靈活性稍微降低以及對於pom.xml細節的學習需要較多時間。Gradle則將這些再次放開,給更多的許可權與開發者,這

工業物聯網7 生命週期管理(3)

7.3    實施部署 實施部署階段的工作就是,根據論證過的設計,開始搭建解決方案的基礎設施和運營團隊,獲取外部支援,最終產生一個可以執行的交付物。交付物根據方案的型別不同,可能是單項產品、應用系統或者

工業物聯網7 生命週期管理(2)

7.2    設計論證 在明確了,或者說至少是階段性地明確了使用者需求之後,實施團隊就需要設計方案並論證其可行性。設計和論證的依據都是需求分析的結果。這個過程是一個迭代,因為經常在論證的過程中隨著對方案理解的加深而發現之前需求獲取和分析過程中的盲區與錯誤。設計論證的結果就是產

spring原始碼分析spring生命週期

接著上一篇我們看看具體是哪裡的程式碼執行了。 1.初始化BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory);--> PostProcessorRegistrationDelegate.

spring原始碼分析spring生命週期

最近在看springboot自動配置,看到了@Conditional,@ConditionalOnMissingBean等等。這些註解,一直研究他們是如何實現springboot的條件註解的。由他們回到了@Configuration,回到了ConfigurationClassPostPr

微信小程式生命週期研究

本文主要研究 App() 中的幾個函式在生命週期中的表現,以準確把我小程式的行為與狀態。 onLaunch() 小程式初始化完成時呼叫(全域性只觸發一次) 研究發現,這個函式只有在冷啟動的時候會呼叫,符合預期。下面具體看看掃碼開啟小程式: 如果是第一次掃

Java面試經典Servlet生命週期

  只存在一個Servlet物件,在有客戶端請求時才進行初始化,也只初始化一次,在destroy之前,所有的請求不再初始化。初始化完成後呼叫init方法,同初始化一樣,init方法也只調用一次。接下來對於每個請求先呼叫公有的service方法,然後公有的service方法再呼叫私有的service方法,私有的

androidActivity-生命週期

2、可見的生命週期,從onStart()開始到onStop()結束。在這段時間,可以看到Activity在螢幕上,儘管有可能不在前臺,不能和使用者互動。在這兩個介面之間,需要保持顯示給使用者的UI資料和資源等,例如:可以在onStart中註冊一個IntentReceiver來監聽資料變化導致UI的變動,當不再

面試專題之一Fragment的生命週期

面試的時候,如果被面試官問到Fragment的生命週期,想必大家直接脫口而出:onCreate()、onCreateView()、onActivityCreated()、on……,然後就沒有然後了。學習生命週期不是為了應付面試,背個執行順序就完了,要理解每個方法的含義,而它

Android 開發藝術探索筆記之一 -- Android 的生命週期和啟動模式

學習內容: Activity 的生命週期和啟動模式以及 IntentFilter 的匹配規則分析 異常情況下的生命週期 Activity 的啟動模式以及 Flags 隱式啟動下的 Intent 匹配 Activity 的生命週期全面分析

Asp.Net底層解析(四)——應用程式生命週期與HttpModule

    前言:一般ASP.NET開發者對頁面生命週期(PageLife Cycle)是比較熟悉的,在開發ASP.NET應用程式中經常需要從頁面週期的角度去思考問題。實際上在頁面生命週期的背後,還存在著一個不太為人所熟知的更廣義的週期——應用程式生命週期(Applicatio

Android必考面試題之一——Activity的生命週期以及流程圖

生命週期包括幾個方法:onCreate()、onStart()、onResume()、onPause()、onStop()、onDestroy()...... 我們也基本瞭解了Activity生命週期的幾個過程,我們就來說一說這幾個過程。 1.啟動Activity

AndroidActivity——生命週期深入詳解

一、生命週期全面分析Android活動預設執行在當前程序所擁有的棧中,前臺可見的活動則在活動棧的最頂部。其他後臺活動則在棧的裡面,在正常的情況下(記憶體充足)其他的活動並沒有被回收或者殺死,它們仍然存在於棧中保持著原來的狀態。當前面的活動退出後,後面的活動就會搬到前臺使得被使

Android總結篇系列Activity生命週期

Android官方文件和其他不少資料都對Activity生命週期進行了詳細介紹,在結合資料和專案開發過程中遇到的問題,本文將對Activity生命週期進行一次總結。 Activity是由Activity棧進管理,當來到一個新的Activity後,此Activity將被加入到Activity棧頂,之前的A

Android問題Activity生命週期事件

四狀態:活躍、暫停、停止、銷燬。 當Android中Activity在執行的時候,Activity的活動狀態由Android和Activity棧的形式管理。當前活動的Activity位於棧頂。 隨著不同應用的執行,每個Activity都可能在活動狀態和非活動狀態之間切換。

React學習元件生命週期、元件間資料傳遞

注:本篇文章僅供個人日後複習,所以沒什麼乾貨,只起類似“備忘錄”的作用。最近,在看《深入淺出React和Redux》,目前到第二章了,這是本章程式碼:(1)counter.jsimport React, { Component } from 'react'; import P