初識(shí)計(jì)算機(jī)組成原理-指令和運(yùn)算篇

計(jì)算機(jī)的硬件組成

undefined

現(xiàn)代計(jì)算機(jī)的基本組成部分其實(shí)主要由三部分組成:CPU获诈,內(nèi)存,主板童本。

你撰寫的程序,打開的任何PC端應(yīng)用脸候。都要加載到內(nèi)存中才能運(yùn)行穷娱,存放在內(nèi)存中的程序及其數(shù)據(jù)需要被CPU讀取,CPU計(jì)算完之后還要把對(duì)應(yīng)的數(shù)據(jù)寫回到內(nèi)存运沦。主板的作用就是承載二者泵额,因?yàn)樗麄儾荒芑ハ嗲度氲綄?duì)方中。

主板上的芯片組總線解決了CPU及內(nèi)存之間如何通信的問題携添。芯片組控制了數(shù)據(jù)傳輸?shù)牧鬓D(zhuǎn)嫁盲,也就是數(shù)據(jù)從哪里到哪里的問題×衣樱總線則是實(shí)際數(shù)據(jù)傳輸?shù)母咚俟贰?/p>

具體流程:CPU讀取內(nèi)存中的二進(jìn)制指令羞秤,然后譯碼,通過控制信號(hào)操作對(duì)應(yīng)的運(yùn)算原件以及存儲(chǔ)單元進(jìn)行操作向叉。

馮諾依曼體系結(jié)構(gòu)

我們現(xiàn)在所使用的機(jī)器既叫圖靈機(jī)也叫馮諾依曼機(jī)锥腻,兩者是不同的計(jì)算機(jī)抽象嗦董。馮諾依曼側(cè)重于硬件抽象母谎,圖靈機(jī)則側(cè)重于計(jì)算抽象。

馮諾依曼體系結(jié)構(gòu)也叫存儲(chǔ)程序計(jì)算機(jī)京革,起源于馮諾依曼發(fā)表的一片文章即“第一份草案”奇唤,文中描述了他心目中的一臺(tái)計(jì)算機(jī)應(yīng)該是什么樣幸斥。即它是“可存儲(chǔ)”,“可編程”的計(jì)算機(jī)咬扇,并且還其中還說了一臺(tái)計(jì)算機(jī)應(yīng)該有哪些部分組成甲葬,即:運(yùn)算器,控制器懈贺,存儲(chǔ)器经窖,輸入設(shè)備,輸出設(shè)備五部分梭灿。

他認(rèn)為現(xiàn)代計(jì)算機(jī)應(yīng)該是一個(gè)“可編程”的計(jì)算機(jī)画侣,是一個(gè)“可存儲(chǔ)”的計(jì)算機(jī)。即也叫存儲(chǔ)程序計(jì)算機(jī)堡妒。因?yàn)檫^去的計(jì)算機(jī)電路是焊死在電路板的配乱,如同現(xiàn)在的計(jì)算器,如果要做其他的操作皮迟,那么就需要重新焊接電路搬泥,它是不可編程的計(jì)算機(jī)。再后來的計(jì)算機(jī)是“插拔式”的計(jì)算機(jī)伏尼,需要用什么程序必須得插入該程序的組件才可即類似我們玩的游戲機(jī)忿檩。這樣程序無(wú)法保存在計(jì)算機(jī)上,每次使用的時(shí)候還需要插拔的方式才可以烦粒。它是不可存儲(chǔ)的計(jì)算機(jī)休溶。

如圖即為馮諾依曼體系結(jié)構(gòu):


undefined

圖靈機(jī)

圖靈是一位數(shù)學(xué)家,他對(duì)于計(jì)算機(jī)的設(shè)想扰她,并沒有考慮計(jì)算機(jī)的硬件基礎(chǔ)兽掰,而是只考慮了計(jì)算機(jī)在數(shù)學(xué)計(jì)算模型上的可行性。即只思考了作為一個(gè)“計(jì)算機(jī)”徒役,他應(yīng)該做的工作孽尽,和怎么工作。也是因?yàn)樵谀莻€(gè)年代忧勿,計(jì)算機(jī)原本的作用就就是幫助數(shù)學(xué)家進(jìn)行運(yùn)算的一個(gè)工具產(chǎn)物杉女。圖靈只思考了計(jì)算機(jī)的計(jì)算模型,及計(jì)算機(jī)所謂的“計(jì)算”的理論邏輯的實(shí)現(xiàn)方法鸳吸。

圖靈機(jī)可以看做由一條兩端可無(wú)限延長(zhǎng)的帶子熏挎,它有一個(gè)讀寫頭,以及一組控制讀寫頭工作的命令組成晌砾,是一種抽象的計(jì)算模型坎拐,即將人們使用紙筆進(jìn)行的數(shù)學(xué)運(yùn)算由一個(gè)虛擬的機(jī)器代替。

它證明了通用計(jì)算理論,肯定了計(jì)算機(jī)實(shí)現(xiàn)的可能性哼勇。同時(shí)它給出了計(jì)算機(jī)應(yīng)有的主要架構(gòu)都伪,引入了讀寫,算法积担,程序語(yǔ)言的概念陨晶。極大的突破了過去的計(jì)算機(jī)的設(shè)計(jì)理念。

其實(shí)圖靈機(jī)本質(zhì)上是狀態(tài)機(jī)帝璧,計(jì)算機(jī)理論模型先誉,馮諾依曼體系則更像是圖靈機(jī)的具體物理實(shí)現(xiàn)。包括運(yùn)算的烁,控制谆膳,存儲(chǔ),輸入撮躁,輸出五個(gè)部分漱病。馮諾依曼體系相對(duì)之前的計(jì)算機(jī)最大的創(chuàng)新在于程序和數(shù)據(jù)的存儲(chǔ)。從此實(shí)現(xiàn)機(jī)器的內(nèi)部編程把曼。圖靈機(jī)的紙帶應(yīng)對(duì)馮諾依曼體系中的存儲(chǔ)杨帽,讀寫頭對(duì)應(yīng)輸入輸出規(guī)則及(讀取一個(gè)符號(hào)后,做了什么)運(yùn)算嗤军,紙帶怎么移動(dòng)則對(duì)應(yīng)著控制注盈。理論上圖靈機(jī)可以模擬人類的所有計(jì)算過程,無(wú)論復(fù)雜與否叙赚。

undefined

我認(rèn)為老客,圖靈機(jī)就是一個(gè)抽象的“思維實(shí)驗(yàn)”,而馮·諾依曼機(jī)就是對(duì)應(yīng)著這個(gè)“思維實(shí)驗(yàn)”的“物理實(shí)現(xiàn)”震叮。如果我們把“圖靈機(jī)”當(dāng)成“靈魂”胧砰,代表計(jì)算機(jī)最抽象的本質(zhì),那么“馮諾伊曼機(jī)”就是“肉體”苇瓣,代表了計(jì)算機(jī)最具體的本質(zhì)尉间。這兩者之間頗有理論物理學(xué)家和實(shí)驗(yàn)物理學(xué)家的合作關(guān)系的意思,可謂是一個(gè)問題的兩面击罪。

芯片組哲嘲,總線

芯片組

芯片組是主板的核心組成部分,聯(lián)系CPU及其他周邊設(shè)備的運(yùn)作媳禁。過去主板上最重要的芯片組就是南北橋芯片組眠副。

南北橋芯片組->主板上的兩個(gè)主要芯片組,靠上方的叫北橋竣稽,下方的叫南橋囱怕。

  • 北橋負(fù)責(zé)與CPU通信槽唾,并且鏈接高速設(shè)備(內(nèi)存,顯卡)光涂,并且與(I/O操作)南橋通信。
  • 南橋負(fù)責(zé)與低速設(shè)備(硬盤/外部IO設(shè)備拧烦,USB等設(shè)備)通信忘闻,并且與北橋通信。

總線

主板的芯片組和總線解決了CPU和內(nèi)存通信的問題(北橋)恋博,芯片組控制數(shù)據(jù)傳輸?shù)牧鬓D(zhuǎn)(從哪來齐佳,到哪兒去),總線則是實(shí)際數(shù)據(jù)傳輸?shù)母咚俟贰?/p>

CPU

指令集债沮,微架構(gòu)

又稱指令集體系炼吴,是計(jì)算機(jī)體系結(jié)構(gòu)中與程序設(shè)計(jì)有關(guān)的部分包含了基本數(shù)據(jù)類型疫衩,指令集硅蹦,寄存器,尋址模式闷煤,存儲(chǔ)體系童芹,中斷,異常處理以及外部I/O鲤拿。指令集架構(gòu)包含一系列的opcode即操作碼(機(jī)器語(yǔ)言)假褪,以及由特定處理器執(zhí)行的基本命令。

不同的處理器“家族”——例如Intel IA-32和x86-64近顷、IBM/Freescale Power和ARM處理器家族——有不同的指令集架構(gòu)。

指令集體系與微架構(gòu)(一套用于執(zhí)行指令集的微處理器設(shè)計(jì)方法)不同。使用不同微架構(gòu)的計(jì)算機(jī)可以共享一種指令集桂敛。 例如赡矢,Intel的Pentium和AMD的AMD Athlon,兩者幾乎采用相同版本的x86指令集體系饱须,但是兩者在內(nèi)部的硬件設(shè)計(jì)上有本質(zhì)的區(qū)別瑟由。

一些虛擬機(jī)支持基于Smalltalk,Java虛擬機(jī)冤寿,微軟的公共語(yǔ)言運(yùn)行時(shí)虛擬機(jī)所生成的字節(jié)碼歹苦,他們的指令集體系將bytecode(字節(jié)碼)從作為一般手段的代碼路徑翻譯成本地的機(jī)器語(yǔ)言,并通過解譯執(zhí)行并不常用的代碼路徑督怜,全美達(dá)以相同的方式開發(fā)了基于x86指令體系的VLIW處理器殴瘦。

指令集是所有機(jī)器指令的集合,匯編可以認(rèn)為是指令集的抽象号杠,為了方便開發(fā)人員使用蚪腋。

指令集的優(yōu)劣設(shè)計(jì):對(duì)微處理器而言可以視為有兩種指令集丰歌。第一種是復(fù)雜指令集,擁有許多不同的指令屉凯。在1970年代立帖,許多機(jī)構(gòu),像是IBM悠砚,發(fā)現(xiàn)有許多指令是不需要的晓勇。結(jié)果就產(chǎn)生了精簡(jiǎn)指令集,它所包含的指令就比較少灌旧。精簡(jiǎn)的指令集可以提供比較高的速度绑咱,使處理器的尺寸縮小,以及較少的電力損耗枢泰。然而描融,比較復(fù)雜的指令集較容易使工作更完善,存儲(chǔ)器及緩存的效率較高衡蚂,以及較為簡(jiǎn)單的代碼窿克。

一些指令集保留了一個(gè)或多個(gè)的opcode,以運(yùn)行系統(tǒng)調(diào)用或軟件中斷毛甲。

指令集的分類:

復(fù)雜指令集計(jì)算機(jī)包含許多應(yīng)用程序中很少使用的特定指令让歼,由此產(chǎn)生的缺陷是指令長(zhǎng)度不固定。精簡(jiǎn)指令集計(jì)算機(jī)通過只執(zhí)行在程序中經(jīng)常使用的指令來簡(jiǎn)化處理器的結(jié)構(gòu)丽啡,而特殊操作則以子程序的方式實(shí)現(xiàn)谋右,它們的特殊使用通過處理器額外的執(zhí)行時(shí)間來彌補(bǔ)。理論上的重要類型還包括最小指令集計(jì)算機(jī)與單指令集計(jì)算機(jī)补箍,但都未用作商業(yè)處理器改执。另外一種派生類型是超長(zhǎng)指令字,處理器接受許多經(jīng)過編碼的指令并通過檢索提取出一個(gè)指令字并執(zhí)行坑雅。

X86指令集和ARM指令集區(qū)別例子:

A要坐車去B

  • arm:準(zhǔn)備四個(gè)輪子和一個(gè)座位 > 把A放在座位上 > 座位放在四個(gè)輪子上 > 去B
  • x86: 倉(cāng)庫(kù)找車 > 坐車 > 去B

同樣的事情辈挂,雖然結(jié)果是一樣把A送到了目的地,但是arm沒有倉(cāng)庫(kù)裹粤,什么都要造终蒂,x86是有一堆倉(cāng)庫(kù),隨便用遥诉,差異在于arm過程中消耗少拇泣,因?yàn)榫蛡€(gè)車殼輪子,而x86那了車出來矮锈,但是車上的燈光霉翔,空調(diào),其余的空座位是本次執(zhí)行中不需要的東西苞笨,也一起弄進(jìn)去了债朵,所以占用資源比arm大子眶,但是做復(fù)雜的事因?yàn)閤86啥都有,所以比arm開發(fā)更簡(jiǎn)單序芦,統(tǒng)一標(biāo)準(zhǔn)的庫(kù)也能讓效率更高臭杰。

指令集就是 add move這些匯編命令對(duì)應(yīng)的二進(jìn)制命令串,微架構(gòu)就是谚中,在硬件層面這些二進(jìn)制命令串是如何實(shí)現(xiàn)功能的渴杆,其實(shí)就是集成電路

參考:

Intel,AMD

  • Intel, AMD 的 CPU藏杖,大多是為 CISC 結(jié)構(gòu)的 CPU,因?yàn)樗麄冎鳡I(yíng) PC 端脉顿。
  • ARM 則屬于 RISC 結(jié)構(gòu)的 CPU蝌麸。

CISC 是復(fù)雜指令集CPU,指令較長(zhǎng)艾疟,分成幾個(gè)微指令去執(zhí)行来吩,開發(fā)程序比較容易(指令多的緣故)。通常一個(gè)指令需要好幾個(gè) Machine Cycle 才能執(zhí)行蔽莱。RISC 是精簡(jiǎn)指令集CPU弟疆,指令較短,如果內(nèi)部的 pipe line 做得好盗冷,可以使得指令的譯碼與數(shù)據(jù)的處理較快怠苔,使得執(zhí)行效率高。通常一個(gè)指令只要一個(gè) Machine Cycle 就能夠執(zhí)行仪糖。

近年來某些 CISC CPU柑司,如 Intel 的 Pentium-Pro、AMD 的K5锅劝、K6實(shí)際上是改進(jìn)了的CISC攒驰,也可以做到一個(gè) Machine Cycle 能執(zhí)行一個(gè)指令。ARM 本身不做CPU故爵,只設(shè)計(jì)CPU架構(gòu)玻粪,并授權(quán)給其他廠商做ARM架構(gòu)的CPU,比如高通诬垂、聯(lián)發(fā)科等我們熟知的CPU公司都是用ARM架構(gòu)劲室。

由於 ARM 的結(jié)構(gòu)一開始就以省電為主,所以一般來說结窘,執(zhí)行速度沒有 Intel 或是 AMD 的 CPU 快痹籍。但是在手持式裝置的世界,ARM 的省電加上以授權(quán)而非自制的聯(lián)合軍團(tuán)方式晦鞋,打敗了所有的人蹲缠,成為王者棺克,現(xiàn)在進(jìn)而慢慢的進(jìn)入其他的領(lǐng)域。

參考:

CPU與GPU

CPU即中央處理器铛楣,GPU即圖形處理器近迁,現(xiàn)在的電腦,大部分GPU都集成在了CPU中也叫集成顯卡簸州,后來原本的GPU即屬于北橋的內(nèi)存控制器等作為一支獨(dú)立的芯片封裝到了CPU基板上鉴竭。所以后來的及其的主板上沒有南北橋之分了,只剩下了PCH芯片即過去的南橋岸浑。當(dāng)然如果你的PC機(jī)要運(yùn)行一些大型游戲搏存,或者有一些對(duì)GPU要求較高的工作的話,也可以配置獨(dú)立的GPU卡到主板上矢洲。

過去:

  • CPU — 北橋 — 內(nèi)存
  • CPU — 北橋 — 顯卡
  • CPU — 北橋 — 南橋 - 硬盤
  • CPU — 北橋 — 南橋 - 網(wǎng)卡
  • CPU — 北橋 — 南橋 - 外部IO設(shè)備

CPU的好壞決定 -> 主頻高璧眠,緩存大,核心數(shù)多读虏。CPU一般安裝在主板的CPU插槽中蛆橡。

數(shù)據(jù)通路:其實(shí)就是連接了整個(gè)運(yùn)算器與控制器,方便我們程序的運(yùn)轉(zhuǎn)和計(jì)算掘譬,并最終組成了CPU泰演。

CPU一般被叫做超大規(guī)模集成電路,由一個(gè)個(gè)晶體管組合而成葱轩,CPU的計(jì)算過程其實(shí)就是讓晶體管中的“開關(guān)”信號(hào)不斷的去“打開”和“關(guān)閉”睦焕,來組合完成各種運(yùn)算和功能,這里的“打開”及“關(guān)閉”操作的快慢就是由CPU主頻來影響靴拱。

控制器:一條條指令執(zhí)行的控制過程垃喊,就是由計(jì)算機(jī)五大組件之一的控制器來控制的。

CPU 的核數(shù)線程數(shù)

要看一臺(tái)PC機(jī)的具體CPU核數(shù)以及線程數(shù)可以通過任務(wù)管理器界面看到袜炕,也可以通過計(jì)算機(jī)右鍵屬性的設(shè)備管理器中看到(僅能看到線程數(shù))本谜。或者通過如下命令看到

wmic
cpu get *
-----------對(duì)應(yīng)屬性
NumberOfCores
NumberLogicProcessors

CPU 主頻

CPU 的主頻即內(nèi)核工作的時(shí)鐘頻率乌助,通常所說的CPU是多少兆赫的溜在,這里所謂的兆赫就是描述的CPU主頻,CPU型號(hào)后面跟著的2.4 GHZ即主頻的數(shù)字描述他托。

主頻并不直接代表CPU的運(yùn)算速度掖肋,所以也會(huì)有CPU主頻高但是CPU的運(yùn)算速度慢的情況,主頻僅是CPU性能表現(xiàn)的一方面赏参。

CPU 線程和 Java 線程的關(guān)系

Java 中的所有線程均在JVM進(jìn)程中志笼,CPU調(diào)度的是進(jìn)程中的線程。

CPU線程數(shù)和Java線程數(shù)并沒有直接關(guān)系把篓,CPU采用分片機(jī)制執(zhí)行線程纫溃,給每個(gè)線程劃分很小的時(shí)間顆粒去執(zhí)行,但是真正的項(xiàng)目中韧掩,一個(gè)程序要做很多的的操作紊浩,讀寫磁盤、數(shù)據(jù)邏輯處理揍很、出于業(yè)務(wù)需求必要的休眠等等操作郎楼,當(dāng)程序正在執(zhí)行的線程進(jìn)入到I/O操作的時(shí)候万伤,線程隨之進(jìn)入阻塞狀態(tài)窒悔,此時(shí)CPU會(huì)做上下文切換,以便處理其他線程的任務(wù)敌买;當(dāng)I/O操作完成后简珠,CPU會(huì)收到一個(gè)來自硬盤的中斷信號(hào),并進(jìn)入中斷處理例程虹钮,手頭正在執(zhí)行的線程則可能因此被打斷聋庵,回到 ready 隊(duì)列悄蕾。而先前因 I/O 而阻塞等待的線程隨著 I/O 的完成也再次回到 就緒隊(duì)列销斟,這時(shí) CPU 在進(jìn)行線程調(diào)度的時(shí)候則可能會(huì)選擇它來執(zhí)行。

進(jìn)程與線程

線程是操作系統(tǒng)最小的調(diào)度單位钧萍,進(jìn)程則是操作系統(tǒng)資源分配的對(duì)小單位春畔。

進(jìn)程:進(jìn)程是操作系統(tǒng)分配資源的基本單位脱货,每個(gè)進(jìn)程擁有虛擬后的獨(dú)立的內(nèi)存空間,存儲(chǔ)空間律姨,CPU資源振峻。各種PC端應(yīng)用均是一個(gè)獨(dú)立的進(jìn)程。

線程:是CPU調(diào)度的基本單位择份,同意進(jìn)程的各個(gè)線程共享進(jìn)程內(nèi)部的資源扣孟,線程間的通訊遠(yuǎn)小于進(jìn)程間的。因?yàn)?各個(gè)線程共享進(jìn)程內(nèi)部的資源)荣赶。所以在多線程并發(fā)的情況下凤价,需要額外關(guān)注對(duì)于共享資源的保護(hù)問題鸽斟,尤其是全局變量。

超線程

Intel的超線程技術(shù)料仗,目的是為了更充分地利用一個(gè)單核CPU的資源湾盗。CPU在執(zhí)行一條機(jī)器指令時(shí),并不會(huì)完全地利用所有的CPU資源立轧,而且實(shí)際上格粪,是有大量資源被閑置著的。超線程技術(shù)允許兩個(gè)線程同時(shí)不沖突地使用CPU中的資源氛改。比如一條整數(shù)運(yùn)算指令只會(huì)用到整數(shù)運(yùn)算單元帐萎,此時(shí)浮點(diǎn)運(yùn)算單元就空閑了,若使用了超線程技術(shù)胜卤,且另一個(gè)線程剛好此時(shí)要執(zhí)行一個(gè)浮點(diǎn)運(yùn)算指令疆导,CPU就允許屬于兩個(gè)不同線程的整數(shù)運(yùn)算指令和浮點(diǎn)運(yùn)算指令同時(shí)執(zhí)行,這是真的并行葛躏。我不了解其它的硬件多線程技術(shù)是怎么樣的澈段,但單就超線程技術(shù)而言,它是可以實(shí)現(xiàn)真正的并行的舰攒。但這也并不意味著兩個(gè)線程在同一個(gè)CPU中一直都可以并行執(zhí)行败富,只是恰好碰到兩個(gè)線程當(dāng)前要執(zhí)行的指令不使用相同的CPU資源時(shí)才可以真正地并行執(zhí)行

本質(zhì)上是一個(gè)物理核在跑一個(gè)線程時(shí)摩窃,同時(shí)利用閑置的運(yùn)算單元管跑其他指令兽叮,這樣就可以提升效能。

性能和功耗

對(duì)于計(jì)算機(jī)而言:性能 即響應(yīng)時(shí)間的倒數(shù)猾愿。有兩個(gè)指標(biāo)來進(jìn)行恒定

  • 響應(yīng)時(shí)間:即任務(wù)的執(zhí)行時(shí)間鹦聪,讓計(jì)算機(jī)處理的更快。
  • 吞吐率:即帶寬蒂秘,讓計(jì)算機(jī)處理的更多泽本。

其中吞吐率的提升有很多種方式,多核處理器就是其中一種姻僧。即我們現(xiàn)在處理器都是6核8核等规丽,同一時(shí)間可以使CPU執(zhí)行多個(gè)任務(wù),相應(yīng)的響應(yīng)時(shí)間變短也同樣可以增加計(jì)算機(jī)的吞吐率段化,但現(xiàn)在CPU在響應(yīng)時(shí)間上的提升卻遇到了瓶頸嘁捷,“摩爾定律”有點(diǎn)兒不再適用了。

性能 = 1 / 響應(yīng)時(shí)間显熏。響應(yīng)時(shí)間越短雄嚣,性能的數(shù)值就越大

理論上程序運(yùn)行時(shí)間 = 程序在用戶態(tài)運(yùn)行指令的時(shí)間 + 程序在內(nèi)核態(tài)運(yùn)行指令的時(shí)間。

但受線程調(diào)度的影響,CPU在同一時(shí)間會(huì)有很多的Task在執(zhí)行缓升,不是只執(zhí)行特定程序的指令鼓鲁,并且同一臺(tái)計(jì)算機(jī)可能CPU滿載執(zhí)行,也能會(huì)降頻執(zhí)行港谊。并且程序運(yùn)行時(shí)間也會(huì)受到相應(yīng)的主板和內(nèi)存的影響骇吭。如下圖:

image

程序的 CPU 執(zhí)行時(shí)間 = CPU 時(shí)鐘周期數(shù) × 單位時(shí)鐘周期時(shí)間。

比如Intel Core - i7 - 7700HQ 2.8GHZ歧寺,這里的2.8GHZ粗淺理解即CPU在一秒里可以執(zhí)行的簡(jiǎn)單指令數(shù)是2.8G條燥狰。準(zhǔn)確說即CPU的一個(gè)“鐘表”能夠識(shí)別出來的最小時(shí)間間隔。

計(jì)算機(jī)的計(jì)時(shí)單位:CPU 時(shí)鐘斜筐。

時(shí)鐘周期時(shí)間: 在CPU內(nèi)部龙致,和我們戴的電子石英表類似,有一個(gè)叫晶體振蕩器的東西簡(jiǎn)稱“晶振”顷链,晶振的每一次“滴答”即電子石英表的時(shí)鐘周期時(shí)間(晶振時(shí)間)目代。在2.8GHZ主頻的CPU上,這個(gè)時(shí)鐘周期時(shí)間就是1/2.8GHZ嗤练。CPU就是按照這個(gè)“時(shí)鐘”提示的時(shí)間來進(jìn)行自己的操作榛了,主頻越高意味著這個(gè)表走的越快,CPU也就“被逼”著走的也快煞抬,CPU越快散熱壓力當(dāng)然也越大霜大。

CPU時(shí)鐘周期數(shù) = 指令數(shù) * 指令的平均時(shí)鐘周期數(shù)(CPI)。

這里說了指令的平均時(shí)鐘周期數(shù)此疹,所以我們就知道不同的指令執(zhí)行時(shí)間是不同的僧诚,即所花費(fèi)的時(shí)鐘周期數(shù)是不同的遮婶,具體到計(jì)算機(jī)蝗碎,乘法的時(shí)鐘周期數(shù)就要多于加法。不過現(xiàn)代的CPU通過流水線技術(shù)可以使得單個(gè)命令的執(zhí)行需要的CPU時(shí)鐘周期數(shù)更少了旗扑。

一個(gè)程序包含多條語(yǔ)句蹦骑,一條語(yǔ)句可能對(duì)應(yīng)多條指令,一條CPU指令可能需要多個(gè)CPU時(shí)鐘周期才能完成臀防。

程序的CPU執(zhí)行時(shí)間: 指令數(shù) * 指令的平均時(shí)鐘周期數(shù)(CPI) * 時(shí)鐘周期時(shí)間

由上面的公式我們知道眠菇,如果想要減少程序的CPU執(zhí)行時(shí)間的話那么就要從以上三點(diǎn)著手。但是指令數(shù)是由不同編譯器所決定的袱衷,時(shí)鐘周期時(shí)間則是由CPU主頻的高低來決定的捎废,而每條指令的平均時(shí)鐘周期數(shù)我們則可以通過流水線技術(shù)來優(yōu)化。

摩爾定律:其中摩爾定律就一直不斷在提升計(jì)算機(jī)主頻致燥,英特爾創(chuàng)始人之一 戈登丶摩爾 曾說:當(dāng)價(jià)格不變時(shí)登疗,集成電路上可容納的元器件的數(shù)目每隔18-24個(gè)月便會(huì)增加一倍,性能也將提升一倍。

由以上分析可知辐益,CPU主頻的提升可以有效的提升計(jì)算機(jī)的性能断傲,但需要注意的是由于機(jī)器功耗的影響, 并非計(jì)算機(jī)的主頻越高智政,計(jì)算機(jī)的性能就越強(qiáng)认罩,主頻的提高意味著CPU的功耗散熱也一并提高了,但CPU本身的散熱程度可能并跟不上這樣大幅度散熱的提升续捂,這就制約了CPU主頻不能一味的提升垦垂,過高的主頻也發(fā)揮不出他應(yīng)有的性能,甚至可能更低牙瓢。

CPU功耗 ~= 1/2 * 負(fù)載電容 * 電壓的平方 * 開關(guān)平率 * 晶體管數(shù)量

可以有效提升CPU性能的方式:

制程:

納米制程乔外,以14nm為例,其制程是指在芯片中一罩,線最小可以做到14納米的尺寸杨幼,縮小晶體管可以減少耗電量(晶體管一定的單位面積中),同時(shí)可以提升信號(hào)量在電路間的傳輸速度聂渊,縮小制程后差购,晶體管之間的電容也會(huì)更低,從而提升他們之間的開關(guān)頻率汉嗽∮樱可知功耗與電容成正比,所以傳輸速度更快饼暑,還更省電稳析。

要想提升計(jì)算機(jī)性能,從響應(yīng)時(shí)間上已經(jīng)很難突破了弓叛,所以后來開發(fā)人員針對(duì)計(jì)算機(jī)的吞吐率進(jìn)行了著手彰居。即通過超線程的多核手段來提升計(jì)算機(jī)的性能,也是通過并行提高性能的一種手段撰筷。

阿姆達(dá)爾定律:

并行優(yōu)化陈惰,并不是所有的問題都可以通過并行去優(yōu)化。

  • 條件1:需要進(jìn)行的計(jì)算本身可以進(jìn)行分解成幾個(gè)并行的任務(wù)毕籽,如乘法可以分解成多個(gè)加法抬闯。
  • 條件2:需要能夠分解的計(jì)算確保最后可以合并在一起。
  • 條件3:“匯總”階段無(wú)法再并行優(yōu)化关筒,只能單步執(zhí)行溶握。

優(yōu)化后的執(zhí)行時(shí)間 = 受優(yōu)化影響的執(zhí)行時(shí)間/加速倍數(shù)(并行處理數(shù)) + 不受影響的執(zhí)行時(shí)間。

計(jì)算機(jī)性能的提升方式蒸播,在“摩爾定律”和“并行計(jì)算”之外睡榆,還有加速大概率事件我通過流水線提高性能通過預(yù)測(cè)提高性能的方法肉微。

編譯器

匯編器是一種工具程序匾鸥,用于將匯編語(yǔ)言源程序轉(zhuǎn)換為機(jī)器語(yǔ)言。機(jī)器語(yǔ)言是一種數(shù)字語(yǔ)言碉纳,專門設(shè)計(jì)成能被計(jì)算機(jī)處理器(CPU)理解勿负。所有 x86 處理器都理解共同的機(jī)器語(yǔ)言。

匯編語(yǔ)言包含用短助記符如 ADD劳曹、MOV奴愉、SUB 和 CALL 書寫的語(yǔ)句。匯編語(yǔ)言與機(jī)器語(yǔ)言是一對(duì)一的關(guān)系:每一條匯編語(yǔ)言指令對(duì)應(yīng)一條機(jī)器語(yǔ)言指令铁孵。這就意味著不同型號(hào)的處理器如果所使用的機(jī)器語(yǔ)言(指令集)不同的話锭硼,那么他們的匯編語(yǔ)言也絕不相同。

高級(jí)語(yǔ)言如 Python蜕劝、C++ 和 Java與匯編語(yǔ)言和機(jī)器語(yǔ)言的關(guān)系是一對(duì)多檀头。比如,C++的一條語(yǔ)句就會(huì)擴(kuò)展為多條匯編指令或機(jī)器指令岖沛。一種語(yǔ)言暑始,如果它的源程序能夠在各種各樣的計(jì)算機(jī)系統(tǒng)中進(jìn)行編譯和運(yùn)行,那么這種語(yǔ)言被稱為是可移植的婴削。

匯編語(yǔ)言不是可移植的廊镜,因?yàn)樗菫樘囟ㄌ幚砥飨盗性O(shè)計(jì)的。目前廣泛使用的有多種不同的匯編語(yǔ)言唉俗,每一種都基于一個(gè)處理器系列嗤朴。 對(duì)于一些廣為人知的處理器系列如 Motorola 68×00、x86虫溜、SUN Sparc雹姊、Vax 和 IBM-370,匯編語(yǔ)言指令會(huì)直接與該計(jì)算機(jī)體系結(jié)構(gòu)相匹配吼渡,或者在執(zhí)行時(shí)用一種被稱為微代碼解釋器的處理器內(nèi)置程序來進(jìn)行轉(zhuǎn)換容为。

要讓一段C語(yǔ)言程序在一個(gè) Linux操作系統(tǒng)上跑起來乓序,我們需要把整個(gè)程序翻譯成一個(gè)匯編語(yǔ)言的程序寺酪,這個(gè)過程我們一般叫編譯成匯編代碼。針對(duì)匯編代碼替劈,我們可以再用匯編器翻譯成機(jī)器碼寄雀。這些機(jī)器碼由“0”和“1”組成的機(jī)器語(yǔ)言表示。對(duì)于C語(yǔ)言也有編譯器可以直接由C語(yǔ)言編譯為機(jī)器語(yǔ)言直接執(zhí)行陨献。這一條條機(jī)器碼盒犹,就是一條條的計(jì)算機(jī)指令的組成,類似我們漢字常用的不過4,5千,總共也才1W+急膀,就能幫助我們完成幾十萬(wàn)沮协,上百萬(wàn)的小說及文集的編寫。這樣一串串的16進(jìn)制數(shù)字卓嫂,就是我們CPU能夠真正認(rèn)識(shí)的計(jì)算機(jī)指令慷暂。為了讀起來方便,我們一般把對(duì)應(yīng)的二進(jìn)制數(shù)晨雳,用 16 進(jìn)制表示行瑞。

解釋型語(yǔ)言,是通過解釋器在程序運(yùn)行的時(shí)候逐句翻譯餐禁,而Java這樣使用虛擬機(jī)的語(yǔ)言血久,則是由虛擬機(jī)對(duì)編譯出來的字節(jié)碼進(jìn)行解釋成機(jī)器碼來最終執(zhí)行。


我們?nèi)粘S玫?Intel CPU帮非,有 2000 條左右的 CPU 指令氧吐。常見的指令可以分成五大類:

  • 第一類是算術(shù)類指令。我們的加減乘除末盔,在 CPU 層面副砍,都會(huì)變成一條條算術(shù)類指令。
  • 第二類是數(shù)據(jù)傳輸類指令庄岖。給變量賦值豁翎、在內(nèi)存里讀寫數(shù)據(jù),用的都是數(shù)據(jù)傳輸類指令隅忿。
  • 第三類是邏輯類指令心剥。邏輯上的與或非,都是這一類指令背桐。
  • 第四類是條件分支類指令盾致。日常我們寫的“if/else”月匣,其實(shí)都是條件分支類指令。
  • 最后一類是無(wú)條件跳轉(zhuǎn)指令。寫一些大一點(diǎn)的程序图仓,我們常常需要寫一些函數(shù)或者方法。在調(diào)用函數(shù)的時(shí)候跳芳,其實(shí)就是發(fā)起了一個(gè)無(wú)條件跳轉(zhuǎn)指令洲脂。
image

來看一段匯編代碼:

#include <time.h>
#include <stdlib.h>


int main()
{
  srand(time(NULL));
  int r = rand() % 2;
  int a = 10;
  if (r == 0)
  {
    a = 1;
  } else {
    a = 2;
  } 
==================
    if (r == 0)
  3b:   83 7d fc 00             cmp    DWORD PTR [rbp-0x4],0x0
  3f:   75 09                   jne    4a <main+0x4a>
    {
        a = 1;
  41:   c7 45 f8 01 00 00 00    mov    DWORD PTR [rbp-0x8],0x1
  48:   eb 07                   jmp    51 <main+0x51>
    }
    else
    {
        a = 2;
  4a:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
  51:   b8 00 00 00 00          mov    eax,0x0
    } 

機(jī)器語(yǔ)言,匯編語(yǔ)言励饵,編譯器:

過去編寫程序是通過紙帶打孔的方式驳癌,那么就只能通過“0,1”機(jī)器碼來進(jìn)行程序的編寫,后來發(fā)展出了匯編語(yǔ)言役听,匯編語(yǔ)言是一種更接近人類語(yǔ)言的語(yǔ)言颓鲜,用匯編器可以將匯編語(yǔ)言轉(zhuǎn)為機(jī)器語(yǔ)言表窘。匯編器則相當(dāng)于翻譯機(jī)的存在,可以根據(jù)具體的匯編指令轉(zhuǎn)為計(jì)算機(jī)能識(shí)別的二進(jìn)制碼甜滨,即各CPU開發(fā)商提供的機(jī)器碼乐严,因?yàn)?strong>匯編代碼和各CPU所能識(shí)別的機(jī)器碼是一一對(duì)應(yīng)的。

我們知道當(dāng)下所流行的各種匯編語(yǔ)言都是與處理器所一對(duì)一的衣摩,即當(dāng)初匯編語(yǔ)言的設(shè)計(jì)人員在編寫匯編語(yǔ)言的時(shí)候是通過CPU開發(fā)商提供的指令集手冊(cè)來對(duì)應(yīng)開發(fā)定義的匯編語(yǔ)言麦备,而各種型號(hào)不同的CPU所獨(dú)有的指令集則是燒錄到了CPU中。

  • C語(yǔ)言:C語(yǔ)言 -> 編譯器編譯 -> 匯編語(yǔ)言 -> 匯編器 -> 機(jī)器碼 || C語(yǔ)言 -> 編譯器編譯 -> 機(jī)器碼
  • Java:Java語(yǔ)言 -> 編譯器編譯 -> 字節(jié)碼 -> JVM -> 機(jī)器碼

參考:

  1. http://c.biancheng.net/view/450.html
  2. https://www.zhihu.com/question/38355661/answer/295808309
  3. https://zhuanlan.zhihu.com/p/53336801
  4. https://www.zhihu.com/question/39941182
  5. https://blog.csdn.net/zaassd/article/details/93916460
  6. https://blog.csdn.net/u013678930/article/details/51980460

寄存器

可以先讀:

內(nèi)存昭娩、寄存器和存儲(chǔ)器的區(qū)別

基本概念:

  • RAM(random access memory)即隨機(jī)訪問內(nèi)存凛篙,這種存儲(chǔ)器在斷電時(shí)將丟失其存儲(chǔ)內(nèi)容,故主要用于存儲(chǔ)短時(shí)間使用的程序栏渺。
  • ROM(Read-Only Memory)即只讀內(nèi)存呛梆,是一種只能讀出事先所存數(shù)據(jù)的固態(tài)半導(dǎo)體存儲(chǔ)器。

寄存器

寄存器是中央處理器內(nèi)的組成部分磕诊。寄存器是有限存貯容量的高速存貯部件填物,它們可用來暫存指令、數(shù)據(jù)和地址霎终。在中央處理器的控制部件中滞磺,包含的寄存器有指令寄存器(IR)程序計(jì)數(shù)器(PC)

  1. 寄存器是和CPU一起的莱褒,只能存少量的信息击困,但是存取速度特別快;
  2. 存儲(chǔ)器是指的是硬盤广凸,U盤阅茶,軟盤,光盤之類的外存儲(chǔ)工具谅海,速度最慢脸哀;
  3. 內(nèi)存指的是內(nèi)存條,由于一半的硬盤讀取速度很慢扭吁,所以用先將硬盤里面的東西讀取到內(nèi)存條里面撞蜂,然后在給CPU進(jìn)行處理,這樣是為了加快系統(tǒng)的運(yùn)行速度侥袜;

各類存儲(chǔ)器按照到cpu距離由近到遠(yuǎn)(訪存速度由高到低)排列分別是寄存器蝌诡,緩存(一,二系馆,三級(jí))送漠,主存,輔存由蘑。

其他優(yōu)質(zhì)解答:

寄存器的種類

邏輯上爷狈,我們可以認(rèn)為,CPU 其實(shí)就是由一堆寄存器組成的裳擎。而寄存器就是 CPU 內(nèi)部涎永,由多個(gè)觸發(fā)器或者鎖存器組成的簡(jiǎn)單電路。

N 個(gè)觸發(fā)器或者鎖存器鹿响,就可以組成一個(gè) N 位(Bit)的寄存器羡微,能夠保存 N 位的數(shù)據(jù)。比方說惶我,我們用的 64 位 Intel 服務(wù)器妈倔,寄存器就是 64 位的。

image

一個(gè) CPU 里面會(huì)有很多種不同功能的寄存器绸贡。其中有三個(gè)比較特殊的

  • PC 寄存器盯蝴,也叫指令地址寄存器。用來存放下一條需要執(zhí)行的計(jì)算機(jī)指令的內(nèi)存地址听怕。
  • 指令寄存器捧挺,用來存放當(dāng)前正在執(zhí)行的指令。
  • 條件碼寄存器尿瞭,用里面的一個(gè)一個(gè)標(biāo)記位(Flag)闽烙,存放 CPU 進(jìn)行算術(shù)或者邏輯計(jì)算的結(jié)果。

CPU 里面還有更多用來存儲(chǔ)數(shù)據(jù)和內(nèi)存地址的寄存器声搁。這樣的寄存器通常一類里面不止一個(gè)鸣峭。我們通常根據(jù)存放的數(shù)據(jù)內(nèi)容來給它們?nèi)∶郑热缯麛?shù)寄存器酥艳、浮點(diǎn)數(shù)寄存器摊溶、向量寄存器和地址寄存器等等。有些寄存器既可以存放數(shù)據(jù)充石,又能存放地址莫换,我們就叫它通用寄存器。

image

一個(gè)程序執(zhí)行的時(shí)候骤铃,CPU 會(huì)根據(jù) PC 寄存器里的地址拉岁,從內(nèi)存里面把需要執(zhí)行的指令讀取到指令寄存器里面執(zhí)行,然后根據(jù)指令長(zhǎng)度自增惰爬,開始順序讀取下一條指令喊暖。一個(gè)程序的一條條指令,在內(nèi)存里面是連續(xù)保存的撕瞧,也會(huì)一條條順序加載陵叽。

而有些特殊指令狞尔,比如 J 類指令,也就是跳轉(zhuǎn)指令巩掺,會(huì)修改 PC 寄存器里面的地址值偏序。這樣,下一條要執(zhí)行的指令就不是從內(nèi)存里面順序加載的了胖替。事實(shí)上研儒,這些跳轉(zhuǎn)指令的存在,也是我們可以在寫程序的時(shí)候独令,使用 if…else 條件語(yǔ)句和 while/for 循環(huán)語(yǔ)句的原因端朵。

CPU 中寄存器的數(shù)量并不多,一般使用的 Intel i7 CPU 只有 16 個(gè) 64 位寄存器燃箭,也就是16個(gè)8字節(jié)寄存器冲呢。

位運(yùn)算

計(jì)算機(jī)中的數(shù)在內(nèi)存中都是以二進(jìn)制形式進(jìn)行存儲(chǔ)的,用位運(yùn)算就是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作遍膜,因此其執(zhí)行效率非常高碗硬,在程序中盡量使用位運(yùn)算進(jìn)行操作,這會(huì)大大提高程序的性能瓢颅。當(dāng)然可讀性才是首要保證的目標(biāo)恩尾。

位操作符

  • & 與運(yùn)算 兩個(gè)位都是 1 時(shí),結(jié)果才為 1挽懦,否則為 0
1 0 0 1 1 
&
1 1 0 0 1 
------------------------------
1 0 0 0 1
  • |或運(yùn)算 兩個(gè)位都是 0 時(shí)翰意,結(jié)果才為 0刽辙,否則為 1
1 0 0 1 1 
| 
1 1 0 0 1 
------------------------------
1 1 0 1 1
  • ^ 異或運(yùn)算条辟,兩個(gè)位相同則為 0,不同則為 1
1 0 0 1 1 
^
1 1 0 0 1 
-----------------------------
0 1 0 1 0
  • ~ 取反運(yùn)算花沉,0 則變?yōu)?1渔嚷,1 則變?yōu)?0
~ 1 0 0 1 1 
-----------------------------
  0 1 1 0 0
  • << 左移運(yùn)算进鸠,向左進(jìn)行移位操作,高位丟棄形病,低位補(bǔ) 0
int a = 8;
a << 3;
移位前:0000 0000 0000 0000 0000 0000 0000 1000
移位后:0000 0000 0000 0000 0000 0000 0100 0000
  • >> 右移運(yùn)算客年,向右進(jìn)行移位操作,對(duì)無(wú)符號(hào)數(shù)漠吻,高位補(bǔ) 0量瓜,對(duì)于有符號(hào)數(shù),高位補(bǔ)符號(hào)位
unsigned int a = 8;
a >> 3;
移位前:0000 0000 0000 0000 0000 0000 0000 1000
移位后:0000 0000 0000 0000 0000 0000 0000 0001

int a = -8;
a >> 3;
移位前:1111 1111 1111 1111 1111 1111 1111 1000
移位后:1111 1111 1111 1111 1111 1111 1111 1111

在真實(shí)的程序里途乃,壓棧的不只有函數(shù)調(diào)用完成后的返回地址绍傲。比如函數(shù)A在調(diào)用B的時(shí)候,需要傳輸一些參數(shù)數(shù)據(jù)耍共,這些參數(shù)數(shù)據(jù)在寄存器不夠用的時(shí)候也會(huì)被壓入棧中烫饼。整個(gè)函數(shù) A 所占用的所有內(nèi)存空間猎塞,就是函數(shù) A 的棧幀

實(shí)際的程序棧布局枫弟,頂和底與我們的乒乓球桶相比是倒過來的邢享。底在最上面鹏往,頂在最下面淡诗,這樣的布局是因?yàn)闂5椎膬?nèi)存地址是在一開始就固定的(內(nèi)存地址偏大的那一邊)。而一層層壓棧之后伊履,棧頂元素地址的內(nèi)存地址是在逐漸變小而不是變大韩容。

觸發(fā)的StackOverFlow常見觸發(fā)方式:函數(shù)的遞歸調(diào)用,在棧中聲明一個(gè)非常占內(nèi)存的變量(巨大數(shù)組)唐瀑。

程序運(yùn)行常見的優(yōu)化方案:

把一個(gè)實(shí)際調(diào)用的函數(shù)產(chǎn)生的指令群凶,直接插入到調(diào)用該函數(shù)的位置,來替換對(duì)應(yīng)的函數(shù)調(diào)用指令哄辣。這種方案在如果被調(diào)用的函數(shù)中沒有調(diào)用其他函數(shù)的情況下请梢,還是可行的。這是一個(gè)常見的編譯器進(jìn)行自動(dòng)優(yōu)化的場(chǎng)景力穗,叫函數(shù)內(nèi)聯(lián)毅弧。

這里編譯器優(yōu)化的具體痛點(diǎn)并非簡(jiǎn)單的少了一些指令的執(zhí)行,而是函數(shù)頻繁進(jìn)出棧所花費(fèi)時(shí)間的開銷当窗,因?yàn)橄鄬?duì)于寄存器來說够坐,內(nèi)存是十分慢的。所以讓CPU反復(fù)操作內(nèi)存的話崖面,開銷還是很大的元咙。所以上述文本著重提示了被調(diào)用函數(shù)沒有調(diào)用其他函數(shù)的情況下,因?yàn)槿绻姓{(diào)用的話巫员,一是寄存器容量可能開銷不夠庶香,二是還是有操作主存的瓶頸在。

代碼演示:

#include <stdio.h>
int static add(int a, int b)
{
    return a+b;
}

int main()
{
    int x = 5;
    int y = 10;
    int u = add(x, y);
}
image

對(duì)應(yīng)上面函數(shù) add 的匯編代碼简识,main 函數(shù)調(diào)用 add 函數(shù)時(shí)赶掖,add 函數(shù)入口在 0~1 行,add 函數(shù)結(jié)束之后在 12~13 行财异。

我們?cè)谡{(diào)用第 34 行的 call 指令時(shí)倘零,會(huì)把當(dāng)前的 PC 寄存器里的下一條指令的地址壓棧,保留函數(shù)調(diào)用結(jié)束后要執(zhí)行的指令地址戳寸。而 add 函數(shù)的第 0 行呈驶,push rbp 這個(gè)指令,就是在進(jìn)行壓棧疫鹊。這里的 rbp 又叫棧幀指針(Frame Pointer)袖瞻,是一個(gè)存放了當(dāng)前棧幀位置的寄存器司致。push rbp 就把之前調(diào)用函數(shù),也就是 main 函數(shù)的棧幀的棧底地址聋迎,壓到棧中脂矫。

接著,第 1 行的一條命令 mov rbp, rsp 里霉晕,則是把 rsp 這個(gè)棧幀指針的值復(fù)制到 rbp 里庭再,而 rsp(記錄著add函數(shù)對(duì)應(yīng)棧幀的入口位置) 始終會(huì)指向棧頂。這個(gè)命令意味著牺堰,rbp(實(shí)際變動(dòng)指令) 這個(gè)棧幀指針指向的地址拄轻,變成當(dāng)前最新的棧頂,也就是 add 函數(shù)的棧幀的棧底地址了伟葫。

而在函數(shù) add 執(zhí)行完成之后恨搓,又會(huì)分別調(diào)用第 12 行的 pop rbp 來將當(dāng)前的棧頂出棧,這部分操作維護(hù)好了我們整個(gè)棧幀筏养。然后斧抱,我們可以調(diào)用第 13 行的 ret 指令,這時(shí)候同時(shí)要把 call 調(diào)用的時(shí)候壓入的 PC 寄存器里的下一條指令出棧渐溶,更新到 PC 寄存器中辉浦,將程序的控制權(quán)返回到出棧后的棧頂。

編譯掌猛、鏈接和裝載:拆解程序執(zhí)行

C 語(yǔ)言的文件在編譯后會(huì)生成以.o為尾綴的匯編語(yǔ)言文件盏浙,如 add_lib.o 以及 link_example.o 并不是一個(gè)可執(zhí)行文件而是目標(biāo)文件。只有通過鏈接器把多個(gè)目標(biāo)文件以及調(diào)用的各種函數(shù)庫(kù)鏈接起來荔茬,我們才能得到一個(gè)可執(zhí)行文件废膘。

C 語(yǔ)言代碼 - 匯編代碼 - 機(jī)器碼 這個(gè)過程,在我們的計(jì)算機(jī)上進(jìn)行的時(shí)候是由兩部分組成的慕蔚。

  • 第一部分:由編譯丐黄、匯編以及鏈接三個(gè)階段組成。在這三個(gè)階段完成之后孔飒,我們就生成了一個(gè)可執(zhí)行文件灌闺。
  • 第二部分,我們通過裝載器把可執(zhí)行文件裝載到內(nèi)存中坏瞄。CPU 從內(nèi)存中讀取指令和數(shù)據(jù)桂对,來開始真正執(zhí)行程序。

由上我們可以得知程序最終是通過裝載器加載程序及數(shù)據(jù)到內(nèi)存然后變成指令和數(shù)據(jù)的鸠匀,所以其實(shí)我們生成的可執(zhí)行代碼也并不僅僅是一條條的指令蕉斜。

image

ELF 格式

在Linux下,可執(zhí)行文件和目標(biāo)文件所使用的都是一種叫ELF的文件格式,這里面不僅存放了編譯成的匯編指令宅此,還保留了很多別的數(shù)據(jù)机错。

如函數(shù)名稱addmain 等父腕,以及定義的全局可以訪問的變量名稱弱匪,都存放在ELF格式文件里。這些名字和它們對(duì)應(yīng)的地址璧亮,在 ELF 文件里面萧诫,存儲(chǔ)在一個(gè)叫作符號(hào)表的位置里。符號(hào)表相當(dāng)于一個(gè)地址簿杜顺,把名字和地址關(guān)聯(lián)了起來财搁。

image
  • 重定位表:發(fā)生在鏈接前蘸炸,該文件中引用的多個(gè)函數(shù)的地址還不明確躬络,由于各函數(shù)文件可能寫在不同文件中,編譯后分別為目標(biāo)文件搭儒,除非經(jīng)過連接器鏈接后才能形成一個(gè)完整的可執(zhí)行文件穷当,所以在初期把不確定的函數(shù)地址記錄在這里,鏈接后進(jìn)行更改淹禾。

執(zhí)行流程: 鏈接器會(huì)掃描所有輸入的目標(biāo)文件馁菜,然后把所有符號(hào)表里的信息收集起來,構(gòu)成一個(gè)全局的符號(hào)表铃岔。然后再根據(jù)重定位表汪疮,把所有不確定要跳轉(zhuǎn)地址的代碼,根據(jù)符號(hào)表里面存儲(chǔ)的地址毁习,進(jìn)行一次修正雪标。最后砰奕,把所有的目標(biāo)文件的對(duì)應(yīng)段進(jìn)行一次合并,變成了最終的可執(zhí)行代碼。

所以一些文件即便是在同一計(jì)算機(jī)同一CPU上的不同操作系統(tǒng)上可能會(huì)出現(xiàn)一個(gè)可執(zhí)行而一個(gè)不可執(zhí)行的情況募疮。根本原因在于不同OS的裝載器所對(duì)應(yīng)的能解析的文件格式也是不同的。Linux的裝載器只能裝載EFL的文件格式胁后,而Windows是PE的讯泣。

PS:如果是編譯型的高級(jí)語(yǔ)言大都是先編譯成匯編語(yǔ)言,再匯編成機(jī)器碼執(zhí)行的嫁艇。也有通過解釋器朗伶,或者虛擬機(jī),轉(zhuǎn)換成實(shí)際的機(jī)器碼指令執(zhí)行的步咪。

這里Java實(shí)現(xiàn)跨平臺(tái)的機(jī)制則是:Java是通過實(shí)現(xiàn)不同平臺(tái)上的虛擬機(jī)论皆,然后即時(shí)翻譯javac生成的中間代碼來做到跨平臺(tái)的。跨平臺(tái)的工作被虛擬機(jī)開發(fā)人員來解決了(如同匯編)纯丸。各平臺(tái)的Java虛擬機(jī)通過連接器鏈接對(duì)應(yīng)的目標(biāo)文件后偏形,可以根據(jù)所在OS生成對(duì)應(yīng)格式的可執(zhí)行文件。來讓裝載器成功裝載觉鼻。

裝載器需要滿足兩個(gè)要求俊扭。

由上可知,由于連接器的作用坠陈,裝載器只需要把對(duì)應(yīng)的指令和數(shù)據(jù)加載到內(nèi)存里面來萨惑,讓 CPU 去執(zhí)行即可。但裝載器還需滿足兩個(gè)要求:

  • 第一仇矾,可執(zhí)行程序加載后占用的內(nèi)存空間應(yīng)該是連續(xù)的庸蔼。因?yàn)樘幚砥髟趫?zhí)行指令的時(shí)候,程序計(jì)數(shù)器是順序地一條一條指令執(zhí)行下去贮匕,所以就意味著這些指令應(yīng)該連續(xù)的存儲(chǔ)在一起姐仅。
  • 第二,我們需要同時(shí)加載很多個(gè)程序刻盐,并且不能讓程序自己規(guī)定在內(nèi)存中加載的位置掏膏。 雖然編譯出來的指令里已經(jīng)有了對(duì)應(yīng)的各種各樣的內(nèi)存地址,但是實(shí)際加載的時(shí)候敦锌,我們其實(shí)沒有辦法確保馒疹,這個(gè)程序一定加載在哪一段內(nèi)存地址上。因?yàn)槲覀儸F(xiàn)在的計(jì)算機(jī)通常會(huì)同時(shí)運(yùn)行很多個(gè)程序乙墙,可能你想要的內(nèi)存地址已經(jīng)被其他加載了的程序占用了颖变。

解決辦法:

分段: 在內(nèi)存中劃分一段連續(xù)的內(nèi)存空間,分配給裝載的程序听想,把連續(xù)的內(nèi)存空間和指令指向的內(nèi)存地址進(jìn)行映射腥刹。

其中指令里用到的內(nèi)存地址叫作虛擬內(nèi)存地址,實(shí)際內(nèi)存硬件里面的物理空間叫做物理內(nèi)存地址哗魂。程序員只需要關(guān)心虛擬內(nèi)存地址就行了肛走。所以我們只需要維護(hù)虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系的起始地址和對(duì)應(yīng)的空間大小就可以了。

問題:內(nèi)存碎片

解決辦法:

內(nèi)存交換录别。即先把內(nèi)存中某個(gè)程序所占用的內(nèi)存寫到硬盤上朽色,然后再?gòu)挠脖P上讀回內(nèi)存中,只不過讀回來的時(shí)候要緊貼上一個(gè)應(yīng)用所占用內(nèi)存空間的后面组题,形成連續(xù)的內(nèi)存占用葫男。

問題:性能瓶頸,內(nèi)存碎片和內(nèi)存交換的空間太大崔列,硬盤的讀寫速度太慢

解決辦法:

內(nèi)存分頁(yè)梢褐。原理是少出現(xiàn)一些內(nèi)存碎片旺遮。另外,當(dāng)需要進(jìn)行內(nèi)存交換的時(shí)候盈咳,讓需要交換寫入或者從磁盤裝載的數(shù)據(jù)更少一點(diǎn)耿眉。和分段這樣分配一整段連續(xù)的空間給到程序相比,分頁(yè)是把整個(gè)物理內(nèi)存空間切成一段段固定尺寸的大小鱼响。而對(duì)應(yīng)的程序所需要占用的虛擬內(nèi)存空間鸣剪,也會(huì)同樣切成一段段固定尺寸的大小。 這樣一個(gè)連續(xù)并且尺寸固定的內(nèi)存空間丈积,就是頁(yè)筐骇。一般頁(yè)遠(yuǎn)小于程序大小只有幾KB。

在內(nèi)存分頁(yè)的情況下江滨,即使內(nèi)存空間不夠铛纬,只需要讓現(xiàn)有的、正在運(yùn)行的其他程序唬滑,通過內(nèi)存交換釋放出一些內(nèi)存的頁(yè)出來告唆,一次性寫入磁盤的也只有少數(shù)的一個(gè)頁(yè)或者幾個(gè)頁(yè),不會(huì)花太多時(shí)間间雀,讓整個(gè)機(jī)器被內(nèi)存交換的過程給卡住悔详。

分頁(yè)的方式使得我們?cè)诩虞d程序的時(shí)候,不再需要一次性都把程序加載到物理內(nèi)存中惹挟。我們完全可以在進(jìn)行虛擬內(nèi)存和物理內(nèi)存的頁(yè)之間的映射之后,并不真的把頁(yè)加載到物理內(nèi)存里缝驳,而是只在程序運(yùn)行中连锯,需要用到對(duì)應(yīng)虛擬內(nèi)存頁(yè)里面的指令和數(shù)據(jù)時(shí),再加載到物理內(nèi)存里面去用狱。

虛擬內(nèi)存是指一段地址运怖,但是沒有加載到物理內(nèi)存里的時(shí)候其實(shí)就是放在硬盤上。

虛擬內(nèi)存夏伊,內(nèi)存交換摇展,內(nèi)存分頁(yè)三者結(jié)合下,其實(shí)運(yùn)行一個(gè)應(yīng)用程序需要用的必要內(nèi)存是很少的溺忧,也是為什么我們優(yōu)先的內(nèi)存可以運(yùn)行比我們內(nèi)存大很多的應(yīng)用的原因咏连。

JVM也是一個(gè)可執(zhí)行程序,同其他程序一樣依賴于操作系統(tǒng)的內(nèi)存管理和裝載程序鲁森,它可以按自己的方式去規(guī)劃它自身的內(nèi)存空間給就Java程序使用祟滴,而無(wú)需考慮怎么映射到物理內(nèi)存這些。這是承載他的操作系統(tǒng)需要做的事情歌溉,每個(gè)應(yīng)用程序都有固定使用的內(nèi)存空間的限度垄懂。

鏈接可以分動(dòng)、靜,共享運(yùn)行省內(nèi)存

用連接器進(jìn)行代碼合并的時(shí)候草慧,是靜態(tài)鏈接的方式桶蛔,相應(yīng)的,也有對(duì)應(yīng)的動(dòng)態(tài)鏈接漫谷。當(dāng)程序在進(jìn)行裝載的時(shí)候同一份代碼如果多個(gè)程序都靜態(tài)連接了一遍那么內(nèi)存中將會(huì)有多分同樣的代碼占用內(nèi)存羽圃,這對(duì)內(nèi)存耗費(fèi)也是非常大的。在動(dòng)態(tài)鏈接的過程中抖剿,我們想要“鏈接”的朽寞,不是存儲(chǔ)在硬盤上的目標(biāo)文件代碼,而是加載到內(nèi)存中的共享庫(kù)斩郎。

既然是共享代碼脑融,那么內(nèi)存中只要裝載一份即可。在程序鏈接的時(shí)候我們鏈接到該共享庫(kù)的內(nèi)存地址即可缩宜,不同系統(tǒng)下肘迎,共享庫(kù)的文件尾綴不同。Windows是.dll锻煌,Linux下是.so妓布。

image

共享庫(kù)文件代碼要求:

編譯出來的共享庫(kù)文件的指令代碼,是地址無(wú)關(guān)的宋梧。 原因是不同程序如果都用同一份共享代碼庫(kù)的話匣沼,不同程序該代碼的虛擬地址是不同的,雖然物理地址上是相同的捂龄,但是對(duì)于該共享代碼庫(kù)的虛擬地址和物理地址的映射就無(wú)法維護(hù)了释涛。

其中利用重定位表的代碼就是與地址相關(guān)的代碼。利用重定位表的代碼在程序鏈接的時(shí)候倦沧,就把函數(shù)調(diào)用后要跳轉(zhuǎn)訪問的地址確定下來了唇撬,這意味著,如果這個(gè)函數(shù)加載到一個(gè)不同的內(nèi)存地址展融,跳轉(zhuǎn)就會(huì)失敗窖认。

image

對(duì)于所有動(dòng)態(tài)鏈接共享庫(kù)的程序來講,雖然我們的共享庫(kù)用的都是同一段物理內(nèi)存地址告希,但是在不同的應(yīng)用程序里扑浸,它所在的虛擬內(nèi)存地址是不同的。

相對(duì)地址: 動(dòng)態(tài)代碼庫(kù)中的數(shù)據(jù)和指令的虛擬地址都是通過相對(duì)地址的方式互相訪問的暂雹。各種指令中使用到的內(nèi)存地址首装,給出的不是一個(gè)絕對(duì)的地址空間,而是一個(gè)相對(duì)于當(dāng)前指令偏移量的內(nèi)存地址杭跪。因?yàn)檎麄€(gè)共享庫(kù)是放在一段連續(xù)的虛擬內(nèi)存地址中的仙逻,無(wú)論裝載到哪一段地址驰吓,不同指令之間的相對(duì)地址都是不變的。

需要注意的是:雖然共享庫(kù)的代碼部分的物理內(nèi)存是共享的系奉,但是數(shù)據(jù)部分是各個(gè)動(dòng)態(tài)鏈接它的應(yīng)用程序里面各加載一份的檬贰。

全局偏移表(GOT): GOT表位于共享庫(kù)的數(shù)據(jù)段里。所以使用動(dòng)態(tài)鏈接的各個(gè)程序在共享庫(kù)中生成各自的GOT缺亮,每個(gè)程序的GOT都不同翁涤。 而 GOT 表里的數(shù)據(jù),則是在加載一個(gè)個(gè)共享庫(kù)的時(shí)候?qū)戇M(jìn)去的萌踱。

所以如果當(dāng)前運(yùn)行程序的共享庫(kù)指令需要用到外部的變量和函數(shù)地址的話葵礼,都會(huì)查詢 GOT,來找到當(dāng)前運(yùn)行程序的虛擬內(nèi)存地址并鸵。

image

不同的進(jìn)程鸳粉,調(diào)用同樣的共享庫(kù),各自 GOT 里面指向最終加載的動(dòng)態(tài)鏈接庫(kù)里面的虛擬內(nèi)存地址是不同的(因?yàn)楦鲬?yīng)用程序調(diào)用該函數(shù)的虛擬內(nèi)存地址是不同的)园担。

雖然不同的程序調(diào)用的同樣的動(dòng)態(tài)庫(kù)届谈,而各自的數(shù)據(jù)部分的內(nèi)存地址是獨(dú)立的,調(diào)用的又都是同一個(gè)動(dòng)態(tài)庫(kù)弯汰,但是不需要去修改動(dòng)態(tài)庫(kù)里面的代碼所使用的地址艰山,而是各個(gè)程序各自維護(hù)好自己的 GOT,能夠找到各自對(duì)應(yīng)的動(dòng)態(tài)庫(kù)的虛擬地址即可咏闪。

像動(dòng)態(tài)鏈接這樣通過修改“地址數(shù)據(jù)”來進(jìn)行間接跳轉(zhuǎn)曙搬,去調(diào)用一開始不能確定位置代碼的思路,在Java中汤踏,類似多態(tài)的實(shí)現(xiàn)织鲸。

如下代碼:

public class DynamicCode {// 動(dòng)態(tài)代碼庫(kù)
   
   private HashMap<String, Object> data; // 各類私有的數(shù)據(jù)部分 - 其中有一項(xiàng)是GOT
   
   public static void main(strs[] args) {// 公用代碼部分
   
   }
}

二進(jìn)制編碼

二進(jìn)制 -> 十進(jìn)制: 把從右到左的第 N 位,乘上一個(gè) 2 的 N 次方溪胶,然后加起來。N 從 0 開始記位稳诚。

示例:

0011
=====
0×2^3 + 0×2^2 + 1×2^1 + 1×2^0

十進(jìn)制 -> 二進(jìn)制: 短除法哗脖。也就是,把十進(jìn)制數(shù)除以 2 的余數(shù)扳还,作為最右邊的一位才避。然后用商繼續(xù)除以 2,把對(duì)應(yīng)的余數(shù)緊靠著剛才余數(shù)的右側(cè)氨距,這樣遞歸迭代桑逝,直到商為 0 就可以了。然后余數(shù)序列從下到上組成的序列就是該整數(shù)的二進(jìn)制表示俏让。

原碼楞遏,反碼茬暇,補(bǔ)碼

首先需要明白:在計(jì)算機(jī)中,數(shù)字都是用補(bǔ)碼來存儲(chǔ)的寡喝,而對(duì)于補(bǔ)碼的表示方式一個(gè)字節(jié)(8bit)的數(shù)字糙俗,規(guī)定1000 0000就是-128。而且對(duì)于正數(shù)而言预鬓,反碼巧骚,補(bǔ)碼是其原碼本身,負(fù)數(shù)通過補(bǔ)碼的方式在計(jì)算機(jī)中存儲(chǔ)格二。

二進(jìn)制負(fù)數(shù)的補(bǔ)碼劈彪,等于該負(fù)數(shù)取反碼再加1,也等于其正數(shù)按位取反再加1顶猜。

原碼: 0001 在原碼中就表示為 +1沧奴。而 1001 最左側(cè)的第一位是 1,所以它就表示 -1驶兜。這個(gè)其實(shí)就是整數(shù)的原碼表示法扼仲。

原碼表示法的問題

  • 0 有兩種表示方法:-0(1000) 及 +0(0000) - 補(bǔ)碼解決
  • +1(0001) 和 -1(1001) 相加不為 0 的情況。(1010 為 -2) - 反碼解決

反碼: 為了解決“正負(fù)相加不等于0”的問題抄淑,在“原碼”的基礎(chǔ)上屠凶,人們發(fā)明了“反碼”。“反碼”表示方式是用來處理負(fù)數(shù)的肆资,符號(hào)位置不變矗愧,其余位置相反

undefined

這樣正負(fù)兩數(shù)相加不為0的情況就解決了

反碼表示法的問題:

  • 但目前0還存在兩種表示方法郑原。

補(bǔ)碼:同樣是針對(duì)"負(fù)數(shù)"做處理的唉韭,從原來"反碼"的基礎(chǔ)上 +1。在補(bǔ)一位1的時(shí)候犯犁,要丟掉最高位(比如1111)属愤。

undefined

這樣就解決了+0和-0同時(shí)存在的問題,另外"正負(fù)數(shù)相加等于0"的問題酸役,同樣得到滿足住诸。同時(shí)還多了一位數(shù) -8。

用原碼的話涣澡,一個(gè)字節(jié)可以表示的范圍是:-127 ~ 127贱呐,用補(bǔ)碼的話表示的范圍是:-128 ~ 127

二進(jìn)制負(fù)數(shù)的補(bǔ)碼,等于該負(fù)數(shù)取反碼再加1入桂,也等于其正數(shù)按位取反再加1奄薇。

正數(shù)的反碼是其本身,負(fù)數(shù)的反碼是在其原碼的基礎(chǔ)上, 符號(hào)位不變抗愁,其余各個(gè)位取反馁蒂。

重點(diǎn):

說了那么多呵晚,只是描述一下三者的區(qū)別及由來。因?yàn)槲覀儚囊婚_始就說了远搪,計(jì)算機(jī)中是按補(bǔ)碼來存儲(chǔ)數(shù)據(jù)的劣纲,所以我們只要想辦法快速搞清楚一個(gè)計(jì)算機(jī)中的二進(jìn)制數(shù)的十進(jìn)制是多少。

我們?nèi)匀煌ㄟ^最左側(cè)第一位的0和1谁鳍,來判斷這個(gè)數(shù)的正負(fù)癞季。但是,我們不再把這一位當(dāng)成單獨(dú)的符號(hào)位倘潜,在剩下幾位計(jì)算出的十進(jìn)制前加上正負(fù)號(hào)绷柒,而是在計(jì)算整個(gè)二進(jìn)制值的時(shí)候,在左側(cè)最高位前面加個(gè)負(fù)號(hào)涮因。

比如流纹,一個(gè) 4 位的二進(jìn)制補(bǔ)碼數(shù)值 1011颤练,轉(zhuǎn)換成十進(jìn)制慈迈,就是 -1×2^3 + 0×2^2 + 1×2^1 + 1×2^0 = -5迄损。如果最高位是 1,這個(gè)數(shù)必然是負(fù)數(shù)澜掩;最高位是 0购披,必然是正數(shù)。并且肩榕,只有 0000 表示 0刚陡,1000 在這樣的情況下表示 -8。一個(gè) 4 位的二進(jìn)制數(shù)株汉,可以表示從 -8 到 7 這 16 個(gè)數(shù)筐乳,不會(huì)浪費(fèi)一位。

需要注意的是:Java中~表示按位取反乔妈,但是按位取反并不是反碼蝙云。

參考:

字符串的編碼

ASCII(American Standard Code for Interchange,美國(guó)信息交換標(biāo)準(zhǔn)代碼: 最早計(jì)算機(jī)只需要使用英文字符各聘,加上數(shù)字和一些特殊符號(hào)揣非,然后用8位的二進(jìn)制,就能表示我們?nèi)粘P枰乃凶址硕阋颍@個(gè)就是ASCII碼早敬。

undefined

ASCII 碼就好比一個(gè)字典忌傻,用 8 位二進(jìn)制中的 128 個(gè)不同的數(shù),映射到 128 個(gè)不同的字符里搞监。比如水孩,小寫字母 a 在 ASCII 里面,就是第 97 個(gè)琐驴,也就是二進(jìn)制的 0110 0001俘种,對(duì)應(yīng)的十六進(jìn)制表示就是 61。而大寫字母 A绝淡,就是第 65 個(gè)宙刘,也就是二進(jìn)制的 0100 0001,對(duì)應(yīng)的十六進(jìn)制表示就是 41牢酵。

需要注意的是:

在 ASCII 碼里面悬包,數(shù)字 9 不再像整數(shù)表示法里一樣,用 0000 1001 來表示馍乙,因?yàn)樗?ASCII 字符集中站位靠后布近。而是用 0011 1001 來表示,是第 57 位丝格。字符串 “15” 也不是用 0000 1111 這 8 位來表示撑瞧,而是變成兩個(gè)字符 1 和 5 連續(xù)放在一起,也就是 0011 0001 和 0011 0101铁追,需要用兩個(gè) 8 位來表示季蚂。 兩個(gè) 8 位的原因是,因?yàn)?4 位最高只能表示到(-8 - 7)琅束。

我們可以看到扭屁,最大的 32 位整數(shù),就是 2147483647涩禀。如果用整數(shù)表示法料滥,只需要 32 位就能表示了。但是如果用字符串來表示艾船,一共有 10 個(gè)字符葵腹,每個(gè)字符用 8 位的話,需要整整 80 位屿岂。比起整數(shù)表示法践宴,要多占很多空間。所以這也是為什么我們?cè)诖鎯?chǔ)數(shù)據(jù)的時(shí)候要通過二進(jìn)制序列化的方式來存儲(chǔ)爷怀。

Unicode: 和 ASCII 一樣是一個(gè)字符集阻肩,包含了 150 種語(yǔ)言的 14 萬(wàn)個(gè)不同的字符。

字符編碼則是對(duì)于字符集里的這些字符运授,怎么一一用二進(jìn)制表示出來的一個(gè)字典。我們上面說的 Unicode渡贾,就可以用 UTF-8、UTF-16空骚,乃至 UTF-32 來進(jìn)行編碼不脯,存儲(chǔ)成二進(jìn)制府怯。所以牺丙,有了 Unicode,其實(shí)我們可以用不止 UTF-8 一種編碼形式峦剔,只要?jiǎng)e人知道這套編碼規(guī)則吝沫,就可以正常傳輸惨险、顯示這段代碼。

undefined

同樣的文本恭朗,采用不同的編碼存儲(chǔ)下來痰腮。如果另外一個(gè)程序诽嘉,用一種不同的編碼方式來進(jìn)行解碼和展示,就會(huì)出現(xiàn)亂碼悦冀。

需要注意的是盒蟆,如果我們程序中使用了一些或者說存儲(chǔ)了一些不常用的古老字符集,那么可能 Unicode 字符集中并不存在這樣的字符寒屯,那么Unicode 會(huì)統(tǒng)一把這些字符記錄為 U+FFFD 這個(gè)編碼。如果用 UTF-8 的格式存儲(chǔ)下來菩掏,就是\xef\xbf\xbf。

參考:

電路

繼電器瞧栗,門電路

計(jì)算機(jī)不用十進(jìn)制而用二進(jìn)制原因如下:

電磁關(guān)系及繼電器的由來可參考繼電器

電信號(hào)在傳遞的時(shí)候系草,由于電線過長(zhǎng)會(huì)導(dǎo)致電阻過大此時(shí)對(duì)電壓要求會(huì)變大或者說用電器會(huì)出現(xiàn)無(wú)響應(yīng)的狀態(tài)找都。所以在進(jìn)行遠(yuǎn)距離信息傳遞的時(shí)候?yàn)榱吮苊怆娐愤^長(zhǎng)這種情況,發(fā)明了繼電器(電驛)晓猛。繼電器可以方便我們的電信號(hào)進(jìn)行傳導(dǎo)栗恩,或者根據(jù)需要組成我們想要的“與”,“或”市咆,“非”等的邏輯電路。

基本門電路的表示:

  • “與”電路的話相當(dāng)于我們?cè)陔娐飞洗?lián)兩個(gè)開關(guān)癞己,當(dāng)兩個(gè)開關(guān)都打開痹雅,電路才接通。
  • “或”相當(dāng)于我們?cè)谳斎攵送▋蓷l電路到輸出端愉耙,任意一條電路是打開狀態(tài),那么到輸出端的電路都是聯(lián)通的赌渣。
  • “非”相當(dāng)于從開關(guān)默認(rèn)關(guān)掉,只有通電有了磁場(chǎng)之后打開鸿竖,換成默認(rèn)是打開通電的悟泵,只有通電之后才關(guān)閉

這三種基本邏輯電路實(shí)現(xiàn)起來都比較簡(jiǎn)單,如果要做復(fù)雜的工作的話則需要更多的邏輯電路通過分層,組合的方式來實(shí)現(xiàn)右钾。

undefined

結(jié)論:我們通過電路的“開”和“關(guān)”怀伦,來表示“1”和“0”房待。就像晶體管在不同的情況下拜鹤,表現(xiàn)為導(dǎo)電的“1”和絕緣的“0”的狀態(tài)。

這些門電路惯裕,是我們創(chuàng)建 CPU 和內(nèi)存的基本邏輯單元。我們的各種對(duì)于計(jì)算機(jī)二進(jìn)制的“0”和“1”的操作猜煮,其實(shí)就是來自于門電路王带,叫作組合邏輯電路。

所謂門電路在數(shù)字電路中搞挣,所謂“門”就是只能實(shí)現(xiàn)基本邏輯關(guān)系的電路,即基本組成單位。最基本的邏輯關(guān)系是與翠语,或,非们童,最基本的邏輯門是與門,或門和非門。如下是最基本的門電路甘磨,其他復(fù)雜的門電路都是由這些門電路組合而成。他們是構(gòu)成現(xiàn)代計(jì)算機(jī)硬件的“積木”滋觉。

image

加法器

半加器

image

如圖通過一個(gè)異或門計(jì)算出個(gè)位,通過一個(gè)與門計(jì)算出是否進(jìn)位慎宾,就可以通過電路算出了一個(gè)一位數(shù)的加法。于是,后來就把這兩個(gè)門電路進(jìn)行打包,叫他為半加器。

全加器

半加器只能解決個(gè)位的運(yùn)算娇昙,二,四股毫,八位的輸入情況與個(gè)位的并不一樣。因?yàn)槎怀艘粋€(gè)加數(shù)和被加數(shù)之外,還需要加上來自個(gè)位的進(jìn)位信號(hào)宣肚,一共需要三個(gè)數(shù)進(jìn)行相加按价,才能得到結(jié)果。解決辦法即通過兩個(gè)半加器和一個(gè)或門就能組合成一個(gè)全加器。

image

W:為二位的值。有了全加器理論上兩個(gè)8位數(shù)的加法運(yùn)算就可以實(shí)現(xiàn)了蘸鲸。

image

有了全加器來實(shí)現(xiàn)各位的加法之后嗡载,更多位的加法計(jì)算只需要更多個(gè)全加器串聯(lián)起來即可埂息。如圖8 位加法器可以由 8 個(gè)全加器串聯(lián)而成。

可以看到的是拾弃,個(gè)位和其他高位不同,個(gè)位只需要一個(gè)半加器即可。而最高位即最左側(cè)的一位表示的是我們的加法是否溢出了增蹭。整個(gè)電路中有這樣一個(gè)信號(hào)來表示我們所做的加法運(yùn)算是否溢出了霎奢,可以給到硬件層面的其它標(biāo)志位中,來讓計(jì)算機(jī)知曉這樣算溢出了,以便得到計(jì)算機(jī)硬件層面的支持舞箍。

算術(shù)邏輯單元(ALU):是中央處理器的執(zhí)行單元,是所有中央處理器的核心組成部分,由與門和或門構(gòu)成的算術(shù)邏輯單元,主要功能是進(jìn)行二進(jìn)制的算術(shù)運(yùn)算,如加減乘數(shù)(不包括整數(shù)除法)。

image

乘法器

13 * 9 = 117 的二進(jìn)制豎式表:

image

如圖從人為計(jì)算角度看,實(shí)際計(jì)算機(jī)層面的乘法是由位移加法組合而成。因?yàn)槭嵌M(jìn)制乘法鸟款,所以乘數(shù)的各位和被乘數(shù)的乘積不是全部為0就是把被乘數(shù)復(fù)制一份下來。需要注意的是乘數(shù)的每位進(jìn)行一次乘積運(yùn)算之后处渣,下一次的運(yùn)算結(jié)果就需要向高位移動(dòng)一位。

乘法器的簡(jiǎn)單實(shí)現(xiàn):根據(jù)乘數(shù)從個(gè)位一直到高位通過一個(gè)門電路來控制每次位移的輸出信號(hào)琅翻,來判斷和被乘數(shù)的結(jié)果是全部為0輸出還是把被乘數(shù)復(fù)制一份輸出凌外,并將結(jié)果累加到統(tǒng)一某個(gè)寄存器上即可摄欲。如圖:

image

具體:先拿乘數(shù)最右側(cè)的個(gè)位乘以被乘數(shù)按咒,然后把結(jié)果存入到寄存器中智袭,然后吼野,把被乘數(shù)左移一位腰奋,把乘數(shù)右移一位馏臭,仍然用乘數(shù)的個(gè)位去乘以被乘數(shù)绕沈,然后把結(jié)果加到剛才的寄存器上乍狐。反復(fù)重復(fù)這一步驟,直到兩者分別不能再左移和右移位置固逗。這樣浅蚪,乘數(shù)和被乘數(shù)其實(shí)僅僅需要簡(jiǎn)單的加法器(每位乘法及位移后的結(jié)果的累加),一個(gè)可以支持其左移一位的電路和一個(gè)右移一位的電路烫罩,以及一個(gè)開關(guān)(判斷乘數(shù)的每位和被乘數(shù)乘積的結(jié)果是復(fù)制還是0)就能完成整個(gè)乘法。 如圖所示

這里的控制測(cè)試贝攒,其實(shí)就是通過一個(gè)時(shí)鐘信號(hào)盗誊,來控制左移、右移以及重新計(jì)算乘法和加法的時(shí)機(jī)隘弊。

但由之前的加法器可以得知哈踱,乘法器所謂的位移+加法,每一位的操作并不是獨(dú)立的梨熙,乘數(shù)的高位在進(jìn)行乘法運(yùn)算之前任然需要低位的運(yùn)算結(jié)果(移位)才可以开镣。比如運(yùn)算數(shù)是 4 位數(shù),就要等待 4 組“位移 + 加法”的操作咽扇。

如果要優(yōu)化整個(gè)乘法器的運(yùn)算邪财,可以看到影響執(zhí)行速度的原因有如下幾點(diǎn):

  1. 每組位移+加法運(yùn)算都具有強(qiáng)關(guān)聯(lián)及先后關(guān)系。
  2. 控制測(cè)試進(jìn)行每次進(jìn)行一次位移及加法所需要等待的時(shí)鐘頻率质欲。(最大)
  3. 每組乘法結(jié)果通過加法器在寄存器上進(jìn)行結(jié)果累加的時(shí)候受門延時(shí)影響卧蜓。

解決辦法:

電路進(jìn)行展開:首先針對(duì)第一點(diǎn),我們上面所看到的豎列圖分析出所謂的每組位移+加法的強(qiáng)關(guān)聯(lián)關(guān)系及先后關(guān)系是因?yàn)槲覀內(nèi)朔治霭殉ǎ鋵?shí)對(duì)于計(jì)算機(jī)的電路而言,當(dāng)相加的兩個(gè)數(shù)是確定的榨惠,那高位是否會(huì)進(jìn)位其實(shí)也是確定的奋早。也就是說,對(duì)于計(jì)算機(jī)的電路而言赠橙,高位和地位可以同時(shí)出結(jié)果耽装,電路是天然并行的,也就不存在所謂的強(qiáng)關(guān)聯(lián)關(guān)系期揪。同時(shí)對(duì)應(yīng)的第三點(diǎn)的門延時(shí)也就只有一組加法進(jìn)行運(yùn)算的門延時(shí)存在了掉奄,即3T的門延時(shí)。并且移位需要等待的時(shí)鐘頻率也不需要了。

其實(shí)可以看到乘法器的實(shí)現(xiàn)方式共有兩種:

  1. RISC:即精簡(jiǎn)的電路姓建,較少的門電路和寄存器诞仓,但相對(duì)的需要更長(zhǎng)的門延遲和時(shí)鐘周期來計(jì)算一個(gè)指令。
  2. CISC:比較復(fù)雜的電路速兔,更短的門延遲和時(shí)鐘周期來計(jì)算一個(gè)復(fù)雜的指令墅拭。

這之間的權(quán)衡,其實(shí)就是計(jì)算機(jī)體系結(jié)構(gòu)中 RISC 和 CISC 的經(jīng)典戰(zhàn)爭(zhēng)

定點(diǎn)數(shù)涣狗,浮點(diǎn)數(shù)

定點(diǎn)數(shù)

定點(diǎn)數(shù)的表示方法:用 4 個(gè)比特來表示 0~9 的整數(shù)谍婉,那么 32 個(gè)比特就可以表示 8 個(gè)這樣的整數(shù)。然后我們把最右邊的 2 個(gè) 0~9 的整數(shù)镀钓,當(dāng)成小數(shù)部分穗熬;把左邊 6 個(gè) 0~9 的整數(shù),當(dāng)成整數(shù)部分丁溅。這樣唤蔗,我們就可以用 32 個(gè)比特,來表示從 0 到 999999.99 這樣 1 億個(gè)實(shí)數(shù)了唧瘾。

比如:0001 1001 就是 19措译,而不是 25。

undefined

用二進(jìn)制來表示十進(jìn)制的編碼方式饰序,叫作BCD 編碼领虹。

缺點(diǎn):能表示數(shù)值太小,原本32位的數(shù)值表示方法求豫,能表示的數(shù)值的最大值是42億塌衰。用BCD編碼的話最大只能表示到100w,一般超市常用蝠嘉。

浮點(diǎn)數(shù)

https://zhuanlan.zhihu.com/p/75581822

可以看到如果都像定點(diǎn)數(shù)那樣表示最疆,那么無(wú)疑會(huì)出現(xiàn)較大的實(shí)數(shù)無(wú)法表示的情況。就需要用到科學(xué)計(jì)數(shù)法的表示方法蚤告,其中浮點(diǎn)數(shù)的科學(xué)計(jì)數(shù)法的表示有一個(gè) IEEE 754的標(biāo)準(zhǔn)努酸,它定義了兩個(gè)基本的格式。 一個(gè)是用 32 比特表示單精度的浮點(diǎn)數(shù)杜恰,也就是我們常常說的 float 或者 float32 類型获诈。另外一個(gè)是用 64 比特表示雙精度的浮點(diǎn)數(shù),也就是我們平時(shí)說的 double 或者 float64 類型心褐。

根據(jù)國(guó)際標(biāo)準(zhǔn)IEEE 754舔涎,任意一個(gè)二進(jìn)制浮點(diǎn)數(shù)V可以表示成下面的函數(shù)形式:

image
  • 符號(hào):(-1)^s表示符號(hào)位,當(dāng)s=0逗爹,V為正數(shù)亡嫌;當(dāng)s=1,V為負(fù)數(shù)。
  • 尾數(shù):M表示有效數(shù)字挟冠,大于等于1刃泌,小于2德迹。
  • 階碼:2^E表示指數(shù)位棺妓。

例如:

  • 十進(jìn)制的6.0且改,寫成二進(jìn)制是110.0,相當(dāng)于1.10×2^2持舆。那么色瘩,按照上面V的格式,可以得出s=0逸寓,M=1.10居兆,E=2。
  • 十進(jìn)制的-5.0竹伸,寫成二進(jìn)制是-101.0泥栖,相當(dāng)于-1.01×2^2。那么勋篓,s=1吧享,M=1.01,E=2譬嚣。

IEEE 754規(guī)定钢颂,對(duì)于32位的浮點(diǎn)數(shù),最高的1位是符號(hào)位s拜银,接著的8位是指數(shù)E殊鞭,剩下的23位為有效數(shù)字M。
對(duì)于64位的浮點(diǎn)數(shù)尼桶,最高的1位是符號(hào)位S操灿,接著的11位是指數(shù)E,剩下的52位為有效數(shù)字M泵督。

因?yàn)镋為8位趾盐,所以它的取值范圍為0~255。由于科學(xué)計(jì)數(shù)法中的E是可以出現(xiàn)負(fù)數(shù)的(為了表示更小的實(shí)數(shù)小腊,無(wú)限接近與0的數(shù))救鲤,所以IEEE 754規(guī)定,E的真實(shí)值必須再加上一個(gè)中間數(shù)溢豆,對(duì)于8位的E,這個(gè)中間數(shù)是127(1-254瘸羡,0和255是標(biāo)志位)漩仙;對(duì)于11位的E,這個(gè)中間數(shù)是1023。

比如队他,2^10的E是10卷仑,所以保存成32位浮點(diǎn)數(shù)時(shí),必須保存成10+127=137麸折,即10001001锡凝。

然后,指數(shù)E還可以再分成三種情況:

  1. E不全為0或不全為1垢啼。這時(shí)窜锯,浮點(diǎn)數(shù)就采用上面的規(guī)則表示,即指數(shù)E的計(jì)算值(10001001 = 137)減去127(或1023)芭析,得到真實(shí)值锚扎,再將有效數(shù)字M前加上第一位的1。
  2. E全為0馁启。這時(shí)驾孔,浮點(diǎn)數(shù)的指數(shù)E等于1-127(或者1-1023),有效數(shù)字M不再加上第一位的1惯疙,而是還原為0.xxxxxx的小數(shù)翠勉。這樣做是為了表示±0,以及接近于0的很小的數(shù)字霉颠。
  3. E全為1对碌。這時(shí),如果有效數(shù)字M全為0掉分,表示±無(wú)窮大(正負(fù)取決于符號(hào)位s)俭缓;如果有效數(shù)字M不全為0,表示這個(gè)數(shù)不是一個(gè)數(shù)(NaN)酥郭。

在這樣的浮點(diǎn)數(shù)表示下华坦,不考慮符號(hào)的話,浮點(diǎn)數(shù)能夠表示的最小的數(shù)和最大的數(shù)不从,差不多是 1.17 * 10^-383.40 * 10^38惜姐,表示的數(shù)值范圍就大很多了。此時(shí)f為23個(gè)0椿息,e為-126 和 f為23個(gè)1歹袁,e為127。

對(duì)于浮點(diǎn)數(shù)的整數(shù)部分十進(jìn)制轉(zhuǎn)二進(jìn)制是如上公式寝优,小數(shù)部分則與整數(shù)部分并不相同条舔。

0.1001 這樣一個(gè)二進(jìn)制小數(shù),換算成 10 進(jìn)制就是

1*2^-1 + 0*2^-2 + 1*2^-3 + 0*2^-4` = 0.5625

小數(shù)的二進(jìn)制表示方法和整數(shù)不同乏矾,比如9.1中的 9 可以表示為 1001孟抗,而作為小數(shù)的 0.1 則和整數(shù)轉(zhuǎn)化成二進(jìn)制除2的做法不同迁杨,小數(shù)的方式就是乘以 2,然后看看是否超過 1凄硼。如果超過 1铅协,我們就記下 1,并把結(jié)果減去 1摊沉,進(jìn)一步循環(huán)操作狐史。在這里,我們就會(huì)看到说墨,0.1 其實(shí)變成了一個(gè)無(wú)限循環(huán)的二進(jìn)制小數(shù)骏全。0.0++0011++0011這里的“0011”會(huì)無(wú)限循環(huán)下去。如圖:

image

然后婉刀,我們把整數(shù)部分和小數(shù)部分拼接在一起吟温,9.1 這個(gè)十進(jìn)制數(shù)就變成了 1001.000110011...這樣的二進(jìn)制表示了。由于浮點(diǎn)數(shù)是用二進(jìn)制科學(xué)計(jì)數(shù)法來進(jìn)行表示的突颊,所以這個(gè)數(shù)就變成了1.001000110011...*2^3鲁豪。對(duì)應(yīng)到浮點(diǎn)數(shù)二進(jìn)制的科學(xué)計(jì)數(shù)法表達(dá)式中,這里的符號(hào)位 s = 0律秃,對(duì)應(yīng)的有效位 f=001000110011...爬橡。因?yàn)橛行閒最長(zhǎng)只有23位,于是f=00100011001100110011 ==001==棒动。最后的一個(gè)“0011”循環(huán)中的最后一個(gè)“1”會(huì)被截?cái)嗟舨谏辍?duì)應(yīng)的指數(shù)為為3,需要加上127船惨,則二進(jìn)制應(yīng)該是130的表示柜裸,即10000010。 - 0.3也是如此表示粱锐。

image

最終可以得到9.1的二進(jìn)制表示:0 10000010 0010001100110011==001==疙挺。

反向過來,這個(gè)二進(jìn)制的值表示成10進(jìn)制的話怜浅,實(shí)際準(zhǔn)確的值則是 9.09999942779541015625铐然。

模擬地址:IEEE-754 Floating Point Converter

由上小數(shù)部分的二進(jìn)制轉(zhuǎn)化可知:0.1~0.9 這 9 個(gè)數(shù),其中只有 0.5 能夠被精確地表示成二進(jìn)制的浮點(diǎn)數(shù)恶座。而其他的都只是一個(gè)近似的表達(dá)搀暑。

參考:

浮點(diǎn)數(shù)的加法原理

先對(duì)齊、再計(jì)算跨琳。

兩個(gè)浮點(diǎn)數(shù)的指數(shù)位可能是不一樣的自点,所以只要把兩個(gè)的指數(shù)位,變成一樣的脉让,然后只去計(jì)算有效位的加法就好了桂敛。

image

可以看到浮點(diǎn)數(shù)的加法過程冈绊,其中指數(shù)位較小的數(shù),需要在有效位進(jìn)行右移埠啃,在右移的過程中,最右側(cè)的有效位就被丟棄掉了伟恶。這會(huì)導(dǎo)致對(duì)應(yīng)的指數(shù)位較小的數(shù)碴开,在加法發(fā)生之前,就丟失精度博秫。兩個(gè)相加數(shù)的指數(shù)位差的越大潦牛,位移的位數(shù)越大,可能丟失的精度也就越大挡育。32 位浮點(diǎn)數(shù)的有效位長(zhǎng)度一共只有 23 位巴碗,如果兩個(gè)數(shù)的指數(shù)位差出 23 位,較小的數(shù)右移 24 位之后即寒,所有的有效位就都丟失了橡淆。這也就意味著,雖然浮點(diǎn)數(shù)可以表示上到3.40*10^38母赵,下到 1.17×10-38這樣的數(shù)值范圍逸爵。但是在實(shí)際計(jì)算的時(shí)候,只要兩個(gè)數(shù)凹嘲,差出224师倔,也就是差不多 1600 萬(wàn)倍,那這兩個(gè)數(shù)相加之后周蹭,結(jié)果完全不會(huì)變化趋艘。

如下代碼:

public class FloatPrecision {
  public static void main(String[] args) {
    float a = 20000000.0f;
    float b = 1.0f;
    float c = a + b;
    System.out.println("c is " + c);
    float d = c - a;
    System.out.println("d is " + d);
  }
}
=====
c is 2.0E7
d is 0.0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凶朗,隨后出現(xiàn)的幾起案子瓷胧,更是在濱河造成了極大的恐慌,老刑警劉巖俱尼,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖单,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡遇八,警方通過查閱死者的電腦和手機(jī)矛绘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刃永,“玉大人货矮,你說我怎么就攤上這事∷构唬” “怎么了囚玫?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵喧锦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我抓督,道長(zhǎng)燃少,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任铃在,我火速辦了婚禮阵具,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘定铜。我一直安慰自己阳液,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布揣炕。 她就那樣靜靜地躺著帘皿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畸陡。 梳的紋絲不亂的頭發(fā)上鹰溜,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音丁恭,去河邊找鬼奉狈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涩惑,可吹牛的內(nèi)容都是我干的仁期。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼竭恬,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼跛蛋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痊硕,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赊级,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后岔绸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體理逊,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年盒揉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晋被。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刚盈,死狀恐怖羡洛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藕漱,我是刑警寧澤欲侮,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布崭闲,位于F島的核電站,受9級(jí)特大地震影響威蕉,放射性物質(zhì)發(fā)生泄漏刁俭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一韧涨、第九天 我趴在偏房一處隱蔽的房頂上張望薄翅。 院中可真熱鬧,春花似錦氓奈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至斋射,卻和暖如春育勺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罗岖。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工涧至, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桑包。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓南蓬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親哑了。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赘方,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353