linux內核源碼雖然是用C寫的,不過其中有很多用嵌入式匯編直接操作底層硬件的“宏函數”,要想順利的理解內核理論和具體實現邏輯,學會看嵌入式匯編是必修課,下面內容是學習過程中的筆記;當做回顧時的參考。
嵌入式匯編語法:
1、格式
1 asm("匯編語句" //"("之前用asm 或 __asm__ 意為"()"的內容是匯編語句 2 :輸出寄存器 3 :輸入寄存器 4 :會被修改的寄存器);
除第一行以外,后面帶冒號的行若不使用就都可一省略。
輸出寄存器:表示當這段嵌入匯編執行完之后,那些寄存器用於存放輸出數據。這些寄存器會分別對應一C語言表達式值或一個內存地址;
輸入寄存器:表示在開始執行匯編代碼時,這里指定的一些寄存器中應存放的輸入值,它們也分別對應着一C變量或常數值;
會被修改的寄存器:表示你已對其中列出的寄存器中的值進行了改動,gcc編譯器不能再依賴於它原來對這些寄存器加載的值,如果必要的話,gcc需要重新加載這些寄存器。因此我們需要把那些沒有在輸出/輸入寄存器中的部分列出,但是在匯編語句中明確使用到或隱含使用到的寄存器名列在這個部分。
2、實例
1 #define get_seg_byte(seg,addr) \ 2 ( { \ 3 register char _res ; \ //定義了一個寄存器變量——res 4 _asm_("push %%fs ; \ //保存fs寄存器原值 5 mov %%ax,%%fs ; \ //用seg設置fs 6 movb %%fs:%2,%%al ; \ //取seg:addr處1字節內容到al寄存器 7 pop %%fs " \ //恢復fs寄存器原內容 8 : "=a" (_res) \ 9 : "0" (seg), "m" ( * (addr) ) ) ; \ 10 _res ; } )
這段代碼定義了一個嵌入式匯編語言函數。通常使用匯編語言最方便的方法是把他們放在一個宏內。用圓括號括住的組合語句(花括號中的語句)“({})”可以作為表達式使用,其中最后一行的變量_res是該表達式的輸出值。因為宏語句需要定義在一行上,因此這里使用反斜杠“\”將這些語句連成一行。這條紅第一將被替換到程序中引用改宏名稱的地方。第一行定義了宏的名稱,即宏函數名稱get_seg_byte(seg,addr)。第三行定義了一個寄存器變量_res。該變量將被保存在一個寄存器中,以便快速訪問和操作。如果想指定寄存器(如eax),那么我們可以把改句寫成"register char _res asm("ax");",其中asm也可以寫成_asm_。第四行上的_asm_表示嵌入式匯編語句的開始。第4-7行的4條語句是AT&T格式的匯編語句。另外,為了讓gcc編譯產生的匯編語言程序中寄存器名稱前有一個百分號“%”,在嵌入匯編語句寄存器名稱前就必須寫上兩個百分號“%%”。
第8行即輸出寄存器,該語句的含義是在這段代碼運行結束后將eax所代表的的寄存器的值放入_res變量中,作為本函數的輸出值,“=a”中的“a”稱為加載代碼,“=”表示這是輸出寄存器,並且其中的值將被輸出值替代。加載代碼是CPU寄存器,內存地址以及一些數值的簡寫字母代號。第9行表示在這段代碼開始運行時將seg放到eax寄存器中,“0”表示使用與上面相同位置上的輸出寄存器。而((*addr))表示一個內存偏移地址值。為了在上面匯編語句中使用該地址值,嵌入式匯編程序規定把輸出和輸入寄存器按統一順序編號,順序是從輸出寄存器序列從左到右從上到下以“%0”開始,分別記為%0、%1···%9.因此,輸出寄存器的編號是%0(這里只有一個輸出寄存器),輸入寄存器前一部分(“0”(seg))的編號是%1,而后部分的編號是%2。上面第6行上的%2即代表(*(addr))這個內存偏移量。
3、輸入輸出寄存器格式說明
“0”表示使用與上面相同位置上的輸出寄存器


4、特別說明
使用Intel CPU時, 當需要進行函數調用時,有以下原則:一、eax 、edx、ecx的內容必須由調用者自行保存;二、ebx、esi、edi得內容必須由被調函數保護,當被調這要使用這些寄存器中的任何一個時,要實現在自己的棧中保存其內容,因為調用者不負責管理;函數操作結束后再還原回去;另外ebp、esp的使用也要遵循第二原則。
