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的使用也要遵循第二原则。