【重磅升級!】寫給前端非科班的「計算機組成原理」!!!

以前寫過一篇相似的文章( 非科班前端注意了! 計算機組成原理知識已送到你嘴邊! ),可是已是1年前的事了,今年我以爲本身又成長了不少,再次總結一次,其內容豐富程度遠超上篇文章!廢話很少說!上車吧!javascript

前言 - 計算機基礎到底有多重要

就拿咱們立刻要講的計算機組成原理來講,咱們立刻的迭代任務,有一個同窗的任務是寫相似javascript計算器,而且帶有不少特定業務的公式計算任務,其實這個任務有不少細節問題,須要懂javascript的數字基本原理才行,好比說:javascript使用的 IEEE 754標準的64位浮點數,那麼64位浮點數天生就會有一些問題出來:css

  • 64位浮點數的支持的最大整數是多少?這個須要跟產品說清楚,爲啥你支持不了更大的整數運算,其實也就是javascript支持的最大整數,是2的53次方減一,爲啥呢?不學計算機組成原理是理解不了的
  • JavaScript中常被詬病的0.3 - 0.2 == 0.1緣由是什麼?這個須要跟產品說清楚,精度問題怎麼處理, 不瞭解小數在浮點數中的表示和如何轉化10進制是不理解的
  • JavaScript的數字小數位有多少位,爲何是這麼多位,也要跟產品說清楚

計算機的簡略發展史

你們別小看這個發展史,對咱們來講也很重要,好比後面講到I/O設備,其實I/O設備的演進就是計算機發展史的一個縮影,對咱們理解I/O設備的演進是頗有幫助的,你是在一個大的框架下理解一些更細節的概念。前端

電子管時代

  • 當時的背景:爲了軍方的計算要求,好比彈道軌跡計算,人們的須要一個能代替人腦的計算裝置。
  • 具體的產出:第一臺電子數字計算機:ENIAC(1946)當時就這這個背景下產生
    • 大概是如何進行計算的呢?當時的計算機會有不少邏輯處理元件,它們在高低電壓(能夠表示01的二進制)下用線路鏈接起來實現計算的功能。java

    • 當時的主要邏輯元件是電子管,電子管有咱們半個手掌那麼大,同時也意味着這個機器的體積是很大的,電子管耗電,計算機的耗電量也很高,並且此時的計算機只能識別0101的二進制數,因此只能用機器語言來編程,此時程序員編程是在一個紙袋上的,以下圖,有孔表明0,沒孔表明1webpack

image.png

  • 產生的問題:耗電量高,體積大,而且啓動和關閉時經常會有邏輯元件損壞,穩定性極差

晶體管時代

以下圖:最右邊的是電子管,挨着的是晶體管和集成電路,咱們能夠看到晶體管比電子管小不少 image.pngcss3

  • 當時的背景:但願計算機體積、耗電量、計算能力等方面比上個時代更出色
  • 具體的產出:晶體管的電氣特性能夠替代電子管,並且晶體管的體積比電子管小不少,這也意味着此時的計算機要小不少
    • 而且出現了面向過程的程序設計語言和操做系統的雛形,製造一臺計算機大概須要幾萬到幾十萬的晶體管,而且須要用手工的方式把晶體管焊接到電路板上,就很是容易出錯

中小規模集成電路時代

  • 具體的產出:集成電路的技術讓咱們計算機變得愈來愈小,同時功耗更低,可靠性也比手動焊接的晶體管更高,此時的計算機主要用於科學計算,一些高級語言同時產生,並出現分時操做系統

超大規模集成電路時代

  • 隨着集成電路工藝的不斷提高,出現了大規模和超大規模集成電路,此時開始出現微處理和微型計算機,也就是咱們如今家用的計算機,就拿蘋果的A13處理器來講,每個邏輯元件在其中不超過7納米,一個指甲蓋大小的cpu就集成了85億個晶體管。

image.png

計算機硬件的基本組成

馮諾依曼體系

  • 由於早期的計算機好比ENIAC,每一步的計算,須要執行的指令都須要程序員手動去操做,也就是手工就浪費了大量的時間c++

  • 爲了解決這個問題,馮諾依曼就提出了存儲程序的概念,就是指,將指令以二進制代碼的形式事先輸入到計算機的內存裏,而後內存根據裏面存儲的指令從首地址也就是第一條指令開始按順序一條一條的執行,直到程序執行結束,這種自動執行的機制比人工操做使計算機的計算效率大大提高程序員

  • 馮諾依曼體系是以運算器爲核心的,咱們現代的計算機是以存儲器爲核心,咱們這裏瞭解馮諾依曼體系如何以運算器爲核心的價值不大,因此就直接介紹以存儲器爲核心的現代計算機涉及的部件吧web

首先,計算機最基本的5大組成部分以下圖,分別爲:輸入設備(好比鍵盤), 存儲器(好比內存), 運算器(cpu), 控制器(cpu), 輸出設備(顯示器),咱們看一下這些基本的硬件設備是如何處理數據的面試

image.png

上圖的實線是數據線,是數據交換的通路,虛線是控制線和反饋線,是傳遞命令的通路

  • 首先咱們的數據經過輸入設備會被加工程計算機可以識別的0101的形式,咱們直接輸入的代碼計算機是不認識的。

  • 而後通過輸入設備處理的數據,先存到了存儲器裏(控制器控制輸入設備),存儲器能夠存放數據和程序指令

  • 而後控制器能夠直接從存儲器裏取得所須要執行的程序指令,取得指令後,控制器會分析指令要作什麼(指令分爲操做碼地址碼),分析的就是操做碼,到底要幹嗎

  • 假設分析出來是讀取數據的操做,也就是從存儲器中取一個數據給運算器,那麼讀取數據的地址就在寫在地址碼裏面,這時運算器就去就告訴存儲器要取數據的地址,而後存儲器直接把數據傳遞給運算器

  • 最後運算結束,運算結果會返回存儲器,存儲器能夠直接把結果返回給輸出設備(在控制器的控制下)

  • 最後輸出設備,好比顯示器上就看到咱們想要的數據

接下來又是乾巴巴的文字,太枯燥了,休息5分鐘,咱們先吃個雞腿,繼續吧!

image.png

上面是基本的計算機運算的過程,咱們拿一個實際的javascript代碼來舉例:

假設在咱們的JS代碼裏,運行代碼 let a = 1 + 1,此時上述的5大計算機部件如何處理的呢? `

  • 首先鍵盤輸入代碼let a = 1 + 1將被解析爲2進制代碼,在控制器的控制下放入了內存

  • 而後內存存儲完畢, CPU的控制器開始從內存裏取出指令,分析出指令是一個加法操做(先讓 1+1運算,後面纔會把1+1運算的結果賦值給變量a)

  • 而後控制器控制運算器,運算器直接從內存裏取出數據兩個1,作一個加法運算得出結果,並返回給存儲器,存儲到一個內存的地址裏

  • 而後控制器接着執行第二條指令(let a = 2),由於以前2已經被算出來了,第二條指令是賦值操做了(把1+1的值賦給變量aa其實就是一個內存地址而已)

  • 此時CPU的控制將控制CPU的運算器作1+1的加法運算,並得出結果2

  • 最後執行指令完畢,若是咱們要打印console.log(a)的話,a由於本質上是一個內存地址,cpu會根據內存地址,找到這個地址裏存放的值,也就是console.log顯示的值

  • 獲取到要顯示的值後,存儲器直接將數據傳給顯示器,這樣咱們就能夠在屏幕上看到2這個結果了

計算機編程語言

經過下圖,咱們簡單介紹一下相似javascript、Python這種解釋型語言和c, c++這種編譯型語言的區別。理解爲何解釋性語言一般都比編譯型語言運算速度慢。

image.png

高級語言通常有兩種方式轉換爲機器語言

  • 一種是直接藉助編譯器,將高級語言轉換爲二進制代碼,好比c,這樣c運行起來就特別快,由於編譯後是機器語言,直接就能在系統上跑,如上圖,但問題是,編譯的速度可能會比較慢。
  • 一種是解釋性的,好比 js,是將代碼翻譯一行成機器語言(中間可能會先翻譯爲彙編代碼或者字節碼),解釋一行,執行一行

須要注意的是,按照第一種將大量的高級代碼翻譯爲機器語言,這其中就有很大的空間給編譯器作代碼優化,解釋性語言就很難作這種優化,可是在v8引擎中,js仍是要被優化的,在編譯階段(代碼分編譯執行兩個階段)會對代碼作一些優化,編譯後當即執行的方式一般被稱爲 JIT (Just In Time) Comipler

進位計數制(重點)

這章主要介紹進制轉換,好比10進制轉2進制怎麼轉,2進制轉10進制怎麼轉。

掌握這些事必要的,好比leetcode有一道簡單題叫excel序號,本質就是26進制轉10進制,不瞭解進制轉換就不容易作出來這道題。

任意進制如何轉化爲十進制

例如2進制101.1如何轉化爲10進制。(有些同窗以爲能夠用parseInt('101.1', 2),這個是不行的,由於parseInt返回整數)

轉化方法以下(按權相加法): 2進制的 101.1 = 1 x 22 + 0 x 21 + 1 x 20 + 1 x 2-1

規律就是二進制的每一個數去乘以2的相應次方,注意小數點後是乘以它的負相應次方

image.png

到這裏我出一個思考題,unicode碼,第一個平面的也就是能包含字符範圍是 0000 - FFFF(16進制),請問16進制的FFFF是10進制的多少?

十進制整數轉爲任意進制

方法是除商取餘法:好比10進制轉2進制

例如: 把89化爲二進制的數

把89化爲二進制的數

89÷2=44 餘1

44÷2=22 餘0

22÷2=11 餘0

11÷2=5 餘1

5÷2=2 餘1

2÷2=1 餘0

1÷2=0 餘1

而後把餘數由下往上排序

1011001

這樣就把89化爲二進制的數了

image.png

十進制小數轉爲n進制

咱們仍是以2進製爲例,方式是採用「乘2取整,順序排列」法。具體作法是:

  • 用2乘十進制小數,能夠獲得積,將積的整數部分取出-
  • 再用2乘餘下的小數部分,又獲得一個積,再將積的整數部分取出-
  • 如此進行,直到積中的小數部分爲零,或者達到所要求的精度爲止

因此n進制是一個道理

咱們具體舉一個例子

如: 十進制 0.25 轉爲二進制

  • 0.25 * 2 = 0.5 取出整數部分:0
  • 0.5 * 2 = 1.0 取出整數部分1

即十進制0.25的二進制爲 0.01 ( 第一次所獲得爲最高位,最後一次獲得爲最低位)

此時咱們能夠試試十進制0.10.2如何轉爲二進制,就知道爲啥0.1 + 0.2不等於0.3了

0.1(十進制) = 0.0001100110011001(二進制)
十進制數0.1轉二進制計算過程:
0.1*20.2……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.2」接着計算。
0.2*20.4……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.4」接着計算。
0.4*20.8……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.8」接着計算。
0.8*21.6……1——整數部分爲「1」。整數部分「1」清零後爲「0」,用「0.6」接着計算。
0.6*21.2……1——整數部分爲「1」。整數部分「1」清零後爲「0」,用「0.2」接着計算。
0.2*20.4……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.4」接着計算。
0.4*20.8……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.8」接着計算。
0.8*21.6……1——整數部分爲「1」。整數部分「1」清零後爲「0」,用「0.6」接着計算。
0.6*21.2……1——整數部分爲「1」。整數部分「1」清零後爲「0」,用「0.2」接着計算。
0.2*20.4……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.4」接着計算。
0.4*20.8……0——整數部分爲「0」。整數部分「0」清零後爲「0」,用「0.2」接着計算。
0.8*21.6……1——整數部分爲「1」。整數部分「1」清零後爲「0」,用「0.2」接着計算。
……
……
因此,獲得的整數依次是:「0」,「0」,「0」,「1」,「1」,「0」,「0」,「1」,「1」,「0」,「0」,「1」……。
由此,你們確定能看出來,整數部分出現了無限循環。
複製代碼

接下來看0.2

0.2化二進制是
0.2*2=0.4,整數位爲0
0.4*2=0.8,整數位爲0
0.8*2=1.6,整數位爲1,去掉整數位得0.6
0.6*2=1.2,整數位爲1,去掉整數位得0.2
0.2*2=0.4,整數位爲0
0.4*2=0.8.整數位爲0
就這樣推下去!小數*2整,一直下去就行
這個數整不斷
0.0011001
複製代碼

因此0.10.2都沒法完美轉化爲二進制,因此它們相加固然不是0.3

真值和機器數

例如:

+15 => 01111(2進制)

-8 => 11000(2進制)

真值是咱們平時生活中用到的數字形式,好比+15,-8,機器數是存到機器裏的形式,也就是2進制的形式,其中01111,第一個0是表明正數的意思,1111是保存的數值,轉換成10進制就是15

因此合起來就是+15

字符編碼

字節

  • 計算機內部,全部信息最終都是一個二進制值
  • 每個二進制位(bit)有0和1兩種狀態,所以八個二進制位就能夠組合出256種狀態,這被稱爲一個字節(byte)

單位

  • 8位 = 1字節
  • 1024字節 = 1K
  • 1024K = 1M
  • 1024M = 1G
  • 1024G = 1T

JavaScript中的進制

進製表示

let a = 0b10100;//二進制
let b = 0o24;//八進制
let c = 20;//十進制
let d = 0x14;//十六進制
console.log(a == b);
console.log(b == c);
console.log(c == d);
複製代碼

進制轉換

  • 10進制轉任意進制 10進制數.toString(目標進制)

    console.log(c.toString(2));
    複製代碼
    複製代碼
  • 任意進制轉十進制 parseInt('任意進制字符串', 原始進制),小數部分會被截斷;

    console.log(parseInt('10100', 2));
    複製代碼
    複製代碼

ASCII

最開始計算機只在美國用,八位的字節能夠組合出256種不一樣狀態。0-32種狀態規定了特殊用途,一旦終端、打印機趕上約定好的這些字節被傳過來時,就要作一些約定的動做,如:

  • 趕上0×10, 終端就換行;
  • 趕上0×07, 終端就向人們嘟嘟叫;

又把全部的空格、標點符號、數字、大小寫字母分別用連續的字節狀態表示,一直編到了第 127 號,這樣計算機就能夠用不一樣字節來存儲英語的文字了

這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的一位統一規定爲0

image.png

這個方案叫作 ASCII 編碼

GB2312

後來西歐一些國家用的不是英文,它們的字母在ASCII裏沒有爲了能夠保存他們的文字,他們使用127號這後的空位來保存新的字母,一直編到了最後一位255。好比法語中的é的編碼爲130。固然了不一樣國家表示的符號也不同,好比,130在法語編碼中表明瞭é,在希伯來語編碼中卻表明了字母Gimel (ג)。

從128 到 255 這一頁的字符集被稱爲擴展字符集。

中國爲了表示漢字,把127號以後的符號取消了,規定

  • 一個小於127的字符的意義與原來相同,但兩個大於 127 的字符連在一塊兒時,就表示一個漢字;
  • 前面的一個字節(他稱之爲高字節)從0xA1用到0xF7,後面一個字節(低字節)從 0xA10xFE
  • 這樣咱們就能夠組合出大約7000多個(247-161)*(254-161)=(7998)簡體漢字了。
  • 還把數學符號、日文假名和ASCII裏原來就有的數字、標點和字母都從新編成兩個字長的編碼。這就是全角字符,127如下那些就叫半角字符。
  • 把這種漢字方案叫作 GB2312。GB2312 是對 ASCII 的中文擴展

GBK

後來仍是不夠用,因而乾脆再也不要求低字節必定是 127 號以後的內碼,只要第一個字節是大於 127 就固定表示這是一個漢字的開始,又增長了近 20000 個新的漢字(包括繁體字)和符號。

各個國家都像中國這樣搞出一套本身的編碼標準,結果互相之間誰也不懂誰的編碼,誰也不支持別人的編碼

Unicode

ISO 的國際組織廢了全部的地區性編碼方案,從新搞一個包括了地球上全部文化、全部字母和符 的編碼! Unicode 固然是一個很大的集合,如今的規模能夠容納100多萬個符號。

  • International Organization for Standardization:國際標準化組織。
  • Universal Multiple-Octet Coded Character Set,簡稱 UCS,俗稱 Unicode

ISO 就直接規定必須用兩個字節,也就是 16 位來統一表示全部的字符,對於 ASCII 裏的那些 半角字符,Unicode 保持其原編碼不變,只是將其長度由原來的 8 位擴展爲16 位,而其餘文化和語言的字符則所有從新統一編碼。

從 Unicode 開始,不管是半角的英文字母,仍是全角的漢字,它們都是統一的一個字符!同時,也都是統一的 兩個字節

  • 字節是一個8位的物理存貯單元,
  • 而字符則是一個文化相關的符號。

平面(Plane)

Unicode 使用的數字是從 0 到 0x10ffff,這些數字都對有相對應的字符(固然,有的尚未編好,有的用做私人自定義)。每個數字,就是一個代碼點(Code Point)。

這些代碼點,分爲 17 個平面(Plane)。其實就是17 組,只是名字高大上而已

image.png

Plane 3 到 Plane 14 尚未使用,TIP(Plane 3) 準備用來映射甲骨文、金文、小篆等表意文字。PUA-A, PUA-B 爲私人使用區,是用來給你們本身玩兒的——存儲自定義的一些字符。

Plane 0,習慣上稱做基本平面(Basic Plane);剩餘的稱做擴展平面(Supplementary Plane)。

utf 32

UTF-32 使用四個字節來表示存儲代碼點:把代碼點轉換爲 32 位二進制,位數不夠的左邊充 0。

4個字節就是4 * 8 = 32位,就能表示2的32次方個數字,這些數字能夠對應2的32次方個字符,但其實咱們經常使用的是 0 - 2的16次方的字符,能夠看到utf32編碼特別浪費空間

utf 16

UTF-16 用二個字節來表示基本平面,用四個字節來表示擴展平面。也就是說,UTF-16的編碼長度要麼是2個字節(U+0000到U+FFFF),要麼是4個字節(U+010000到U+10FFFF)

UTF-8

UTF-8是一種變長的編碼方法,字符長度從1個字節到4個字節不等。 越是經常使用的字符,字節越短,最前面的128個字符,只使用1個字節表示,與ASCII碼徹底相同。

編號範圍 字節
0x0000 - 0x007F 1
0x0080 - 0x07FF 2
0x0800 - 0xFFFF 3
0x010000 - 0x10FFFF 4

中文在unicode裏面的範圍

4E00~9FA5 中日韓統一表意文字
2E80-A4CF 中日朝部首補充、康熙部首、表意文字描述符、中日朝符號和標點、日文平假名、 日文片假名、注音字母、諺文兼容字母、象形字註釋標誌、注音字母擴展、 中日朝筆畫、日文片假名語音擴展、帶圈中日朝字母和月份、中日朝兼容、 中日朝統一表意文字擴展A、易經六十四卦符號、 中日韓統一表意文字、彝文音節、彝文字根
F900-FAFF 中日朝兼容表意文字
FE30-FE4F 中日朝兼容形式
FF00-FFEF 全角ASCII、全角中英文標點、半寬片假名、半寬平假名、半寬韓文字母

通常用4E00-9FA5已經能夠,若是要更廣,則用2E80-A4CF || F900-FAFF || FE30-FE4F

看到了吧,4E00-9FA5 就是通常正則表達式匹配中文的範圍。爲何來的,這下知道原理了吧

在javascript中,如何轉utf8呢?

可使用encodeURIComponent

encodeURIComponent('張')
"%E5%BC%A0"
複製代碼

並且,平時咱們說中文是兩個字節表示的,這個是錯誤的,幾個字節表示徹底是看編碼,好比utf8utf16有可能一樣的unicode碼,編碼出來的字節數是不同的。

咱們平時的頁面都是utf8編碼的,其實在底層2進制上,中文一般是3個字節表示的。

JavaScript 如何在內部使用 Unicode

雖然 JavaScript 源文件能夠有任何類型的編碼,但 JavaScript 會在執行以前在內部將其轉換爲 UTF-16。

JavaScript 字符串都是 UTF-16 序列,正如 ECMAScript 標準所說:

當 String 包含實際文本數據時,每一個元素都被視爲單個 UTF-16 代碼單元

定點數(重點)

定點數和浮點數

image.png

無符號數

就是整個機器字長(機器字長是指計算機進行一次整數運算所能處理的二進制數據的位數,好比咱們常說32位機器,64位機器)的所有二進制位均爲數值位,沒有符號位,至關於都是正數。

好比8位無符號整數的範圍就是 二進制: 00000000 - 11111111

轉化爲10進制就是0 - 255

注意咱們說的無符號數都是針對整數,沒有小數

有符號數的定點表示

先來看看定點整數和定點小數如何在計算機裏表示。

屏幕快照 2021-07-30 下午2.14.24.png

  • 定點整數:符號位在第一位,一般0表示正數,1表示負數,小數點默認在最後一位,是隱藏的
  • 定點小數:符號位在第一位,一般0表示正數,1表示負數,小數點隱藏在符號位後面,小數的數值部分也能夠叫尾數,這個咱們在浮點數介紹的時候會出現這個名詞(數值部分 = 尾數)

定點數整數和小數均可以用原碼,反碼,補碼錶示,整數還能夠用移碼錶示,具體什麼意思咱們稍後介紹。

原碼

原碼就是用尾數表示真值的絕對值,符號位0表示正數,1表示負數,假設咱們機器字長爲8位

咱們拿 +19和 -19來解釋一下

屏幕快照 2021-07-30 下午3.14.10.png

+19表示爲:0,0010011 -19表示爲: 1,0010011

下面是定點小數的表示,同理

屏幕快照 2021-07-30 下午3.13.58.png

反碼

若符號位爲0,則反碼和原碼一致

若符號位爲1,則數值位所有取反

補碼

補碼分爲:

正數的補碼 = 原碼

負數的補碼 = 反碼末尾 + 1

移碼

補碼的基礎上符號位取反,只能表示整數。爲何須要移碼,移碼能夠很是方便的判斷兩個數的大小。以下圖:

咱們會發現移碼從左往右,只要先有1就更大,若是都有1,就日後面比,先出來1的就更大

屏幕快照 2021-07-30 下午3.39.25.png

爲何須要這些什麼補碼移碼

爲何原碼有問題呢,好比咱們作一個運算 14 + (-14)

按道理應該等於0,可是咱們把它們轉爲2進制,定點數的加法就出現問題了,竟然不等於0,以下圖

屏幕快照 2021-07-30 下午3.28.01.png

那該怎麼辦呢,原碼的加法須要變爲減法也就是14-14,這樣就對了,可是這意味咱們的計算機既要設計一個加法器又要設計一個減法器,減法器的複雜度是很高的,爲了方便運算,一些聰明的人實現了讓加法代替減法,這就須要咱們以前講的補碼知識了。

14 + (-14)怎麼才能計算正確呢?

咱們可讓14的原碼 加上 -14的補碼,這時候就是

00001110 + 11110010(這個是-14的補碼) = 100000000,由於機器字長是8位,也就是最多容納8位2進制,最左邊的1會被機器自然丟棄,這樣最終結果就是00000000.

浮點數

爲何須要浮點數,主要是定點數對於很大的數字是特別浪費空間的,舉個例子,好比說浮點數1.2 x 10的20次方,咱們知道是10進制,就只須要存1.2和20這些數據就能表示這個數,可是定點數一個數字佔一個坑,確定沒有浮點數在更小的空間表示更大的數。

咱們舉一個例子來理解浮點數的表示,好比數字+302657264526,這是定點整數的表示方法,若是是科學計數法,咱們表示爲:+3.026 * 1011 ,而其中的10是否是固定不變的呢,因此若是要保存這個科學技術法表示的數字,咱們能夠不看10這個基數,只須要保存+11 和 +3.026就能推出這個數字的科學技術法,從而獲得這個數字

咱們能夠給+11 和 +3.026取兩個名字,在浮點數裏分別叫階碼和尾數,以下圖

注意階碼分爲了階符和階碼的數值部分,尾數分爲數符合尾數的數值部分。

階符是正表示小數點日後移,爲負表示小數點往前移動。階碼錶示小數點移動多少位。

數符表示數值的正負性,尾數表示數字精度。

其中, 階碼反映數值的大小,尾數反應數值的精度,爲何這麼說呢,好比以前舉的例子中,+11表示小數點要右移多少位,是否是越大,移動的位數越多,數字就越大呢,對於尾數,好比+3.0265748是否是一樣右移5位比+3.026表示的數字更精確呢

在二進制表示的計算機內,階碼經常使用補碼或者移碼錶示的定點整數,尾數經常使用原碼或者補碼錶示的定點小數。

浮點數的表示爲     N = rE * M , r至關於底數,是2(跟10進制科學計數法是10意思是同樣的),E表明階碼,M表明尾數。

IEEE 754標準的雙精度浮點數

常見的IEEE 754標準分爲單精度浮點數float和雙精度浮點數double,咱們能夠看一下它的區別(階碼也能夠稱爲指數)

由於javascript的數字就是雙精度浮點數,因此咱們只介紹這一種。在雙精度浮點數的尾數是52位,實際上是能夠表示53位,爲何呢,咱們知道科學計數法,但二進制只能表示0和1,0不符合科學計數法,因此52位最前面有一個省略的數字是1,默認存在,可是不顯示在52位中。

而且尾數用原碼錶示。

如今咱們講一下階碼須要注意的點。階碼是用的無符號定點整數表示,爲[0,2047](2的11次方減一)但這就有一個問題了,不能表示負數,爲了表示負數通常能夠引入符號位,可是符號位定點數減法運算又要引入補碼,就很麻煩,因此採起一種取巧的方式,將階碼部分統一減去1023,就變成[-1023, 1024]

又由於階碼的全0和全1有特殊用途,因此-1023和1024的移碼就是全1和全0,因此階碼的範圍變爲[-1022, 1023]。那若是階碼是0和1有什麼特殊用途呢?

當階碼E全爲0,尾數M不全爲0時,此時尾數隱藏的首位1.xxx的1變爲0

當階碼E全爲0,尾數M全爲0時,表示真值正負0

當階碼E全爲1,尾數M全爲0時,表示無窮大

當階碼E全爲1,尾數M不全爲0時,表示NaN

指令系統(瞭解)

指令的格式

首先什麼是指令呢?是指計算機執行某種操做的命令,是計算機運行的最小功能單位。一臺計算機的全部指令的集合構成該機的指令系統,也稱爲指令集。好比著名的x86架構(intel的pc)和ARM架構(手機)的指令集是不一樣的。

好比以前有新聞說,蘋果開發了基於ARM架構(精簡指令集)的本身的芯片,放棄了以前採用複雜指令集的intel芯片。

一條指令就是機器語言的一個語句,它是一組有意義的二進制代碼。一條指令一般包括操做碼(OP) + 地址碼(A)

image.png

  • 操做碼簡單來講就是我要進行什麼操做,好比我要實現1+1,加法操做,停機操做等等
  • 地址碼就是好比實現加法操做的數據地址在哪,一般是內存地址。

根據指令中操做數地址碼的數目不一樣,可將指令分紅如下幾種格式。咱們舉幾個例子(沒有覆蓋所有)讓你們感覺一下,尤爲注意三地址指令,就能理解指令的大體格式了

一、零地址指令

只給出操做碼OP,沒有顯式地址。這種指令有兩種可能:

  • 不須要操做數的指令,如空操做指令、停機指令等

二、三地址指令

指令的含義:(A1)OP(A2)->A3

它表示從A1和A2地址上取出數據,而後進行OP操做,最後存放到A3地址上。

尋址方式

尋址尋什麼呢?咱們計算機裏無非保存的是指令和數據,尋的就是上面這兩個傢伙。

指令尋址

指令尋址方式有兩種:一種是順序尋址方式另外一種是跳躍尋址方式

一、順序尋址可經過程序計數器PC加1,也就是按照在內存的順序依次執行指令

image.png

二、跳躍尋址經過轉移類指令實現,跳躍,就是不按照程序計數器自動加一的方式(不是按順序執行指令)給出下調指令地址,而是由本條指令給出的下條指令格式

數據尋址

肯定本條指令的地址碼指明的真實地址。大體有10種尋址方式,咱們只介紹其中3種,由於這部份內容都是以瞭解爲主。

直接尋址

直接尋址是,在指令中的地址碼指向的內存地址就是操做數的有效地址,以下圖

image.png

如上圖,地址碼A對應的就是咱們要的操做數

間接尋址

image.png

如上圖,地址碼A對應的不是操做數,而是另外一個地址,這個地址指向的地址纔是操做數

基址尋址

意思是尋到的地址,並非咱們要去內存尋找的真正地址,而是須要加上一個基礎地址,至關於偏移量。以下圖:

image.png

CISC和RISC

  • CISC (複雜指令系統計算機):一條指令完成一個複雜的基本功能。好比x86架構的計算機,主要用於筆記本和臺式電腦。計算機的指令系統比較豐富,有專用指令來完成特定的功能。所以,處理特殊任務效率較高。

  • RISC (精簡指令系統計算機):一條指令完成一個基本"動做";多條指令組合完成一個複雜的基本功能。好比ARM架構,主要用於手機,平板等。設計者把主要精力放在那些常用的指令上,儘可能使它們具備簡單高效的特點。對不經常使用的功能,常經過組合指令來完成。所以,在RISC 機器上實現特殊功能時,效率可能較低。

中央處理器 + GPU (瞭解)

  • 由於咱們在第一章已經瞭解過cpu和內存在執行指令的大體過程,其實學好第一章,就已經差很少了,因此你們能夠回看一下第一節。

我這裏補充一些cpu內部細節

CPU中比較重要的兩個部件是運算器控制器,咱們先來看看運算器的主要做用

2.1 運算器主要部件

image.png

如上圖,運算器裏最重要的部件是ALU,中文叫算術邏輯單元,用來進行算術邏輯運算的。其它的MQ,ACC這些咱們不用管了,是一些寄存器

2.2 控制器主要部件

image.png

控制器中最重要的部件是CU(控制單元),只要是分析指令,給出控制信號

IR(指令寄存器),存放當前須要執行的指令

PC存放的指令的地址。

2.3 舉例 - 取數指令執行過程

首先,是取指令的過程以下

image.png

  • 第一步,PC,也就是存放指令地址的地方,咱們要知道下一條指令是什麼,就必須去存儲器拿,CPU才知道接下來作什麼。PC去了存儲器的MAR拿要執行的指令地址,MAR(存儲器裏專門存指令地址的地方)
  • 第二步和第三步,MAR去存儲體內拿到指令以後,將指令地址放入MDR(存儲器裏專門存數據的地方)
  • 第四步MDR裏的數據返回到IR裏面,IR是存放指令的地方,咱們把剛纔從存儲體裏拿的指令放在這裏

而後,分析指令,執行指令的過程以下

image.png

  • 第五步, IR將指令放入CU中,來分析指令,好比說分析出是一個取數指令,接着就要執行指令了(這裏取數指令,其實就是一個地址碼,按着這個地址去存儲體取數據)

  • 第六步,第七步 IR就會接着去找存儲體裏的MAR(存儲地址的地方),MAR就根據取數指令裏的地址嗎去存儲體裏去數據

  • 第八步,取出的數據返回給MDR(存放數據的地方)

  • 第九步,MDR裏的數據放到運算器的寄存器裏,這裏的取指令的過程結束了。

  • 這裏咱們主要補充一下GPU的內容。

GPU(Graphics Processing Unit) 圖形處理單元,又稱圖形處理器,是咱們所周知的顯卡的核心部件,是顯卡的「心臟」。GPU是專爲複雜數學運算和幾何運算而設計的芯片,它的用途咱們日常所周知的就是用於圖形圖像處理(顯卡)。

CPU與GPU

咱們能夠看一下CPU和GPU的對比圖

  • 上圖的一段總結很是好,CPU至關於1名老教授,奧數題和小學算術題都會,GPU至關於1000名小學生,只會小學算術題。

  • 從上圖咱們能夠知道GPU將更多的空間(晶體管)用做執行單元,而不是像CPU那樣用做複雜的控制單元和緩存(CPU須要同時很好的支持並行和串行操做,須要很強的通用性來處理各類不一樣的數據類型,同時又要支持複雜通用的邏輯判斷,這樣會引入大量的分支跳轉和中斷的處理。

  • 這些都使得CPU的內部結構異常複雜,計算單元的比重被下降了),實際來看CPU的芯片控件25%是ALU,而GPU則高達90%(GPU面對的則是類型高度統一的、相互無依賴的大規模數據和不須要被打斷的純淨的計算環境。所以GPU的芯片比CPU芯片簡單不少),這也就是爲啥GPU運算能力超強的緣由。

GPU加速在前端的應用

首先咱們要知道爲何要用(開啓)GPU加速(硬件加速), 而後咱們才能去探討如何以及怎麼樣去應用GPU加速。

  1. 3D 或透視變換(perspective,transform) CSS 屬性

  2. 使用加速視頻解碼的video元素

  3. 擁有 3D (WebGL) 上下文或加速的 2D 上下文的 canvas 元素

  4. 混合插件(如 Flash)

  1. 對本身的 opacity 作 CSS 動畫或使用一個動畫 webkit 變換的元素

  2. 擁有加速 CSS 過濾器的元素

  3. 元素A有一個 z-index 比本身小的元素B,且元素B是一個合成層(換句話說就是該元素在複合層上面渲染),則元素A會提高爲合成層

這裏裏面最經常使用的是1和7。1很好理解,就是transfrom3d屬性。第七點我解釋一下,你怎麼來判斷本身的頁面是否使用了3d加速。請看下圖:

首先:

而後觀察這兩個圖層:

那如何讓2d也能單獨的圖層渲染啓用GPU加速呢,只須要給2d的css上加一個index以後,而後點擊動畫,就會出現黃色邊框,你們能夠用這個網址作測試:www.w3school.com.cn/css3/css3_3…

總線(瞭解便可)

  • 總線這部分不是重點,主要了解總線的大體工做工做流程便可

總線的定義

總線是一組能爲多個部件分時共享的公共信息傳送線路

爲何須要總線結構

一、簡化了硬件的設計。咱們從計算機簡史裏面知道,當時的設備是分散接入計算機的,這樣計算機沒辦法統一接口命令來控制這些設備。總線結構便於採用模塊化結構設計方法,面向總線的微型計算機設計只要按照這些規定製做cpu插件、存儲器插件以及I/O插件等,將它們連入總線就可工做,而沒必要考慮總線的詳細操做。

二、系統擴充性好。一是規模擴充,規模擴充僅僅須要多插一些同類型的插件。二是功能擴充,功能擴充僅僅須要按照總線標準設計新插件,插件插入機器的位置每每沒有嚴格的限制。

就至關於webpack的插件系統,加入功能和減小功能都是可插拔的,比把代碼寫死更加靈活。

總線工做的簡單過程

屏幕快照 2021-07-29 上午11.58.49.png

咱們拿上圖爲例:

  • CPU能夠經過地址總線給主存、打印機或者硬盤發送地址信息。
  • 同理,CPU也能夠經過數據總線和控制總線去跟其餘硬件設備進行數據傳輸或者發送控制命令

存儲系統

  • 本章絕對重點就是cache的基本原理(爲何須要cache, 局部性原理是什麼),cache的替換算法(面試被好幾回問道LRU緩存算法怎麼寫)

多級存儲系統(瞭解)

爲何須要多級存儲的結構呢?以下圖所示,能夠了解到爲何要引入cache

主存的執行速度相比cpu要慢不少,這會形成主存在運行的時候,cpu會等待的問題,好比cpu1秒就處理10條指,但從內存裏取10條指令就須要1分鐘,就很浪費cpu資源,因此爲了解決這個問題,採用了cache-主存的方式,cache是高速緩衝儲存器,它的速度接近於cpu。

存儲器的分類 --- 按存取方式(瞭解)

  • 隨機存取存儲器:讀寫任何一個存儲單元所須要的時間都相同,與存儲單元所在的物理位置無關。好比內存條。
  • 順序存取存儲器:讀寫一個存儲單元所需時間取決於存儲單元所在的位置。好比磁帶,你若是已經放完了磁帶,想從頭開始聽,須要倒帶到開始的位置。

RAM和ROM的特色(重點)

RAM

RAM又被稱做「隨機存儲器」,是與CPU直接交換數據的內部存儲器,也叫主存(內存)。它能夠隨時讀寫,並且速度很快,一般做爲操做系統或其餘正在運行中的程序的臨時數據存儲媒介。當電源關閉時RAM不能保留數據(掉電數據消失哦)若是須要保存數據,就必須把它們寫入一個長期的存儲設備中(例如硬盤)。

ROM

ROM又被稱爲「只讀存儲器」,ROM所存數據,通常是裝入整機前事先寫好的,整機工做過程當中只能讀出,而不像隨機存儲器那樣能快速地、方便地加以改寫。ROM所存數據穩定,斷電後所存數據也不會改變

局部性原理(重點)

先看下圖

(說明一下,MDRMAR雖然邏輯上屬於主存,可是在電路實現的時候,MDRMARCPU比較近)

上圖是在執行一串代碼,能夠理解爲js的for循環

const n = 1000;
const a = [1, 2, 3, 4, 5, 6, 7]
for(let i =0; i < n; i++) {
    a[i] = a[i] + 2
}
複製代碼

咱們能夠發現

  • 數組的數據有時候在內存是連續存儲的(代碼裏但數組a,對應圖中主存裏但a[0]-a[7]的數據塊)

  • 若是咱們要取數據,好比從內存取出a[0]的數據須要1000ns(ns是納秒的意思),那麼取出a[0]到a[7]就須要1000 * 8 = 8000 ns

  • 若是咱們cpu發現這是取數組數據,那麼我就把就近的數據塊a[0]到a[7]所有存到緩存上多好,這樣只須要取一次數據,消耗1000ns

cahce就是局部性原理的一個應用

  • 空間局部性:在最近的將來要用到的信息(指令數據),極可能與如今正在使用的信息在存儲空間上是鄰近的(好比for循環用到數據在主存都是相鄰存儲的)
  • 時間局部性:在最近的將來要用到的信息,極可能是如今正在使用的信息

下圖注意的是,cpu拿數據是先從cache裏拿,若是沒有才從主存裏面取

能夠看到cache一次性取了a[0]a[9]存儲體上的數據,只須要1000ns,由於Cache高速存儲器,跟cpu交互速度就比cpu主存交互速度快不少

輸入/輸出系統

  • I/O這部分重中之中的知識點是I/O方式,即理解立刻就要講到的I/O設備的演進過程,其它知識點做爲了解就好

I/O是什麼呢?

輸入/輸出(Input /Output ,簡稱I/O),指的是一切操做、程序或設備與計算機之間發生的數據傳輸過程。

好比文件讀寫操做,就是典型的I/O操做。接下來咱們看一下I/O設備的演進過程

I/O設備的演進過程

關鍵:I/O設備的演進過程其實就是解放cpu的過程,爲何這麼說呢,看完下面的介紹就知道了!

  • 早期計算機主要功能就是計算,因此就以cpu爲核心
  • 外設鏈接cpu須要一套專用的線路,外設的增刪就很麻煩
  • cpu和外設串行工做模式

在早期的計算機裏,由於cpu啓動外設後,外設準備數據是須要時間的,好比讀取外部傳來的數據,此時cpu如何知道I/O設備已經完成任務呢?好比說怎麼知道I/O設備已經讀取完一個文件的數據呢?CPU會不斷查詢I/O設備是否已經準備好。這時,cpu就處於等待狀態。也就是cpu工做的時候,I/O系統是不工做的,I/O系統工做,cpu是不工做。並且主存和外設也須要藉助cpu來通訊,因此留給CPU的時間又少了。

因此我來看此階段比較明顯的問題:

  • 外設設備分散鏈接,外設的增刪就很麻煩 ,咱們引入了總線結構
  • 高速外設跟cpu交流頻繁

接着看第二階段

  • cpu啓動好外設以後,就返回繼續本身的工做了,外設準備好數據後,經過中斷請求的方式通知cpu,cpu只須要暫停手上的工做,處理一下具體數據傳輸的過程,減小不斷查詢的時間

  • 爲了解決第一階段CPU要等待I/O設備串行的工做方式,全部I/O設備經過I/O總線來跟CPU打交道,一旦某個I/O設備完成任務,就會以中斷請求的方式,經過I/O總線,告訴CPU,我已經準備好了。

  • 可是對於高速外設,它們完成任務的速度很快,因此會頻繁中斷CPU,(舉一個例子,每輸入一個字符就中斷CPU,是否是很影響cpu的執行呢) 爲了解決這個問題,高速外設跟主存之間用一條直接數據通路,DMA總線鏈接,DMA控制方式只須要CPU安排最開始高速外設最初的任務,接下來的數據交換就是靠DMA控制器控制了,這樣就能夠防止頻繁中斷CPU,讓CPU獲得瞭解放

問題:

  • DMA控制器的傳送任務是cpu來安排的,還有dma鏈接外設的類型和數量是不靈活的,因此咱們設置了一些專門管理外設的處理器

最後來看一下第三階段

第三階段,CPU經過通道控制部件來管理I/O設備,CPU不須要幫它安排任務,只須要簡單的發出啓動和中止相似的命令,通道部件就會自動的安排相應的I/O設備工做。爲何這種方式比DMA更好呢,由於商用的中型機、大型機可能會接上超多的I/O設備,若是都讓CPU來管理,那麼CPU就很是累。

通道能夠理解爲一種「弱雞版的CPU」。能夠識別通道指令,你能夠理解爲CPU告訴通道,取多少數據,數據存放到內存哪裏,通道就本身去處理,不用cpu來管理這麼多事情,注意看下圖

如上圖,通道是跟CPU並列的,因此通道能幫CPU分擔任務,此時的通道有本身的一套指令集,能夠執行通道指令。

後面進一步加強了通道的功能,這裏出現了跟cpu處理能力差很少的I/O處理機

補充:中斷是什麼(重點,操做系統課也會涉及這個概念)

以前咱們講到一個名詞叫中斷,這個概念很是重要,咱們做爲補充概念學習一下

中斷的概念

程序中斷是指在計算機執行現行程序的過程當中,出現某些急需處理的異常狀況或特殊請求,CPU暫時停止現行程序,而轉去對這些異常狀況或特殊請求進行處理,在處理完畢後CPU又自動返回到現行的斷點處,繼續執行程序。咱們來舉個例子就明白了。

當程序執行到K的時候,鍵盤的敲擊產生了中斷(I/O中斷),此時CPU會終止執行當前的指令,轉而去處理中斷,等中斷服務程序執行完畢,繼續執行k+1。

本文主要參考資料:

唐朔飛:計算機組成原理

袁春風: 計算機組成原理

王道考研:計算機組成原理

極客時間:深刻淺出計算機組成原理

相關文章
相關標籤/搜索