目的:
(1) 优化,对特定代码进行优化;
(2)C 语言需要访问某些特殊指令来实现特殊功能,比如内存屏障指令;
内嵌汇编两种模式:
基础内嵌汇编:不带参数;
扩展的内嵌汇编:C 语言变量参数;
(1) 基础内嵌汇编
格式 :
asm 关键字 :表明是一个 GNU 扩展;
修饰词 (qualifiers)
volatile: 基础内嵌汇编中,通常不需要;
inline: 内敛,asm 代码会尽可能小;
汇编代码块 :
GCC 编译器把内嵌汇编当成一个字符串;
GCC 编译器不会去解析和分析内嵌汇编;
多条汇编指令,需要使用“\n\t”换行;
GCC 的优化器,可能乱序汇编指令,如果需要保持汇编指令的顺序,最好使用多个内嵌汇编的方式;
内核 arch/arm64/include/asm/barrier.h 文件
(2) 扩展内嵌汇编
asm asm-qualifiers(
Assembler Template;
:outputOperands
:inputOperands
[:Clobbers])
格式 :
asm 关键字:表明是一个 GNU 扩展;
修饰词 (qualifiers):
volatile: 用来关闭 GCC 优化;
inline: 内敛,减小汇编代码尺寸;
goto: 在内嵌汇编里会跳转到 C 语言的 label;
输出部 :
用于描述在指令部中可以被修改的 C 语言变量以及约束条件;
每个约束通常以 "=" 开头,接着的字母表示对操作类型的说明,然后是关于变量结合的约束;
“=+”+ 约束修饰符 + 变量
“=”表示被修饰的操作数具有可写属性;
“+”表示被修饰的操作数具有可读可写属性;
输出部可以是空的;
输入部:
用来描述在指令部中,只读的 C 语言变量以及约束条件;
输入部描述的参数,是只读属性,不要修改参数内容,因为 GCC 编译器假定输入部参数,在内嵌汇编前后是一致的;
输入部中不能使用 "=/+" 约束条件,编译器会报错;
输入部可以是空的;
损坏部:
“memory”告诉 GCC 内嵌汇编指令 改变了内存的值,强迫编译器在执行该汇编代码前,存储所有缓存的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序;
“cc”: 表示内嵌汇编代码影响状态寄存器相关的标志位;
内核里的实例,arch/arm64/include/asm/barrier.h
扩展内嵌汇编指令部中的参数表示:
案例 1,用内嵌汇编实现 memcpy:
static void my_memcpy_asm_test(unsigned long src, unsigned long dst,unsigned long counter)
{
unsigned long tmp;
unsigned long end = src+counter;
asm volatile("1: ldr %1, [%2], #8\n"
"str %1, [%0], #8\n"
"cmp %2, %3\n"
"b.cc 1b"
:"+r"(dst),"+r"(tmp),"+r"(src) //output list, can write and read
:"r"(end) //input list, readonly
:"memory"
);
}
使用汇编符号名实现内嵌汇编:
static void my_memcpy_asm_test(unsigned long src, unsigned long dst,unsigned long counter)
{
unsigned long tmp;
unsigned long end = src+counter;
asm volatile("1: ldr %[tmp], [%[src]], #8\n"
"str %[tmp], [%[dst]], #8\n"
"cmp %[src], %[end]\n"
"b.cc 1b"
:[dst]"+r"(dst),[tmp]"+r"(tmp),[src]"+r"(src) //output list, can write and read
:[end]"r"(end) //input list, readonly
:"memory"
);
}
内嵌汇编与宏结合
#define MY_OPS(ops,asm_ops) \
static inline my_asm_##ops(unsigned long mask, void *p) \
{ \
unsigned long tmp; \
asm volatile( \
"ldr %1, [%0]\n" \
" "#asm_ops" %1, %1, %2\n" \
"str %1,[%0]\n" \
: "+r"(p), "+r"(tmp) \
:"r"(mask) \
: "memory" \
); \
}
MY_OPS(and, and)
MY_OPS(or, orr)
MY_OPS(andnot, bic)
static void my_ops_test(void)
{
unsigned long p;
p = 0xf;
my_asm_and(0x2, &p);
printk("test and:p=0x%x\n",p);
p = 0x0;
my_asm_or(0x3, &p);
printk("test or:p=0x%x\n",p);
p = 0x3;
my_asm_andnot(0x2, &p);
printk("test andnot:p=0x%x\n",p);
}
技巧:
(1) 使用了 C 语言宏中的“#”和“##”;
“#”:预处理器把这个参数转换为一个字符串;
“##”:是一种分隔链接方式,它的作用是先分隔,在进行强制链接;
(2) 通过一个宏来实现多个类似的函数,这是 linux 内核常用的技巧;
实现读和写系统寄存器的宏
//equals asm volatile("mrs %0,""#reg" :"=r"(_val)); _val;}
#define read_sysreg(reg) ({ \
unsigned long _val; \
asm volatile("mrs %0,"#reg \
:"=r"(_val)); \
_val; \
})
#define write_sysreg(val, reg) ({\
unsigned long _val=(unsigned long)val; \
asm volatile("msr "#reg ",%0" \
::"rZ"(_val)); \
})
static test_sysregs(void)
{
unsigned long el;
el = read_sysreg(CurrentEL);
printk("el=%d\n",el>>2);
write_sysreg(0x10000, vbar_el1);
printk("read vbar:0x%x\n",read_sysreg(vbar_el1));
}
实现从汇编到 C 代码的跳转
static void test_asm_goto(int a)
{
asm goto(
"cmp %w0, 1\n"
"b.eq %l[label]\n"
: // 必须为空
:"r"(a)
:"memory"
:label
);
return 0;
label:
printk("%s: a=%d.\n",__func__,a);
}
goto 模板的输出部必须为空;