关于ARM上编程的

来源:互联网 发布:windows自带邮件服务器 编辑:程序博客网 时间:2024/05/01 09:58

ARM汇编优化
 要做程序的优化,最彻底的方法当然是汇编!还有除了汇编以外(除了二进制)能让你对你的处理器有更全面的控制吗?!对于ARM汇编,作为一个初学者,也就只好先补补基础了@_@。
     首先,程序段的定义从AREA 开始,它命名一个代码区域,注意,用非阿拉伯数字作为名字时,应该用|把名字包起来,CODE关键字声明程序(猜测),readonly声明访问权限(猜测)。EXPORT 来表示某个可以用作外部连接的符号(简单点,应该就是函数名?)。END用来结尾。
#eg:
    AREA    |.text|, CODE, READONLY
    EXPORT   square
 
    ; int square(int i)
 
square      ;armcc把不缩进的正文作为一个标号定义
           MUL    r1,r0,r0
           MOV   r0,r1           ;ARM乘法指令有一个限制,就是目标寄存器不能和第一个参数寄存器相同
           MOV   pc,lr            ;对Thumb指令,应该改为BX lr
           END
 
      使用import,可以声明其他文件中定义的标号,要用ARM C库的话,就import |Lib$Request$armlib|, WEAK表示本行的标号如果找不到,不会报告连接错误。如果程序包含主程序main,那么要引入标号__main,代表C库初始化的开始。RN可以让用户给寄存器命名。
#eg:
     AREA            |.text|, CODE,READONLY
 
     EXPORT     main
 
     IMPORT     |Lib$$Request$$armlib|, WEAK
     IMPORT     __main             ;C library entry
     IMPORT     printf                ;prints to stdout
 
i    RN   4
 
        ;int main(void)
main
          STMFD      sp!,{i,lr}
          MOV         i,#0
loop
          ADR          r0,print_string
          MOV         r1,i
          MUL          r2,i,i
          BL            printf
          ADD         i,i,#1
          CMP          i,#10
          BLT          loop
          LDMFD      sp!,{i,pc}
 
print_string
         DCB         " Square of %d is %d/n", 0
         END
 
MAP(别名^)和FIELD(别名#),可以在堆栈中为变量和数组定义和分配空间。
 
              MAP     0                             ;map symbols to offsets starting at offset 0?
a            FIELD    4                             ;a is 4 bytes integer(at offset 0)
b            FIELD    2                            ;b is bytes integer(at offset 4)
c            FIELD    64                           ;c is an array of 64 characters (at offset 6)
length     FIELD   0                             ;length records the current offset reached?
 
MACRO用来声明宏:
 
             MACRO
             CHECKSUM $ alignment
checksum_$ alignment
            LDR         w,[data],#4
l0         ;loop
           IF $ alignment<>0
               ADD     sum,sum,w,LSR #8, $alignement
               LDR      w,[data],#4
               SUBS    N,N,#1
               ADD     sum,sum,w,LSL#32-8* $ alignment
           ELSE
               ADD     sum,sum,w
               LDR      w,[data],#4
               SUBS    N,N,#1
           ENDIF
           BGT         %BT l0
           MOV         pc,lr
           MEND
 
      针对汇编的优化主要是面向硬件的。首先是流水线,有些load指令要需要多个周期来完成,可以通过调整指令的顺序(当然要保证逻辑)来改善性能。另外,尽量让程序只是用寄存器,方法是搞清楚数据占用寄存器的时间关系,实现寄存器有效的分时复用。另外,可以将长度较小的变量合并到一个32位寄存器中保存,以节省寄存器。由于PC可以通过程序操作,对于条件指令,可以直接用PC与形成分支的参数作运算来寻找对应的分支:
            ;int switch_relative(int x)
switch_relative
            CMP          x,#8
            ADDLT      pc,pc,x,LSL,#2
            B              method_d      ;利用流水线,如果PC还是按顺序那么default分支的预取址就不
                                                ;会被冲掉
            B              method_0
            B              method_1
 

 

 

 ARM上的C编程
1.arm c编译器默认char类型是8位无符号的,与其它编译器有点不同
2.局部变量最好用int型,因为寄存器是32位的,如果变量不是32位的就需要额外的指令限制范围.
  例如: 变量i,操作i++ ,如果int i, 则只需add r1,r1,#1  如果char i,则变成add r1,r1,#1 
and r1,r1, 0xff  .多了一条指令

3.循环最好用do{}while()型的,相比for(;;)型循环每次循环可以节省3条指令

4.函数参数也最好用int 型的,例如 short add(short x,short y)
      编译器为了保证输入参数的是short型的会添加额外的指令,比如确保x是short型的,需要
      mov r0,r0,lsr #16    mov r0,r0,asr #16

5.函数参数最好不要超过4个,因为前4个参数是通过寄存器r0-r3传递的,超过4个后的参数使用堆栈传递,速度慢多了.

6.适当的展开循环.循环有一定的开销,在一个循环中多做几遍操作,减少循环的次数可以减少循环的开销.
 例如:  i = 0;         int i;
               do                                        do{
              {                                             i++;
                 i++;                                     i++;
              }while(i<64)   改为             i++;}while(i<64)
    当然,这样做也增加了代码长度.

7.使用减计数到0的循环结构,这样就不用用寄存器保存终止值.

8.使用无符号的循环计数值,循环条件是i!=0,而不是i>0, 这样循环的开销只有2条指令 


 ARM上的汇编优化小方法
1. 加减法,逻辑操作占一个周期,目的地址是PC寄存器时增加一个周期。分支指令占3个周期。在cache命中的情况下,16位和8位的装载指令(LDRH、LDRH等)占一个周期,但紧跟的2个周期不能使用装入的数据。32位装载指令占一个周期,紧跟的一个周期不能使用装载数据。如果装载入PC,同样要增加2个周期。
             LDR    r1,[r2]        ADD  r1,r1,r3       ADD  r4,r4,r5    占4个周期
改变次序后
             LDR    r1,[r2]        ADD  r4,r4,r5        ADD  r1,r1,r3    占3个周期

2. load指令占时间比较长,在循环中可以使用预载的方法将load与跳转指令放在一起,减少流水线的断流。
例如:
loop
    LDRB   r2,[r1]
     ...............           //do
    B            loop
更改为
   LDRB   r2,[r1]
loop
    ..............            //do
    LDRB   r2,[r1]
    B          loop

3. 循环展开时,可以在计算i步时就加载i+1步的数据,在i步的结果还没准备好时执行i+1步计算。

4. ARM只有16个可见寄存器,其中14个通用寄存器,1个堆栈指针r13,1个程序计数器r15。在图像处理的应用中很多是8位的操作数,可以利用32为寄存器一次进行两组运算。
例如:加操作      100 + 50    和       2 + 3
    位                     24                16               8                 0
    操作数1           0                100              0                 2
    操作数2           0                 50                0                3
    结果                 0                 150              0                5

5. 寄存器数量不够时,可用32为寄存器保存两个16位变量和4个8位变量。

 


初入嵌入式软件开发不久,最近在看UC/OS2的内核源码,感觉很有意思,记下一些学习过程,欢迎大家拍砖。

 
嵌入式软件经常要同时完成若干任务,可以在无人干预的情况下应对所有的事件及异常,并且可以根据事件的轻重缓急自动保证最先完成最紧急的任务。
 
嵌入式软件由RTOS跟其上跑的应用部分软件组成,应用部分软件可简单看成一个个任务,每个任务可以对相关的外界产生的事件或是异常响应。而RTOS的核心功能就是管理各个任务,并建立起任务和外界事件的联系。
 
一个典型的任务示意如下:
 
void Task()
{
       for (;;)
       {
              //Do something initial;
              OSFlagPend();
              OSMboxPend();
              OSMutexPend();
              OSQPend();
              OSSempend();
 
              OSTaskPend(priority);
              OSTaskDel(priority);
              OSTimeDly();
             
              /* application code */
       }
}
 
首先任务应该包含一个无限循环,这个循环意在一直处理跟任务自己感兴趣的事件。可以看到上面的示意代码里有一串的Pend调用,这些Pend函数就是每个RTOS都会提供的,使用了这些Pend调用后RTOS就可以让这个任务与它感兴趣的事件建立起联系。如果有它感兴趣的事件发生,就会向下执行到application code来处理这个事件。处理完以后又进入循环回等待它感兴趣事件的状态,直到有它感兴趣的事件发生,然后又处理,如此周而复此。如果没有这个任务感兴趣事件发生会怎么呢?那RTOS就会挂起这个任务,去执行其它的任务处理其自身感兴趣的事件。如果没有任何事件发生会怎么样呢?这种情况下通常RTOS会运行一个自建的任务TaskIdle(), 这个任务什么事也不干,通常是给一个整数进行自加动作。
上面就是嵌入式软件开发最根本的东西,但RTOS是究竟是怎样工作的呢?后续日子里我们再一起学习分享吧。^0^