linux内核源码虽然是用C写的,不过其中有不少用嵌入式汇编直接操做底层硬件的“宏函数”,要想顺利的理解内核理论和具体实现逻辑,学会看嵌入式汇编是必修课,下面内容是学习过程当中的笔记;当作回顾时的参考。linux
1、嵌入式汇编语法:编程
一、格式函数
1 asm("汇编语句" //"("以前用asm 或 __asm__ 意为"()"的内容是汇编语句 2 :输出寄存器 3 :输入寄存器 4 :会被修改的寄存器);
除第一行之外,后面带冒号的行若不使用就均可一省略。学习
输出寄存器:表示当这段嵌入汇编执行完以后,那些寄存器用于存放输出数据。这些寄存器会分别对应一C语言表达式值或一个内存地址;spa
输入寄存器:表示在开始执行汇编代码时,这里指定的一些寄存器中应存放的输入值,它们也分别对应着一C变量或常数值;code
会被修改的寄存器:表示你已对其中列出的寄存器中的值进行了改动,gcc编译器不能再依赖于它原来对这些寄存器加载的值,若是必要的话,gcc须要从新加载这些寄存器。所以咱们须要把那些没有在输出/输入寄存器中的部分列出,可是在汇编语句中明确使用到或隐含使用到的寄存器名列在这个部分。blog
二、实例内存
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编译产生的汇编语言程序中寄存器名称前有一个百分号“%”,在嵌入汇编语句寄存器名称前就必须写上两个百分号“%%”。get
第8行即输出寄存器,该语句的含义是在这段代码运行结束后将eax所表明的的寄存器的值放入_res变量中,做为本函数的输出值,“=a”中的“a”称为加载代码,“=”表示这是输出寄存器,而且其中的值将被输出值替代。加载代码是CPU寄存器,内存地址以及一些数值的简写字母代号。第9行表示在这段代码开始运行时将seg放到eax寄存器中,“0”表示使用与上面相同位置上的输出寄存器。而((*addr))表示一个内存偏移地址值。为了在上面汇编语句中使用该地址值,嵌入式汇编程序规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9.所以,输出寄存器的编号是%0(这里只有一个输出寄存器),输入寄存器前一部分(“0”(seg))的编号是%1,然后部分的编号是%2。上面第6行上的%2即表明(*(addr))这个内存偏移量。编译器
三、输入输出寄存器格式说明
“0”表示使用与上面相同位置上的输出寄存器
四、特别说明
使用Intel CPU时, 当须要进行函数调用时,有如下原则:1、eax 、edx、ecx的内容必须由调用者自行保存;2、ebx、esi、edi得内容必须由被调函数保护,当被调这要使用这些寄存器中的任何一个时,要实如今本身的栈中保存其内容,由于调用者不负责管理;函数操做结束后再还原回去;另外ebp、esp的使用也要遵循第二原则。