《Linux0.11内核完全注释》读书笔记
嵌入汇编
asm("汇编语句":输出寄存器
:输入寄存器
:会被修改的寄存器);
kernel/traps.c文件中第22行开始的一段代码作为例子。
#define get_seg_byte(seg,addr) \({ \
register char __res; \ //定义了一个寄存器变量__res。
__asm__("push %%fs; \ //首先保存fs寄存器原值(段选择符)。
mov %%ax, %%fs; \ //然后用seg设置fs。
movb %%fs:%2, %%al; \//取seg:addr处1字节内容到al寄存器中。
pop %%fs" \ //恢复fs寄存器原内容。
:"=a" (__res) \ //输出寄存器列表
:"0" (seg), "m" (*(addr))); \//出入寄存器列表__res;
})
这段10行代码定义了一个嵌入汇编语言宏函数。通常使用汇编语句最方便的方式是把它们放在一个宏内。用圆括号括住的组合语句(花括号中的语句):“({})”可以作为表达式使用,其中最后一行上的变量__res(第10行)是该表达式的输出值。
再看一个简单的例子。
asm("cld\n\t"
"rep\n\t"
"stol":/*没有输出寄存器*/
:"c"(count-1), "a"(fill_value),"D"(dest)
:"%ecx", "%edi");
常用寄存器加载代码说明
代码 | 说明 | 代码 | 说明 |
---|---|---|---|
a | 使用寄存器eax | m | 使用内存地址 |
b | 使用寄存器ebx | o | 使用内存地址并可以加偏移值 |
c | 使用寄存器ecx | I | 使用常数0-31 |
d | 使用寄存器edx | J | 使用常数0-63 |
S | 使用esi | K | 使用常数0-255 |
D | 使用edi | L | 使用常数0-65535 |
q | 使用动态分配字节可寻址寄存器(eac、ebx、ecx或edx) | M | 使用常数0-3 |
r | 使用任意动态分配的寄存器 | N | 使用1字节常数(0-255) |
g | 使用通用有效的地址即可(eax、ebx、ecx、edx、或内存变量) | O | 使用常数0-31 |
A | 使用eax与edx联合(64位) | = | 输出操作数。输出值将替换前值 |
+ | 表示操作数可读可写 | & | 早期会变的(earlyclobber)操作数,表示在使用完操作数之前,内容会修改 |
关键词volatile也可以放在函数名前来修饰函数,用来通知gcc编译器该函数不会返回。这样就可以让gcc产生更好的一些代码。
volatile void do_exit(long code);static inline volatile void oom(void)
{
printk("out of memory\n\r");
do_exit(SIGSEGV);
}
圆括号中的组合语句
花括号对”{…}“用于把变量声明和语句组合成一个复合语句(组合语句)或一个语句块,这样在语义上这些语句就等同于一条语句。圆括号中的组合语句,即形如”({…})“的语句,可以在GNU C中用作一个表达式使用。这样就可以在表达式中使用loop、switch语句和局部变量,因此这种形式的语句通常称为语句表达式。
({int y = foo(); int z; if (y>0) z=y; else z = -1; 3+z;})
其中组合语句最和一条语句必须是后面跟随一个分号的表示式。这个表达式(”3+z“)的值即用作整个圆括号括住语句的值。如果最后一条语句不是表达式,那么整个语句表达式具有void类型,因此没有值。另外表达式中声明的局部变量会在语句块结束后失效。这个实例可以像如下形式的赋值语句来使用:
res=x+({...})+b;
当然,一般这种语句表达式通常用来定义宏。例如内核源码init/main.c程序中读取CMOS时钟信息的宏定义:
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr, 0x70); \
inb_p(0x70); \
})
再看一个例子在include/asm/io.h中读I/O端口port的宏定义,其中最后变量_v的值就是inb()的返回值。
#define inb(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx, %%al":"=a" (_v):"d" (port)); \
_v; \
})