ARM64基础11:GCC内嵌汇编补充

2021年9月6日 3点热度 0条评论 来源: 科学边界

目的:
(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模板的输出部必须为空;

    原文作者:科学边界
    原文地址: https://blog.csdn.net/luteresa/article/details/120140887
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。