8086彙編語言學習(一) 8086彙編介紹

1. 學習彙編的心路歷程 

  進行8086彙編的介紹以前,想先分享一下我學習彙編的心路歷程 。java

rocketmq的學習

  其實我並無想到這麼快的就須要進一步學習彙編語言,由於彙編對於個人當前的工做內容來講太過底層。node

  但在幾個月前,當時我正嘗試着閱讀rocketmq的源碼。和許多流行的java中間件、框架同樣,rocketmq底層的網絡通訊也是經過netty實現的。但因爲我對netty並不熟悉,在工做中使用spring-cloud-gateway的時候甚至寫出了一些致使netty內存泄漏的代碼,卻不太明白箇中原理 。出於我我的的習慣,在學習源碼時,拋開總體的程序架構不論,但願至少能對其中涉及到的底層內容有一個大體的掌握,能讓我像黑盒子同樣去看待它們。nginx

  趁熱打鐵,我決定先學習netty,這樣既能在工做時更好的定位、解決netty相關的問題,又能在研究依賴netty的開源項目時更加駕輕就熟。程序員

netty的學習

  隨着對netty學習的深刻,除了感嘆netty統一規整的api接口設計,內部交互靈活可配置、同時又提供了足夠豐富的開箱即用組件外;更進一步的,netty或者說java nio涉及到了許多更底層的東西,例如:io多路複用,零拷貝,事件驅動等等。而這些底層技術在redis,nginx,node-js等以高效率io著稱的應用中被普遍使用。web

  捫心自問,本身在多大程度上理解這些技術?爲何io多路複用在io密集型的應用中,效率可以比之傳統的同步阻塞io顯著提升?一次網絡或磁盤的io傳輸內部到底發生了什麼,零拷貝到底快在了哪裏?redis

  若是沒有很好的弄明白這些問題,那麼個人netty學習將是不完整的。spring

  我有限的知識告訴我,答案就在操做系統中。操做系統做爲軟硬件的大管家,對上提供應用程序接口(程序員們一般使用高級語言提供的api間接調用);對下控制硬件(cpu、內存、磁盤網卡等外設);依賴硬件提供控制併發的系統原語;其牽涉的許多模塊內容都已經獨立發展了(多系統進程間通訊->計算機網絡、文件系統->數據庫)。要想理解現代計算機系統的工做原理,操做系統是絕對繞不開的。數據庫

操做系統的學習

  雖然也上過操做系統的課,讀過幾本操做系統的書。但一方面因爲缺少實際場景的應用,另外一方面也由於當時水平有限,學習操做系統的方式是經過完成一個個孤立簡單的實驗,而不是連貫的實現一個完整的demo操做系統。使得對許多關鍵的知識點的理解依然是模糊不清的,因此也沒法很好地回答上述netty學習中碰到的問題。編程

  在從新學習操做系統的過程當中,除了撿起當初沒有看完的《現代操做系統》外,我驚喜的發現了清華大學的操做系統公開課(本身動手實現ucore操做系統),以及《OrangeOS 一個操做系統的實現》。api

  但操做系統的學習從一開始我就遇到了大問題,從零開始實現的操做系統,雖然內核主體是C語言實現的,但在CPU加電開機時的引導程序以及在特定平臺上操做特定硬件的功能卻都須要經過彙編來實現(ucore和OrangeOS都是基於Intel-80386的(32位)),看的我是一頭霧水,很是鬱悶。

彙編語言的學習

  因爲在學校裏學習的彙編語言是囫圇吞棗的,沒法支持繼續操做系統的學習實踐。我找到了王爽老師編寫的《彙編語言》進行學習,雖然《彙編語言》使用的是更早的基於Intel-8086(16位)機器的彙編語言進行講解,但因爲Intel的CPU迭代是向前兼容的(x86體系),所以其知識也可以適用於更先進的Intel-80386。

  對於像我這樣的彙編語言初學者,學習簡單經典的8086彙編可以爲理解更復雜的彙編語言打下基礎。經過《彙編語言》這本書的學習,加深了我對諸如內存尋址,中斷,指令跳轉等硬件工做原理的理解,可以讓我從更底層的角度去看待上層的一些技術。

  這一段時間,我進行了相似遞歸的,由上層至底層的學習。在初步完成了8086彙編語言的學習後,我準備返回上層繼續操做系統的學習。

  經過寫博客的方式來鞏固這段時間彙編語言學習總結的成果,對知識點查漏補缺的同時也能做爲彙編語言知識體系的索引讓之後在有須要時能更好的進行回顧。若是能幫助到一樣感興趣的人就更好了( ^_^ )。

2.彙編語言基本介紹 

  彙編語言做爲編程語言的一種,雖然貼近機器底層,但和咱們熟悉的高級編程語言依然有諸多共通之處。站在更高的角度去看待彙編語言,能更好的去理解彙編。

  編程語言一般由兩部分組成:編程語言的基礎語法以及操縱編程語言所處環境的api

舉個例子:

  對於java,其基礎語法部分包括變量/方法/類定義、循環/賦值、繼承多態等;同時java做爲一門面向通用計算機的編程語言,一般直接運行在操做系統之上,其多線程、系統io、網絡傳輸、圖形編程等api使得java開發人員可以更簡單的使用操做系統。

  對於javaScript,基礎語法部分由EcmaScript規範構成;而javaScript做爲運行在web瀏覽器環境中的語言,提供了操做BOMDOM對象的api,使得js的開發人員能控制瀏覽器的行爲,實現所須要的功能。

對於彙編語言,狀況又是什麼呢?

  一方面,彙編語言的語法部分大體包括指令的格式,註釋,定義數據、代碼段等的僞指令等。

  另外一方面,彙編語言是面向CPU硬件編程的,其指令與最終的機器碼一一對應,但比起以二進制表示的機器碼可讀性要高不少。

  舉個例子,機器指令:1000100111011000 其對應的彙編語言表示爲:mov ax bx,表示將寄存器bx的值送入寄存器ax。對比一下,表示一樣的內容,彙編語言的可讀性比機器語言要高不少。而機器最終執行的是機器碼,須要由彙編器將彙編源程序轉換成最終的機器碼。

  彙編語言提供了直接操做CPU寄存器的指令(各類寄存器的取值、賦值)、控制CPU內存尋址的指令(內存單元的取值、賦值)、控制CPU經過端口操做外設的指令以及控制CPU進行程序跳轉的指令等等。

  8086彙編的語法和硬件指令的內容會在後續的博客中,進行更加詳細的說明。

3.8086硬件介紹

  彙編語言是用來操做CPU硬件的,彙編語言與其對應的硬件緊密相關。所以,在學習8086彙編語言以前,咱們須要先大體瞭解一下8086硬件的工做原理(以黑盒子的角度來看待,而不是去深刻的研究硬件內部複雜的結構)。

3.1 CPU寄存器

  CPU一般由運算器,控制器和寄存器組成;運算器和控制器的工做通常沒法直接控制,但寄存器卻可以經過彙編語言直接與之交互。

  8086CPU中有14個寄存器,各自都有着特殊的功能,咱們能夠經過彙編語言將其協調起來,知足咱們的需求

寄存器能夠分爲三大類,分別是:

通用寄存器 段寄存器 特殊功能寄存器
ax  accumulate-register  累加寄存器 cs  code-segment  代碼段寄存器 si  source-index  源變址寄存器
bx  based-register  基地址寄存器 ds  data-egment   數據段寄存器 di  destination-index  目的變址寄存器
cx  count-register  計數寄存器 ss  stack-segment  棧段寄存器

sp  stack-point  堆棧指針寄存器

dx  data-register  數據寄存器 es  extra-segment  附加段寄存器 bp  base-point  基礎指針寄存器
    ip  instructor-point  指令指針寄存器
    psw  program-state-word  程序狀態字寄存器

  千萬別一會兒被繁多的寄存器弄糊塗了,後續會在有須要時進行上述寄存器的詳細介紹和用法的。

3.2 CPU和存儲器的交互

  在計算機中,CPU做爲處理器一般不能獨自進行工做,還須要與外部存儲器(內存 RAM、ROM)進行交互來讀寫所須要執行的代碼指令或數據。

  8086 CPU經過邏輯上分爲三類的地址總線數據總線控制總線共同完成與存儲器交互的任務,。

  總線由一系列的導線組成,一般高電平表示1,低電平表示0,數量爲N的總線集合能夠表示一個N位的二進制數

  其中地址總線用於肯定存儲器的地址,數據總線用於在對應存儲器地址和寄存器之間傳輸數據,而控制總線則能夠標識當前所進行的控制操做(讀或是寫或是其它指令)。

地址總線:

  存儲器在設計時,被劃分爲多個存儲單元,每一個存儲單元都有獨一無二的地址標識。CPU能夠經過這些地址標識來定位對應的存儲單元,這叫作內存尋址

  CPU的內存尋址範圍由地址總線的根數(位數)決定,20位的地址總線能尋址的最大範圍爲(2 ^ 20)b = 1M;而32位的地址總線能尋址的最大範圍爲(4 * (2 ^ 30))b = 4G,這也是在32位CPU時代,PC的內存廣泛是4G的主要緣由。

數據總線:

  CPU與內存或其它器件的數據傳輸是經過數據總線來完成的。數據總線的位數決定了一次數據傳輸的數據大小,數據總線的位數越多,數據傳輸的效率就越高。

  8086做爲一個16位的CPU,內部寄存器是16位的,其數據總線也是16位的,其一次能夠傳輸一個16位的二進制數據。

控制總線:

  CPU經過控制總線來對外部設備實施控制。和前兩種總線不一樣的是,控制總線是不一樣的控制線的總集合,其中的每一根導線一般是單獨提供控制的。CPU經過控制總線發送控制信號和時許信號來對外圍設備進行控制(讀、寫信號等)或者從控制總線接收外圍設備發出的通知(中斷申請信號 等)。

以8086 CPU從指定內存地址中讀取數據爲例簡單說明CPU總線的工做原理:

  首先,CPU經過地址總線發送內存地址選取信號。

  而後,CPU經過控制總線發送"讀"信號通知內存芯片將要讀取數據,而具體被選中的內存芯片由地址總線信號指定。  

  最後,CPU經過數據總線將對應內存單元中的數據送入CPU中。

  這裏工做原理的解釋很模糊,但大體說明了CPU經過總線與外部存儲器交互的方式。

  (圖片源自 《彙編語言》 王爽著)

3.3 內存單元物理地址

  前面提到每一個存儲器單元都有惟一的標識,這個惟一標識被稱爲物理地址。

  8086的地址總線是20位的,擁有1MB的內存尋址能力。但8086的寄存器卻只有16位,單次處理的數據最多也是16位,若是簡單的將尋址地址送出,那麼最多隻能尋址2的16次方,也就是64KB的地址空間。

  爲此,8086內部經過將兩個16位的尋址地址疊加爲一個20位地址的方式實現物理地址尋址。

  其中一個尋址地址稱爲段地址,另外一個尋址地址被稱爲偏移地址;16位的段地址左移4位(擴大16倍)後將其和偏移地址相加獲得最終的物理尋址地址。

舉個例子:

  段地址 = 1234 (16進制 0x1024) 

  偏移地址 = 1000 (16進制 0x1000)

  最終物理地址 = (段地址 * 16) + (偏移地址) = 13340 (16進制 0x13340) 

  這裏引入了內存段的概念,"段"這一律念在8086彙編中很是重要,從寄存器中專門存在一類段寄存器可見一斑,這裏就不繼續展開了。

  (圖片源自 《彙編語言》 王爽著)

3.4 CPU執行程序的基本過程

  相信不少人都多少對CPU執行程序的原理感到好奇。對於日常再熟悉不過的程序中的if、else邏輯判斷,for、while循環以及函數的調用(call)、返回(return)機制在以圖靈機爲模型的機器中是如何實現的呢?在存儲器中數據都是以010101的二進制形式存在的,但是CPU是如何區分在程序中一般是涇渭分明的代碼和數據的呢?換句話說,CPU是如何知道應該把0101這樣的的二進制"數據"當作代碼執行仍是視做數據處理呢?

  想知道答案,須要先了解一下前面提到的8086寄存器中的CS(代碼段寄存器)IP(指令指針寄存器)這兩個或許是最重要的寄存器了,CS/IP兩個寄存器

  1. CPU在每次執行指令時,都會去讀取CS:IP所指向內存單元的"數據",將其當作指令來執行。(CS : IP 其中前面的CS表明段地址,後面的IP表明偏移地址)。

  2. 在指令執行完畢後,IP值會增長,增長的值取決於以前加載指令的長度(8086的指令通常須要1-3個字節),這樣CS:IP就能正確的指向下一條須要執行的指令了。

  3. CPU會不斷的重複執行(1)、(2)這兩個步。CPU能以很是快的速度執行這一運算過程,這通常取決於CPU的主頻。

  程序一般都不是線性的、自始至終從上至下執行的,而是存在各類分支判斷來決定最終執行的程序片斷。爲此,CPU提供了許多指令讓咱們可以修改CS和IP寄存器中的值(例如jmpcallret指令等),這類指令被統稱爲跳轉指令。有了跳轉指令,就能夠在實現邏輯分支的跳轉、循環以及函數子程序的調用,返回等功能。

  上述解釋依然是很簡陋、不徹底的,諸如如何實現函數返回時參數的傳遞、返回後以前變量的恢復等等更細節的問題都尚未給出答案。限於篇幅不會在這裏回答這些問題。咱們能夠帶着這些問題進行接下來的學習,隨着學習的深刻,相信這些問題的答案會慢慢浮出水面。就我我的而言,若是帶着問題去學習,會更加興致高昂,經過努力將感興趣卻還不理解的地方弄懂是一件頗有成就感的事情。

總結

  做爲8086彙編語言學習的第一篇博客,這裏僅僅把學習8086彙編所須要的部分基礎知識走馬觀花的簡單介紹了一下,不少知識點只起了個頭就沒後續了,會在後續的博客裏繼續分享8086學習的內容。

  做爲彙編語言的初學者,博客中存在理解有問題的地方還請多多指教。但願對彙編語言或是計算機底層原理感興趣的小夥伴有所幫助。

相關文章
相關標籤/搜索