Chinaunix首页 | 论坛 | 博客
  • 博客访问: 62283
  • 博文数量: 28
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-30 01:24
文章分类
文章存档

2017年(1)

2016年(5)

2015年(22)

我的朋友

分类: LINUX

2015-07-21 22:22:46

原文地址:裸奔之指令cache操作 作者:草根老师

一、cache的介绍

我们知道cpu访问其内部的寄存器是最快的,但是通常情况下我们的代码和数据是存放在内存中的。由于内存的读写速度是比较慢的,这样从cpu的角度来看,内存就是一个慢设备了。

为了解决cpu访问内存比较慢的问题,cpu在设计了时候,在主存和cpu通用寄存器之间设置了一个高速的,容量相对较小的存储器,把正在执行的指令地址附近的一部分指令或数据从主存调入这个存储器,提供cpu在一段时间内使用。这个介于主存和cpu之间的高速小容量存储器称作高速缓冲存储器(cache)。

启用cache后,cpu读取数据时,如果cache中有这个数据的复本则直接放回,否则从主存中读入数据,并存入cache中,下次再使用(读、写)这个数据时,可以直接使用cache中的复本。

二、指令cache和数据cache

s3c2410内置了指令cache(icaches)、数据cache(dcache)、写缓存(write buffer)。
系统一上电的时候,这些东西全部是关闭的。要想打开和控制cache需要通过操作CP15协处理器实现。ARM支持16个协处理器,当然,并不是所有的ARM系统结构都全部包含这些协处理器。在ARM920T系列处理器中,协处理器只有两个,一个负责系统调试的CP14,另一个就是负责系统控制的CP15。系统中的cache 、write buffer、MMU、时钟模式等都可以通过操作CP15来完成。

访问CP15协处理器不能使用常规指令,ARM提供了一组专门操作协处理器的指令,这些指令与ARM自身的指令格式有很大的不同。

协处理器也有属于它自己的寄存器,比如 CP15协处理器内部就有16个寄存器,通常使用Cn来表示,这里的n代表协处理器寄存器的序号。想使能cache只需要设置CP15相关的寄存器就可以了。

三、操作协处理的指令

对CP15协处理器的操作使用mcr和mrc两条协处理器指令,这两条指令的记法是从后往前看:mcr 是把r(cpu核寄存器)中的数据传送到c(协处理器寄存器)中,mrc则是把c(协处理器)中的数据传送到r(cpu核寄存器)中。对cp15协处理器的所有操作都是通过cpu核寄存器和cp15寄存器之间交换数据来完成的。

协处理的指令格式:


和其它ARM指令一样,Cond是条件码,bit 20是L位,表示该指令是读还是写,如果L=1就表示Load,从外面读到CPU核中,也就是mrc指令,如果L=0就表示Store,也就是mcr指令。[11:8]这四个位是协处理器编号,CP15的编号是15,因此是4个1。CRn是CP15寄存器编号,Rd是CPU核寄存器编号,各占4个位。对于CP15协处理器,规定opcode1应该为0,opcode2和CRm是指令的选项,具体含义取决于不同的寄存器。


下面看个实例来了解一下,协处理器指令怎么用


实现目标:A.enable icache      B.disable icache


通过查s3c2410数据手册可以知道,通过配置cp15的c1寄存器控制cache是否使能.。


操作cp15的c1寄存器的指令:



c1寄存器的每一位含义:



通过上表我们可以知道,c1寄存器的12bit控制icache 是否使能。

A.enable cache

mrc  p15,0,r0,c1,c0,0 /*将cp15协处理中c1寄存器值读到r0*/
orr    r0,r0,#0x1000
mcr  p15,0,r0,c1,c0,0/*将r0寄存器的值读到cp15协处理器的c1*/

B.disable cache

mrc  p15,0,r0,c1,c0,0 /*将cp15协处理中c1寄存器值读到r0*/
bic    r0,r0,#0x1000
mcr  p15,0,r0,c1,c0,0/*将r0寄存器的值读到cp15协处理器的c1*/

四、案例代码

通过打开和关闭icache,观察流水灯的效果。我们会发现,打开icache时,流水灯的流水效果会变快。

  1. #include "s3c2410.h"

  2. //初始化
  3. void led_init()
  4. {
  5.     //GPFCON -> [8:15]清零
  6.     GPFCON &= ~(0xff << 8);
  7.     //GPF4 GPF5 GPF6 GPF7设为输出模式
  8.     GPFCON |= 0x55 << 8;
  9.     //输出高低平,关闭四路LED灯
  10.     GPFDAT |= 0xf << 4;

  11.     return;
  12. }

  13. //关闭LED
  14. int led_off()
  15. {
  16.     GPFDAT |= 0xf << 4;
  17.     
  18.     return 0;
  19. }

  20. //延时函数
  21. int delay_time(int time)
  22. {
  23.     int i,j;
  24.     
  25.     //让两个for循环作为延时
  26.     for(i = 0;i < time;i ++)
  27.         for(j = 0;j < time;j ++);

  28.     return 0;
  29. }

  30. //流水灯
  31. int run_water_led(int count)
  32. {
  33.     int i = 0;
  34.     
  35.     while(count --)
  36.     {
  37.         led_off();
  38.         delay_time(500);

  39.         for(i = 4;i < 8;i ++)
  40.         {
  41.             GPFDAT &= ~(0x1 << i);
  42.             delay_time(500);
  43.         }
  44.     }

  45.     return 0;
  46. }

  47. void icache_enable()
  48. {
  49.     unsigned int temp = 1 << 12;

  50.     asm(
  51.         "mrc p15,0,r0,c1,c0,0\n"
  52.         "orr r0,r0,%0\n"
  53.         "mcr p15,0,r0,c1,c0,0\n"
  54.         :
  55.         :"r"(temp)
  56.         :"r0"
  57.     );

  58.     return;
  59. }

  60. void icache_disable()
  61. {
  62.     unsigned int temp = 1 << 12;

  63.     asm(
  64.         "mrc p15,0,r0,c1,c0,0\n"
  65.         "bic r0,r0,%0\n"
  66.         "mcr p15,0,r0,c1,c0,0\n"
  67.         :
  68.         :"r"(temp)
  69.         :"r0"
  70.     );

  71.     return;
  72. }

  73. int main()
  74. {    
  75.     led_init();

  76.     icache_disable();
  77.     run_water_led(5);
  78.     led_off();
  79.     
  80.     icache_enable();
  81.     run_water_led(5);
  82.     led_off();

  83.     return 0;
  84. }
红色部分是在c语言中内联汇编。

五、GCC内联汇编

首先,让我们来共同了解一下GCC内联汇编的一般格式:

asm(
    代码列表
    :输出运算符列表
    :输入运算符列表
    :被更改资源列表
);

在C代码中嵌入汇编需要使用asm 关键字,在asm的修饰下,代码列表、输出运算符列表、输入运算符列表和被更改的资源列表这4个部分被3个 ":"分隔。这种格式虽然简单,但并不足以说明问题,我们还是通过例子来解释它吧。

void test(void)
{
    .....
    asm(
    "mov r1,#1\n"
    :
    :
    :"r1"
    );
    ......
}

在上面的代码中,函数 test中内嵌了一条汇编指令实现将立即数1赋值给寄存器r1的操作。由于没有任何形式的输出和输入,因此输出和输入列表的位置上什么都没有填写。但是,我们发现在更改资源列表中,出现了寄存器r1的身影,这表示我们通知了编译器,在汇编代码执行过程中r1寄存器会被修改。


void  test(void)
{
    ....
    asm(
    "stmfd sp!,{r1}\n"
    "mov r1,#1\n"
    "ldmfd sp!,{r1}\n"
    );
    ....
}

这段代码内联汇编既无输出也无输入,也没有资源被更改,只留下了汇编代码部分。

c与内联汇编的交互

void test(void)
{
    int tmp = 5;
    asm(
    "mov r4,%0\n"
    :
    :"r"(tmp)
    :"r4"
    );
}

代码中有一条mov指令,该指令将%0赋值给r4。这里,符号%0代表出现在输入运算符列表和输出运算符列表的第一个值。如果%1存在的话,那么它就代表出现在列表中的第二个值,依次类推。所以,该段代码中,%0代表的就是"r"(tmp)这个表达式的值了。

那么这个新的表达式又该怎样解释呢?原来 ,在"r"(tmp)这个表达式中,tmp代表的正是C语言向内联汇编输入的变量,操作符"r"则代表tmp的值会通过某一个寄存器来传递。在gcc中与之类似的操作符还包括"m","I"等等,其含义如下表


当C语言需要利用内联汇编输出结果时,可以使用输出运算符列表来实现,其格式如下

void test(void)
{
    int tmp;
    
    asm(
    "mov %0,#1\n"
     :"=r"(tmp)
     :
    );
}

原本应出现在输入运算符列表中的运算符,现在出现在了输出运算符列表中,同时变量tmp将会存储内联汇编的输出结果。这里有一点可能已经引起大家的注意了,代码中操作符r的前面多了一个"="。这个等号被称为约束修饰符,其作用是对内联汇编的操作符进行修饰。


当一个操作符没有修饰符对其进行修饰时,代表这个操作符是只读的,在上述代码中,我们需要将内联汇编的结果输出出来,那么至少要保证该操作符是可写的。因此,"="或者"+"也就必不可少了。

这里只是介绍了C内联汇编的常用用法,更复杂的语法读者可以通过相关文档自己学习。

最后附上这个实验的源码:
 led3.rar  

阅读(718) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~