国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
換種方法學(xué)操作系統(tǒng)輕松入門(mén)Linux內(nèi)核(與圖靈機(jī)不同馮諾依曼機(jī)是一個(gè)實(shí)際的體系結(jié)構(gòu))CPU就是從EIP指向的那個(gè)地址取過(guò)來(lái)一條指令執(zhí)行

(與圖靈機(jī)不同馮諾依曼機(jī)是一個(gè)實(shí)際的體系結(jié)構(gòu))CPU就是從EIP指向的那個(gè)地址取過(guò)來(lái)一條指令執(zhí)行


         (將編寫(xiě)好的程序和數(shù)據(jù)先存入存儲(chǔ)器中然后啟動(dòng)計(jì)算機(jī)工作這就是存儲(chǔ)程序的基本含義)



計(jì)算機(jī)已成為現(xiàn)代人日常工作、學(xué)習(xí)和生活中必不可少的工具。操作系統(tǒng)是計(jì)算機(jī)之魂,作為用戶使用計(jì)算機(jī)的接口,它負(fù)責(zé)調(diào)度執(zhí)行各個(gè)用戶程序,使計(jì)算機(jī)完成特定的任務(wù);作為計(jì)算機(jī)硬件資源的管理者,它負(fù)責(zé)協(xié)調(diào)計(jì)算機(jī)中各類設(shè)備高效地工作。操作系統(tǒng)的重要性不言而喻。


對(duì)于軟件工程師,理解操作系統(tǒng)的工作原理和關(guān)鍵機(jī)制是設(shè)計(jì)高質(zhì)量應(yīng)用程序的前提,但要做到這一點(diǎn)是十分困難的。

一方面,操作系統(tǒng)設(shè)計(jì)涉及計(jì)算機(jī)科學(xué)與工程學(xué)科的方方面面,包括數(shù)據(jù)結(jié)構(gòu)與算法、計(jì)算機(jī)組成與系統(tǒng)結(jié)構(gòu)、計(jì)算機(jī)網(wǎng)絡(luò),甚至程序設(shè)計(jì)語(yǔ)言與編譯系統(tǒng)等核心知識(shí),以及并發(fā)、同步和通信等核心概念。

另一方面,作為一個(gè)復(fù)雜龐大的軟件產(chǎn)品,理解操作系統(tǒng)更需要理論與實(shí)踐深度結(jié)合。

操作系統(tǒng)的相關(guān)學(xué)習(xí)資料十分豐富。有闡述基本原理者,有剖析典型系統(tǒng)者,還有構(gòu)造示例系統(tǒng)者;有面向?qū)I(yè)理論者,亦有面向應(yīng)用實(shí)踐者。角度多種多樣,內(nèi)容簡(jiǎn)繁不一。

本書(shū)的最大特點(diǎn)在于作者結(jié)合其多年的Linux操作系統(tǒng)實(shí)際教學(xué)經(jīng)驗(yàn)編撰而成。作為一位經(jīng)驗(yàn)豐富的高級(jí)軟件工程師和專業(yè)教師,本書(shū)作者基于自己學(xué)習(xí)和研究Linux的心得,創(chuàng)新性地以一個(gè)mykernel和MenuOS為基礎(chǔ)實(shí)驗(yàn)平臺(tái)進(jìn)行教學(xué)和實(shí)驗(yàn)組織,實(shí)現(xiàn)了理論學(xué)習(xí)與工程實(shí)踐的自然融合,達(dá)到了事半功倍的效果。

同時(shí),書(shū)中設(shè)計(jì)了豐富的單元測(cè)試題和實(shí)驗(yàn),引導(dǎo)讀者循序漸進(jìn)地掌握所學(xué)知識(shí),并有效地促進(jìn)讀者深入思考和實(shí)踐所學(xué)內(nèi)容。

作者基于本書(shū)開(kāi)設(shè)的操作系統(tǒng)課程,其教學(xué)形式涉及面對(duì)面的課堂教學(xué)和在線慕課教學(xué),選課對(duì)象既包括軟件工程碩士,又包括一般工程實(shí)踐者,學(xué)習(xí)人數(shù)已數(shù)以萬(wàn)計(jì)。本書(shū)的出版體現(xiàn)了作者認(rèn)真吸收大量的學(xué)員反饋,不斷優(yōu)化課程的教學(xué)內(nèi)容和過(guò)程組織的成果。

本文重點(diǎn)介紹計(jì)算機(jī)的工作原理,具體涉及存儲(chǔ)程序計(jì)算機(jī)工作模型、基本的匯編語(yǔ)言,以及C語(yǔ)言程序匯編出來(lái)的匯編代碼如何在存儲(chǔ)程序計(jì)算機(jī)工作模型上一步步地執(zhí)行。其中重點(diǎn)分析了函數(shù)調(diào)用堆棧相關(guān)匯編指令,如call/ret和pushl/popl。

存儲(chǔ)程序計(jì)算機(jī)工作模型

存儲(chǔ)程序計(jì)算機(jī)的概念雖然簡(jiǎn)單,但在計(jì)算機(jī)發(fā)展史上具有革命性的意義,至今為止仍是計(jì)算機(jī)發(fā)展史上非常有意義的發(fā)明。一臺(tái)硬件有限的計(jì)算機(jī)或智能手機(jī)能安裝各種各樣的軟件,執(zhí)行各種各樣的程序,這在人們看起來(lái)都理所當(dāng)然,其實(shí)背后是存儲(chǔ)程序計(jì)算機(jī)的功勞。

存儲(chǔ)程序計(jì)算機(jī)的主要思想是將程序存放在計(jì)算機(jī)存儲(chǔ)器中,然后按存儲(chǔ)器中的存儲(chǔ)程序的首地址執(zhí)行程序的第一條指令,以后就按照該程序中編寫(xiě)好的指令執(zhí)行,直至程序執(zhí)行結(jié)束。

相信很多人特別是學(xué)習(xí)計(jì)算機(jī)專業(yè)的人都聽(tīng)說(shuō)過(guò)圖靈機(jī)和馮·諾依曼機(jī)。圖靈機(jī)關(guān)注計(jì)算的哲學(xué)定義,是一種虛擬的抽象機(jī)器,是對(duì)現(xiàn)代計(jì)算機(jī)的首次描述。只要提供合適的程序,圖靈機(jī)就可以做任何運(yùn)算?;趫D靈機(jī)建造的計(jì)算機(jī)都是在存儲(chǔ)器中存儲(chǔ)數(shù)據(jù),程序的邏輯都是嵌入在硬件中的。

與圖靈機(jī)不同,馮·諾依曼機(jī)是一個(gè)實(shí)際的體系結(jié)構(gòu),我們稱作馮·諾依曼體系結(jié)構(gòu),它至今仍是幾乎所有計(jì)算機(jī)平臺(tái)的基礎(chǔ)。我們都知道“庖丁解?!边@個(gè)成語(yǔ),比喻經(jīng)過(guò)反復(fù)實(shí)踐,掌握了事物的客觀規(guī)律,做事得心應(yīng)手,運(yùn)用自如。馮·諾依曼體系結(jié)構(gòu)就是各種計(jì)算機(jī)體系結(jié)構(gòu)需要遵從的一個(gè)“客觀規(guī)律”,了解它對(duì)于理解計(jì)算機(jī)和操作系統(tǒng)非常重要。下面,我們就來(lái)看看什么是馮·諾依曼體系結(jié)構(gòu)。

在1944~1945年期間,馮·諾依曼指出程序和數(shù)據(jù)在邏輯上是相同的,程序也可以存儲(chǔ)在存儲(chǔ)器中。馮·諾依曼體系結(jié)構(gòu)的要點(diǎn)包括:

馮·諾依曼體系結(jié)構(gòu)如圖1-1所示,其中運(yùn)算器、存儲(chǔ)器、控制器、輸入設(shè)備和輸出設(shè)備5大基本類型部件組成了計(jì)算機(jī)硬件;

圖1-1 馮·諾依曼體系結(jié)構(gòu)

計(jì)算機(jī)內(nèi)部采用二進(jìn)制來(lái)表示指令和數(shù)據(jù);

將編寫(xiě)好的程序和數(shù)據(jù)先存入存儲(chǔ)器中,然后啟動(dòng)計(jì)算機(jī)工作,這就是存儲(chǔ)程序的基本含義。

計(jì)算機(jī)硬件的基礎(chǔ)是CPU,它與內(nèi)存和輸入/輸出(I/O)設(shè)備進(jìn)行交互,從輸入設(shè)備接收數(shù)據(jù),向輸出設(shè)備發(fā)送數(shù)據(jù)。

CPU由運(yùn)算器(算術(shù)邏輯單元ALU)、控制器和一些寄存器組成。有一個(gè)非常重要的寄存器稱為程序計(jì)數(shù)器,在IA32(x86-32)中是EIP,指示將要執(zhí)行的下一條指令在存儲(chǔ)器中的地址。

C/C++程序員可以將EIP看作一個(gè)指針,因?yàn)樗偸侵赶蚰骋粭l指令的地址。CPU就是從EIP指向的那個(gè)地址取過(guò)來(lái)一條指令執(zhí)行,執(zhí)行完后EIP會(huì)自動(dòng)加一,執(zhí)行下一條指令,然后再取下一條指令執(zhí)行,CPU像“貪吃蛇”一樣總是在內(nèi)存里“吃”指令。

CPU、內(nèi)存和I/O設(shè)備通過(guò)總線連接。內(nèi)存中存放指令和數(shù)據(jù)。

“計(jì)算機(jī)內(nèi)部采用二進(jìn)制來(lái)表示指令和數(shù)據(jù)”表明,指令和數(shù)據(jù)的功能和處理是不同的,但都可以用二進(jìn)制的方式存儲(chǔ)在內(nèi)存中。

上述第3個(gè)要點(diǎn)指出了馮·諾依曼體系結(jié)構(gòu)的核心是存儲(chǔ)程序計(jì)算機(jī)。

我們用程序員的思維來(lái)對(duì)存儲(chǔ)程序計(jì)算機(jī)進(jìn)行抽象,如圖1-2所示。

圖1-2 存儲(chǔ)程序計(jì)算機(jī)工作原理示意圖

我們可以把CPU抽象成一個(gè)for循環(huán),因?yàn)樗偸窃趫?zhí)行next instruction(下一條指令),然后從內(nèi)存里取下一條指令來(lái)執(zhí)行。從這個(gè)角度來(lái)看,內(nèi)存保存指令和數(shù)據(jù),CPU負(fù)責(zé)解釋和執(zhí)行這些指令,它們通過(guò)總線連接起來(lái)。這里揭示了計(jì)算機(jī)可以自動(dòng)化執(zhí)行程序的原理。

這里存在一個(gè)問(wèn)題,CPU能識(shí)別什么樣的指令,我們這里需要有一個(gè)定義。學(xué)過(guò)編程的讀者基本都知道API,也就是應(yīng)用程序編程接口。

而對(duì)于程序員來(lái)講,還有一個(gè)稱為ABI的接口,它主要是一些指令的編碼。在指令編碼方面,我們不會(huì)涉及那么具體的細(xì)節(jié),而只會(huì)涉及和匯編相關(guān)的內(nèi)容。

至于這些指令是如何編碼成二進(jìn)制機(jī)器指令的,我們不必關(guān)心,有興趣的讀者可以查找指令編碼的相關(guān)資料。此外,這些指令會(huì)涉及一些寄存器,這些寄存器有些約定,我們約定什么樣的指令該用什么寄存器。

同時(shí),我們也需要了解寄存器的布局。還有,大多數(shù)指令可以直接訪問(wèn)內(nèi)存,對(duì)于x86-32計(jì)算機(jī)指令集來(lái)講,這也是一個(gè)重要的概念。

對(duì)于x86-32計(jì)算機(jī),有一個(gè)EIP寄存器指向內(nèi)存的某一條指令,EIP是自動(dòng)加一的(不是一個(gè)字節(jié),也不是32位,而是加一條指令),雖然x86-32中每條指令占的存儲(chǔ)空間不一樣,但是它能智能地自動(dòng)加到下一條指令,它還可以被其他指令修改,如call、ret、jmp等,這些指令對(duì)應(yīng)C語(yǔ)言中的函數(shù)調(diào)用、return和if else語(yǔ)句。

現(xiàn)在絕大多數(shù)具有計(jì)算功能的設(shè)備,小到智能手機(jī),大到超級(jí)計(jì)算機(jī),基本的核心部分可以用馮·諾依曼體系結(jié)構(gòu)(存儲(chǔ)程序計(jì)算機(jī))來(lái)描述。因此,存儲(chǔ)程序計(jì)算機(jī)是一個(gè)非?;镜母拍?,是我們理解計(jì)算機(jī)系統(tǒng)工作原理的基礎(chǔ)。

x86-32匯編基礎(chǔ)

Intel處理器系列也稱為x86,經(jīng)過(guò)不斷的發(fā)展,體系結(jié)構(gòu)經(jīng)歷了16位、32位和64位幾個(gè)關(guān)鍵階段。32位的體系結(jié)構(gòu)稱為IA32,64位體系結(jié)構(gòu)稱為x86-64,但為了明確區(qū)分兩者,本書(shū)中把32位體系結(jié)構(gòu)稱作x86-32。本書(shū)與Linux內(nèi)核采用的匯編格式保持一致,采用AT&T匯編格式。

1.x86-32 CPU的寄存器

為了便于讀者理解,下面先來(lái)介紹16位的8086 CPU的寄存器。8086 CPU中總共有14個(gè)16位的寄存器:AX、BX、CX、DX、SP、BP、SI、DI、IP、FLAG、CS、DS、SS和ES。這14個(gè)寄存器分為通用寄存器、控制寄存器和段寄存器3種類型。

通用寄存器又分為數(shù)據(jù)寄存器、指針寄存器和變址寄存器。

AX、BX、CX和DX統(tǒng)稱為數(shù)據(jù)寄存器。

qAX(Accumulator):累加寄存器,也稱為累加器。

qBX(Base):基地址寄存器。

qCX(Count):計(jì)數(shù)器寄存器。

qDX(Data):數(shù)據(jù)寄存器。

SP和BP 統(tǒng)稱為指針寄存器。

qSP(Stack Pointer):堆棧指針寄存器。

qBP(Base Pointer):基指針寄存器。

SI和DI統(tǒng)稱為變址寄存器。

qSI(Source Index):源變址寄存器。

qDI(Destination Index):目的變址寄存器。

控制寄存器主要分為指令指針寄存器和標(biāo)志寄存器。

qIP(Instruction Pointer):指令指針寄存器。

qFLAG:標(biāo)志寄存器。

段寄存器主要有代碼段寄存器、數(shù)據(jù)段寄存器、堆棧段寄存器和附加段寄存器。

qCS(Code Segment):代碼段寄存器。

qDS(Data Segment):數(shù)據(jù)段寄存器。

qSS(Stack Segment):堆棧段寄存器。

qES(Extra Segment):附加段寄存器。

以上數(shù)據(jù)寄存器AX、BX、CX和DX都可以當(dāng)作兩個(gè)單獨(dú)的8位寄存器來(lái)使用,如圖1-3所示,以AX寄存器為例。

圖1-3 AX 寄存器示意圖

qAX寄存器可以分為兩個(gè)獨(dú)立的8位的AH和AL寄存器。

qBX寄存器可以分為兩個(gè)獨(dú)立的8位的BH和BL寄存器。

qCX寄存器可以分為兩個(gè)獨(dú)立的8位的CH和CL寄存器。

qDX寄存器可以分為兩個(gè)獨(dú)立的8位的DH和DL寄存器。

除了上面4個(gè)數(shù)據(jù)寄存器以外,其他寄存器均不可以分為兩個(gè)獨(dú)立的8位寄存器。注意,每個(gè)分開(kāi)的寄存器都有自己的名稱,可以獨(dú)立存取。程序員可以利用數(shù)據(jù)寄存器的這種“可分可合”的特性,靈活地處理字/字節(jié)的信息。

了解了16位的8086 CPU的寄存器之后,我們?cè)賮?lái)看32位的寄存器。

IA32所含有的寄存器包括:

q4個(gè)數(shù)據(jù)寄存器(EAX、EBX、ECX和EDX)。

q2個(gè)變址和指針寄存器(ESI和EDI)。

q2個(gè)指針寄存器(ESP和EBP)。

q6個(gè)段寄存器(ES、CS、SS、DS、FS和GS)。

q1個(gè)指令指針寄存器(EIP)。

q1個(gè)標(biāo)志寄存器(EFlags)。

32位寄存器只是把對(duì)應(yīng)的16位寄存器擴(kuò)展到了32位,如圖1-4所示為EAX寄存器示意圖,它增加了一個(gè)E。所有開(kāi)頭為E的寄存器,一般是32位的。

EAX累加寄存器、EBX基址寄存器、ECX計(jì)數(shù)寄存器和EDX數(shù)據(jù)寄存器都是通用寄存器,程序員在寫(xiě)匯編碼時(shí)可以自己定義如何使用它們。EBP是堆?;分羔?,比較重要;ESI、EDI是變址寄存器;ESP也比較重要,它是堆棧棧頂寄存器

這里可能會(huì)涉及堆棧的概念,學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)課程的讀者應(yīng)該知道堆棧的概念,本書(shū)后面會(huì)具體講到push指令壓棧和pop指令出棧,它是向一個(gè)堆棧里面壓一個(gè)數(shù)據(jù)和從堆棧里面彈出一個(gè)數(shù)據(jù)。這些都是32位的通用寄存器。

圖1-4 EAX寄存器示意圖

值得注意的是在16位CPU中,AX、BX、CX和DX不能作為基址和變址寄存器來(lái)存放存儲(chǔ)單元的地址,但在32位CPU中,32位寄存器EAX、EBX、ECX和EDX不僅可以傳送數(shù)據(jù)、暫存數(shù)據(jù)保存算術(shù)邏輯運(yùn)算結(jié)果,還可以作為指針寄存器,因此這些32位寄存器更具通用性。

除了通用寄存器外,還有一些段寄存器。雖然段寄存器在本書(shū)中用得比較少,但還是要了解一下。除了CS、DS、ES和SS外,還有其他附加段寄存器FS和GS。

常用的是CS寄存器和SS寄存器。我們的指令都存儲(chǔ)在代碼段,在定位一個(gè)指令時(shí),使用CS:EIP來(lái)準(zhǔn)確指明它的地址。

也就是說(shuō),首先需要知道代碼在哪一個(gè)代碼段里,然后需要知道指令在代碼段內(nèi)的相對(duì)偏移地址EIP,一般用CS:EIP準(zhǔn)確地標(biāo)明一個(gè)指令的內(nèi)存地址。還有堆棧段,每一個(gè)進(jìn)程都有自己的堆棧段(在Linux系統(tǒng)里,每個(gè)進(jìn)程都有一個(gè)內(nèi)核態(tài)堆棧和一個(gè)用戶態(tài)堆棧)。

標(biāo)志寄存器的功能細(xì)節(jié)比較復(fù)雜煩瑣,本書(shū)就不仔細(xì)介紹了,讀者知道標(biāo)志寄存器可以保存當(dāng)前的一些狀態(tài)就可以了。

現(xiàn)在主流的計(jì)算機(jī)大多都是采用64位的CPU,那么我們也需要簡(jiǎn)單了解一下x86-64的寄存器。實(shí)際上,64位和32位的寄存器差別也不大,它只是從32位擴(kuò)展到了64位。前面帶個(gè)“R”的都是指64位寄存器,如RAX、RBX、RCX、RDX、RBP、RSI、RSP,還有Flags改為了RFLAGS,EIP改為了RIP。

另外,還增加了更多的通用寄存器,如R8、R9等,這些增加的通用寄存器和其他通用寄存器只是名稱不一樣,在使用中都是遵循調(diào)用者使用規(guī)則,簡(jiǎn)單說(shuō)就是隨便用。

2.數(shù)據(jù)格式

在Intel的術(shù)語(yǔ)規(guī)范中,字表示16位數(shù)據(jù)類型;在IA32中,32位數(shù)稱為雙字;在x86-64中,64位數(shù)稱為四字。圖1-5所示為C語(yǔ)言中基本類型的IA32表示,其中列出的匯編代碼后綴在匯編代碼中會(huì)經(jīng)常看到。

圖1-5 C語(yǔ)言中基本類型的IA32表示

3.尋址方式和常用匯編指令

匯編指令包含操作碼和操作數(shù),其中操作數(shù)分為以下3種:

(1)立即數(shù)即常數(shù),如$8,用$開(kāi)頭后面跟一個(gè)數(shù)值;

(2)寄存器數(shù),表示某個(gè)寄存器中保存的值,如%eax;而對(duì)字節(jié)操作而言,是8個(gè)單字節(jié)寄存器中的一個(gè),如%al(EAX寄存器中的低8位);

(3)存儲(chǔ)器引用,根據(jù)計(jì)算出的有效地址來(lái)訪問(wèn)存儲(chǔ)器的某個(gè)位置。

還有一些常見(jiàn)的匯編指令,我們來(lái)看它們是如何工作的。最常見(jiàn)的匯編指令是mov指令,movl中的l是指32位,movb中的b是指8位,movw中的w是指16位,movq中的q是指64位。我們以32位為主進(jìn)行介紹。

首先介紹寄存器尋址。所謂寄存器尋址就是操作的是寄存器,不和內(nèi)存打交道,如%eax,其中%開(kāi)頭后面跟一個(gè)寄存器名稱。

movl %eax,%edx

上述代碼把寄存器%eax的內(nèi)容放到%edx中。如果把寄存器名當(dāng)作C語(yǔ)言代碼中的變量名,它就相當(dāng)于:

edx = eax;

立即尋址(immediate)是用一個(gè)$開(kāi)頭后面跟一個(gè)數(shù)值。例如:

movl $0x123, %edx

就是把0x123這個(gè)十六進(jìn)制的數(shù)值直接放到EDX寄存器中。如果把寄存器名當(dāng)作C語(yǔ)言代碼中的變量名,它就相當(dāng)于:

edx = 0x123;

立即尋址也和內(nèi)存沒(méi)有關(guān)系。

直接尋址(direct)是直接用一個(gè)數(shù)值,開(kāi)頭沒(méi)有$符號(hào)。開(kāi)頭有$符號(hào)的數(shù)值表示這是一個(gè)立即數(shù);沒(méi)有$符號(hào)表示這是一個(gè)地址。例如:

movl 0x123, %edx

就是把十六進(jìn)制的0x123內(nèi)存地址所指向的那塊內(nèi)存里存儲(chǔ)的數(shù)據(jù)放到EDX寄存器里,這相當(dāng)于C語(yǔ)言代碼:

edx = *(int*)0x123;

把0x123這個(gè)數(shù)值強(qiáng)制轉(zhuǎn)化為一個(gè)32位的int型變量的指針,再用一個(gè)*取它指向的值,然后放到EDX寄存器中,這就稱為直接尋址。

換句話說(shuō),就是用內(nèi)存地址直接訪問(wèn)內(nèi)存中的數(shù)據(jù)。

間接尋址就是寄存器加個(gè)小括號(hào)。舉例說(shuō)明,%ebx這個(gè)寄存器中存的值是一個(gè)內(nèi)存地址,加個(gè)小括號(hào)表示這個(gè)內(nèi)存地址所存儲(chǔ)的數(shù)據(jù),我們把它放到EDX寄存器中:

move (%ebx), %edx

就相當(dāng)于:

edx = *(int*)ebx;

把這個(gè)EBX寄存器中存儲(chǔ)的數(shù)值強(qiáng)制轉(zhuǎn)化為一個(gè)32位的int型變量的指針,再用一個(gè)*取它指向的值,然后放到EDX寄存器中,這稱為間接尋址。

變址尋址比間接尋址稍微復(fù)雜一點(diǎn)。例如:

movl 4(%ebx), %edx

讀者會(huì)發(fā)現(xiàn)代碼中“(%ebx)”前面出現(xiàn)了一個(gè)4,也就是在間接尋址的基礎(chǔ)上,在原地址上加上一個(gè)立即數(shù)4,相當(dāng)于:

edx = *(int*)(ebx+4)

把這個(gè)EBX寄存器存儲(chǔ)的數(shù)值加4,然后強(qiáng)制轉(zhuǎn)化為一個(gè)32位的int類型的指針,再用一個(gè)*取它指向的值,然后放到EDX寄存器中,這稱為變址尋址。

如上所述的CPU對(duì)寄存器和內(nèi)存的操作方法,都是比較基礎(chǔ)的知識(shí),需要牢固掌握。

x86-32中的大多數(shù)指令都能直接訪問(wèn)內(nèi)存,但還有一些指令能直接對(duì)內(nèi)存操作,如push/pop。它們根據(jù)ESP寄存器指向的內(nèi)存位置進(jìn)行壓棧和出棧操作,注意這是指令執(zhí)行過(guò)程中默認(rèn)使用了特定的寄存器。

還需要特別說(shuō)明的是,本書(shū)中使用的是AT&T匯編格式,這也是Linux內(nèi)核使用的匯編格式,與Intel匯編格式略有不同。

我們?cè)谒阉髻Y料時(shí)可能會(huì)遇到Intel匯編代碼,一般來(lái)說(shuō),全是大寫(xiě)字母的一般是Intel匯編,全是小寫(xiě)字母的一般是AT&T匯編。

本書(shū)中的代碼用到的寄存器名稱都遵守AT&T匯編格式采用全小寫(xiě)的方式,而正文中需要使用寄存器名稱一般使用大寫(xiě),因?yàn)樗鼈兪鞘鬃帜缚s寫(xiě)。

還有幾個(gè)重要的指令:pushl/popl和call/ret。pushl表示32位的push,如:

pushl %eax

就是把EAX寄存器的值壓到堆棧棧頂。它實(shí)際上做了這樣兩個(gè)動(dòng)作,其中第一個(gè)動(dòng)作為:

subl $4, %esp

把堆棧的棧頂ESP寄存器的值減4。因?yàn)槎褩J窍蛳略鲩L(zhǎng)的,所以用減指令subl,也就是在棧頂預(yù)留出一個(gè)存儲(chǔ)單元。第二個(gè)動(dòng)作為:

movl %eax, (%esp)

把ESP寄存器加一個(gè)小括號(hào)(間接尋址),就是把EAX寄存器的值放到ESP寄存器所指向的地方,這時(shí)ESP寄存器已經(jīng)指向預(yù)留出的存儲(chǔ)單元了。

接下來(lái)介紹popl指令,如:

popl %eax

就是從堆棧的棧頂取一個(gè)存儲(chǔ)單元(32位數(shù)值),從堆棧棧頂?shù)奈恢梅诺紼AX寄存器里,這稱為出棧。出棧同樣對(duì)應(yīng)兩個(gè)操作:

movl (%esp), %eax

addl $4, %esp

第一步是把棧頂?shù)臄?shù)值放到EAX寄存器里,然后用指令addl把棧頂加4,相當(dāng)于棧向上回退了一個(gè)存儲(chǔ)單元的位置,也就是棧在收縮。每次執(zhí)行指令pushl棧都在增長(zhǎng),執(zhí)行指令popl棧都在收縮。

call指令是函數(shù)調(diào)用,調(diào)用一個(gè)地址。例如:

call 0x12345

上述代碼實(shí)際上做了兩個(gè)動(dòng)作,如下兩條偽指令,注意,這兩個(gè)動(dòng)作并不存在實(shí)際對(duì)應(yīng)的指令,我們用“(*)”來(lái)特別標(biāo)記一下,這兩個(gè)動(dòng)作是由硬件一次性完成的。出于安全方面的原因,EIP寄存器不能被直接使用和修改。

pushl %eip (*)

movl $0x12345, %eip (*)

上述偽指令先是把當(dāng)前的EIP寄存器壓棧,把0x12345這個(gè)立即數(shù)放到EIP寄存器里,該寄存器是用來(lái)告訴CPU下一條指令的存儲(chǔ)地址的。

把當(dāng)前的EIP寄存器的值壓棧就是把下一條指令的地址保存起來(lái),然后給EIP寄存器又賦了一個(gè)新值0x12345,也就是CPU執(zhí)行的下一條指令就是從0x12345位置取得的。

再看與call指令對(duì)應(yīng)的指令ret,ret指令是函數(shù)返回,例如:

ret

上述代碼實(shí)際上做了一個(gè)動(dòng)作,如下一條偽指令,注意,這個(gè)動(dòng)作并不存在實(shí)際對(duì)應(yīng)的指令,我們用“(*)”來(lái)特別標(biāo)記一下,這個(gè)動(dòng)作是由硬件一次性完成的。出于安全方面的原因,EIP寄存器不能被直接使用和修改。

popl %eip(*)

也就是把當(dāng)前堆棧棧頂?shù)囊粋€(gè)存儲(chǔ)單元(一般是由call指令壓棧的內(nèi)容)放到EIP寄存器里。上述pushl/popl和call/ret匯編指令對(duì)應(yīng)執(zhí)行的動(dòng)作匯總?cè)鐖D1-6所示。

圖1-6 pushl/popl和call/ret匯編指令

總結(jié)一下,call指令對(duì)應(yīng)了C語(yǔ)言里我們調(diào)用一個(gè)函數(shù),也就是call一個(gè)函數(shù)的起始地址。ret指令是把調(diào)用函數(shù)時(shí)壓棧的EIP寄存器的值(即call指令的下一條指令的地址)還原到EIP寄存器里,ret指令之后的下一條指令也就回到函數(shù)調(diào)用位置的下一條指令。

換句話說(shuō)就是函數(shù)調(diào)用結(jié)束了,繼續(xù)執(zhí)行函數(shù)調(diào)用之后的下一條指令,這和C語(yǔ)言中的函數(shù)調(diào)用過(guò)程是嚴(yán)格對(duì)應(yīng)的。但是需要注意的是,帶個(gè)“(*)”的指令表示這些指令都是不能被程序員直接使用的,是偽指令。

因?yàn)镋IP寄存器不能被程序員直接修改,只能通過(guò)專用指令(如call、ret和jmp等)間接修改。

若程序員可以直接修改EIP寄存器,那么會(huì)有嚴(yán)重的安全隱患。讀者可以思考一下為什么?我們就不展開(kāi)討論了。

4.匯編代碼范例解析

我們已經(jīng)對(duì)指令和寄存器有了大致的了解,下面做一個(gè)練習(xí)來(lái)驗(yàn)證我們的理解。在堆棧為空棧的情況下,執(zhí)行如下匯編代碼片段之后,堆棧和寄存器都發(fā)生了哪些變化?

1 push $8

2 movl %esp, %ebp

3 subl $4, %esp

4 movl $8, (%esp)

我們分析這段匯編代碼每一步都做了什么動(dòng)作。首先在堆棧為空棧的情況下,EBP和ESP寄存器都指向棧底。

第1行語(yǔ)句是將立即數(shù)8壓棧(即先把ESP寄存器的值減4,然后把立即數(shù)8放入當(dāng)前堆棧棧頂位置)。

第2行語(yǔ)句是把ESP寄存器的值放到EBP寄存器里,就是把ESP寄存器存儲(chǔ)的內(nèi)容放到EBP寄存器中,把EBP寄存器也指向當(dāng)前ESP寄存器所指向的位置。

換句話說(shuō),在堆棧中又新建了一個(gè)邏輯上的空棧,這一點(diǎn)理解起來(lái)并不容易,讀者暫時(shí)理解不了也沒(méi)有關(guān)系。本書(shū)后面會(huì)將C語(yǔ)言程序匯編成匯編代碼來(lái)分析函數(shù)調(diào)用是如何實(shí)現(xiàn)的,其中會(huì)涉及函數(shù)調(diào)用堆??蚣堋?/p>

第3行語(yǔ)句中的指令是subl,是把ESP寄存器存儲(chǔ)的數(shù)值減4,也就是說(shuō),棧頂指針ESP寄存器向下移了一個(gè)存儲(chǔ)單元(4個(gè)字節(jié))。

最后一行語(yǔ)句是把立即數(shù)8放到ESP寄存器所指向的內(nèi)存地址,也就是把立即數(shù)8通過(guò)間接尋址放到堆棧棧頂。

本例是關(guān)于棧和寄存器的一些操作的,我們可以對(duì)照上述文字說(shuō)明一步一步跟蹤堆棧和寄存器的變化過(guò)程,以便更加準(zhǔn)確地理解指令的作用。

再來(lái)看一段匯編代碼,同樣在堆棧為空棧的情況下,執(zhí)行如下匯編代碼片段之后,堆棧和寄存器都發(fā)生了哪些變化?

1 pushl $8

2 movl %esp, %ebp

3 pushl $8

同樣我們也分析一下這段匯編代碼每一步都做了什么動(dòng)作。首先在堆棧為空棧的情況下EBP和ESP寄存器都指向棧底。

第1行語(yǔ)句是將立即數(shù)8壓棧,即堆棧多了一個(gè)存儲(chǔ)單元并存了一個(gè)立即數(shù)8,同時(shí)也改變了ESP寄存器。

第2行語(yǔ)句把ESP寄存器的值放到EBP寄存器里,堆棧空間沒(méi)有變化,但EBP寄存器發(fā)生了變化。

第3行語(yǔ)句將立即數(shù)8壓棧,即堆棧多了一個(gè)存儲(chǔ)單元并存了一個(gè)立即數(shù)8。

讀者會(huì)發(fā)現(xiàn),這個(gè)例子和上一個(gè)例子的實(shí)際效果是完全一樣的。

小試牛刀之后,再看下面這段更加復(fù)雜一點(diǎn)的匯編代碼:

1 pushl $8

2 movl %esp, %ebp

3 pushl %esp

4 pushl $8

5 addl $4, %esp

6 popl %esp

這段匯編代碼同樣首先在堆棧為空棧的情況下EBP和ESP寄存器都指向棧底。

第1行語(yǔ)句是將立即數(shù)8壓棧,即堆棧多了一個(gè)存儲(chǔ)單元并保存立即數(shù)8,同時(shí)也改變了ESP寄存器。

第2行語(yǔ)句是把ESP寄存器的值放到EBP寄存器里,堆棧空間沒(méi)有變化,但EBP寄存器發(fā)生了變化。

第3行語(yǔ)句是把ESP寄存器的內(nèi)容壓棧到堆棧棧頂?shù)拇鎯?chǔ)單元里。需要注意的是,pushl指令本身會(huì)改變ESP寄存器。“pushl %esp”語(yǔ)句相當(dāng)于如下兩條指令:

subl $4, %esp

movl %esp, (%esp)

顯然,在保存ESP寄存器的值到堆棧中之前改變了ESP寄存器,保存到棧頂?shù)臄?shù)據(jù)應(yīng)該是當(dāng)前ESP寄存器的值減4。ESP寄存器的值發(fā)生了變化,同時(shí)??臻g多了一個(gè)存儲(chǔ)單元保存變化后的ESP寄存器的值。

第4行語(yǔ)句是將立即數(shù)8壓棧,即堆棧多了一個(gè)存儲(chǔ)單元保存立即數(shù)8,同時(shí)也改變了ESP寄存器。

第5行語(yǔ)句是把ESP寄存器的值加4,這相當(dāng)于堆棧空間收縮了一個(gè)存儲(chǔ)單元。

最后一條語(yǔ)句相當(dāng)于如下兩條指令:

movl (%esp), %esp

addl $4, %esp

也就是把當(dāng)前棧頂?shù)臄?shù)據(jù)放到ESP寄存器中,然后又將ESP寄存器加4。這一段代碼比較復(fù)雜,因?yàn)镋SP寄存器既作為操作數(shù),又被pushl/popl指令在執(zhí)行過(guò)程中使用和修改。

讀者需要仔細(xì)分析和思考這段匯編代碼以理解整個(gè)執(zhí)行過(guò)程,本書(shū)后續(xù)內(nèi)容會(huì)結(jié)合C代碼的函數(shù)調(diào)用和函數(shù)返回,來(lái)進(jìn)一步理解這段匯編代碼中涉及的建立一個(gè)函數(shù)調(diào)用堆棧和拆除一個(gè)函數(shù)調(diào)用堆棧。

《庖丁解牛Linux內(nèi)核分析》

孟寧 婁嘉鵬 劉宇棟 著

本書(shū)從理解計(jì)算機(jī)硬件的核心工作機(jī)制(存儲(chǔ)程序計(jì)算機(jī)和函數(shù)調(diào)用堆棧)和用戶態(tài)程序如何通過(guò)系統(tǒng)調(diào)用陷入內(nèi)核(中斷異常)入手,通過(guò)上下兩個(gè)方向雙向夾擊的策略,并利用實(shí)際可運(yùn)行程序的反匯編代碼從實(shí)踐的角度理解操作系統(tǒng)內(nèi)核,然后開(kāi)始分析Linux內(nèi)核源代碼,從系統(tǒng)調(diào)用陷入內(nèi)核,進(jìn)程調(diào)度與進(jìn)程切換,最后返回到用戶態(tài)進(jìn)程。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux2.6對(duì)新型CPU的支持
調(diào)用門(mén)的使用
《windows內(nèi)核情景分析》讀書(shū)筆記(2)
第一課 易語(yǔ)言之寄存器 - 易語(yǔ)言,易源碼,易語(yǔ)言教程,易語(yǔ)言模塊,支持庫(kù) - 一起資源吧...
x86、arm、mips架構(gòu)函數(shù)調(diào)用實(shí)例分析
棧幀&溢出
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服