作業系統真象還原6 完善核心(內聯彙編 c混編)

2021-10-11 05:17:27 字數 4566 閱讀 5671

2.1.2:機器模式

3:編寫列印字元函式

引數傳遞方式---------存放在棧中

在函式未執行前發生程序切換,引數還是需要轉移陣地,暫存器又太少,乾脆直接放在記憶體中

為了避免多程序的引數覆蓋問題,將引數放在程序自己的棧中。

引數壓棧順序---------從右向左,棧空間的清理員----------呼叫者(僅限在當前cdecl呼叫約定下)

由於編寫函式的程式設計師或者呼叫函式的人都知道需要呼叫的引數,所以這些工作其實有誰做都可以,只需要雙方做好約定即可。呼叫約定例如有cdeclsyscalloptlinkthiscall

cdecl ( c declaration,即 c 宣告〉,起源於 c語言的一種呼叫約定,在c語言中,函式引數是 從右到左的順序入梭的。 gnu/linux gcc,把這一約定作為 準, x86 架構上的許多 c 編譯器也都使用這個約定。在 cdecl 中,引數是在棧中傳遞的。 eax、 ecx 和 edx 寄存 器是由呼叫者儲存的,其餘的暫存器由被呼叫者儲存.函式的返回值儲存在 eax 暫存器。 由 呼叫者清理棧空間

為何要混編:

彙編可以直接操作暫存器,對底層設計更方便

由於c**經過編譯成彙編過程中會經過編譯器的優化,所以有時為了高效率採用內聯彙編。

混編的分類

單獨的彙編和c分別編譯成待重定位檔案(目標檔案),再鏈結成可執行檔案

c中嵌入彙編,直接編譯

系統呼叫:linux核心提供給使用者程式用來間接操作硬體的一套子程式,也稱為作業系統功能呼叫

呼叫方式:通過中斷描述符中唯一的系統呼叫入口0x80號中斷,具體的子功能存在eax中。區別於bios中斷很多的中斷號,是因為idt中有很多已被預留的中斷號。

混合程式設計總結:

在彙編**中匯出符號供外部引用是用的關鍵字global引用外部檔案的符號是用的關鍵宇extern

在 c **中只要將符號定義為全域性便可以被外部引用,引用外部符號時用extern宣告即可。

擴充套件內聯彙編要解決的是在同乙個程式中c和彙編如何避免使用暫存器衝突

彙編執行前不知道那些暫存器c程式正在占用,所以使用者在完成這步需要增加棧的壓力,也會降低執行速度,因此這步由編譯器來執行,彙編中提供要使用到的c程式中的變數和暫存器,編譯器來提前進行保護

格式asm (volatile) ("assembly code" : output : input : clobber/modify)

volatile等同於_ volatile _,和c中關鍵字volatile不一樣:編譯器不要優化**,後面的指令保留原樣 output:「運算元修飾符約束名」 (c 變數名)

input :「[運算元修飾符]約束名」( c 變數名)

clobber/modify:輸入彙編**執行後可能破壞的暫存器或者記憶體,來通知編譯器保護

上面對output和input的要求稱為「約束」,它用來把c**中的運算元(變數、立即數)對映為彙編中所使用的運算元(暫存器,記憶體位址)

2.1.1:約束

暫存器約束,把c中運算元存入暫存器中

asm (」addl %%ebx, %%eax 」:」=a」( out_sum ):「 a」( in_a ),」b 」( in_b));

「a」 (in_a)表示把in_a存入eax中,b表示存入ebx中。

」=a」( out_sum )表示為out_sum = eax的值

記憶體約束:把c變數的記憶體位址當作內聯彙編**的運算元, 不需要暫存器做中轉,直接進行記憶體讀寫,也就是彙編**的運算元是變數的指標

立即數約束:傳值的時候不通過記憶體和暫存器,直接作為立即數傳給彙編**,只能作為右值,放在 input 中。

通用約束

c語言中的volatile作用(同擴充套件內聯彙編中的memory)

記憶體約束的記憶體位址,編譯器可以知道,但如果有內存在彙編執行過程中被修改,就需要用在clobber/modify中加入「memory」來告訴gcc了。

memeory宣告的另個作用是清除暫存器快取:

記憶體的訪問速度比cpu中的暫存器來說是比較慢的,所以gcc為了提速,把可能常用到的變數存入暫存器中,但這就帶來乙個問題,編譯器編譯程式時不知道變數的記憶體是否會發生變化,也就是說在程式執行過程中變數所在的記憶體可能會變化,改變的時間可以在cpu的執行緒排程過程,地點是其他執行緒的**執行中。這就導致暫存器的值是「過時」的值

int

main

(void

)//沒優化情況下

mov dword ptr[ebp-4]

,1mov dword ptr[ebp-4]

,2mov eax,dword ptr[ebp-4]

//訪問記憶體的值

ret//優化情況下

mov eax,

2//直接把變數的值放入暫存器中,[ebp-4]位址處的值如果變化,結果就會錯誤!

ret

因此volatile修飾變數,編譯器就會放棄暫存器快取的方法,採用標準定址memory宣告告訴編譯器變數所在的記憶體資料會改變,這樣就可以從記憶體再讀取一次新資料

佔位符分為序號佔位符和名稱佔位符

產生原因:

肯定是為了方便**編寫,也容易讓編譯器識別

暫存器約束中有一種r約束,即讓編譯器自主選擇暫存器來對映c**中的變數,但編寫者不知道用哪個暫存器,所以引入佔位符

擴充套件內聯彙編中的佔位符要有字首%,所以描述暫存器要用兩個%(%%ebx

//序號佔位符 支援10個運算元

asm(

"movb %h1, %0;"\

:"=m"

(in_b)\

:"a"(in_a));

//%0指output %1指input,有多輸入則記為%2,%3,%4...

//運算元預設是32位,根據指令對運算元的要求再取8位、16位等

//%和序號之間新增h表示取暫存器的低16位,新增b表示取低8位

//名稱佔位符	數量不受限制  規則 [名稱]"約束名"(c變數)

asm(

"movb %[xx],%[yy];"\

:[yy]

"=m"

(in_b)\

:[xx]

"a"(in_a)

);

2.1.2:機器模式

機器模式用來在機器層面上指定資料的大小和格式,因為約束不能準確的表示資料物件,因此機器模式從更細的粒度上進行描述。

如序號佔位符的例子中%h1,h表示暫存器高位部分的乙個位元組(ah)

常用的包括:

背景資訊:

與視訊記憶體的暫存器(crt controller data registers),預設情況下i/oas位置1,則crt的address register的埠位址為0x3d4,data register 的埠位址 0x3d5,其中ar是用來索引埠,dr是儲存埠對應的暫存器,而索引為0x0e和0x0f的暫存器分別儲存游標的座標的高低8位

游標是字元的座標,是一維的線性座標,以0為起始的順序。在預設的 80*25 模式下,每行80個字元共25 行,螢幕上可以容納 2000個字元(座標值範圍0~1999)

實現步驟:

備份呼叫環境

讀取游標座標

獲取棧中字元

判斷字元型別,如果是換行\n0xa、回車\r0xd、退格0x8行為符號就另外處理,不然直接當成顯示字元列印出來(列印前判斷是否需要滾屏)

更新游標座標

文字換行方式有兩種:(其實結果沒有區別)

crlf:windows下表示為「\r\n",先回到行首再換行

lf:linux下表示為"\n",直接換行

鏈結時注意要按照「呼叫在前,實現在後」的順序輸入待鏈結檔案。

int

main

(void

)//沒優化情況下

mov dword ptr[ebp-4]

,1mov dword ptr[ebp-4]

,2mov eax,dword ptr[ebp-4]

//訪問記憶體的值

ret//優化情況下

mov eax,

2//直接把變數的值放入暫存器中,[ebp-4]位址處的值如果變化,結果就會錯誤!

ret

《作業系統 真象還原》書評

首先我對這本書的評價是正面的,這是一本還算不錯的書。請以這個基調閱讀本文。我也從來沒寫過書評,只不過這本書實在是讓人忍不住要寫一下,因為各種各樣的原因。這本書大致就是在bochs虛擬機器上面,實現乙個簡單的作業系統,實現的內容包括 mbr,loader,記憶體管理,中斷管理,檔案管理,系統呼叫,多程...

作業系統真象還原 記憶體管理

翻來覆去看了好多遍的記憶體管理 還是沒有弄明白 先把想明白的記下來好了 首先 是開啟分頁管理 一共三步 1 準備好頁目錄表 頁表 2 將頁目錄表的實體地址寫入到cr3暫存器中 3 開啟cr0的31位 即pg位 先說說準備頁目錄表和頁表 頁目錄表被放在了低端1mb記憶體之內 具體位置是0x100000...

《作業系統真象還原》 閱讀筆記(上)

配置bochs,進入bochs simulator後一直是黑屏,原來預設是除錯模式,需要輸入c continue 來讓除錯繼續。主講mbr及進入mbr前的步驟 1.實模式只能訪問1mb的記憶體空間。2.bios在rom中。3.開機上電後cs ip指向記憶體0xfff0,這裡有個跳轉語句,轉到fe05...