uboot代码中有这么一句话“#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")”,困扰了山人多时。经过多番求索,才得知原来是定义了一个全局的寄存器变量gd_t(r8是它的专用寄存器)。

详细解释一下,register意思是定义的变量保存在寄存器中,volatile代表禁止编译器优化,后边的asm ("r8")说的是使用的寄存器是r8。

一、何谓寄存器变量

     百度百科

在程序运行时,根据需要到内存中相应的存储单元中调用,如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。

    山人认为寄存器变量的优势的确是提高了变量的访问速度,也牺牲了宝贵的寄存器资源。在学谭浩强的C语言时,书中有关寄存器变量的介绍。

二、APCS 简介

本文讲的是寄存器变量的事,看似与APCS无关。但是,实质上APCS涉及了寄存器是怎样被使用的,也就关寄存器变量的事了。所以,还是需要先介绍APCS。以下文字摘录于“arm指令集”。

    APCS,ARM 过程调用标准(ARM Procedure Call Standard),提供了紧凑的编写例程的一种机制,定义的例程可以与其他例程交织在一起。最显著的一点是对这些例程来自哪里没有明确的限制。它们可以编译自 C、 Pascal、也可以是用汇编语言写成的。

APCS 定义了:

  • 对寄存器使用的限制。
  • 使用栈的惯例。
  • 在函数调用之间传递/返回参数。
  • 可以被‘回溯’的基于栈的结构的格式,用来提供从失败点到程序入口的函数(和给予的参数)的列表。

APCS 不一个单一的给定标准,而是一系列类似但在特定条件下有所区别的标准。例如,APCS-R (用于 RISC OS)规定在函数进入时设置的标志必须在函数退出时复位。在 32 位标准下,并不是总能知道进入标志的(没有 USR_CPSR),所以你不需要恢复它们。如你所预料的那样,在不同版本间没有相容性。希望恢复标志的代码在它们未被恢复的时候可能会表现失常...

如果你开发一个基于 ARM 的系统,不要求你去实现 APCS。但建议你实现它,因为它不难实现,且可以使你获得各种利益。但是,如果要写用来与编译后的 C 连接的汇编代码,则必须使用 APCS。编译器期望特定的条件,在你的加入(add-in)代码中必须得到满足。一个好例子是 APCS 定义 a1 到 a4 可以被破坏,而 v1 到 v6 必须被保护。现在我确信你正在挠头并自言自语“a 是什么? v 是什么?”。所以首先介绍 APCS-R 寄存器定义...

ARM寄存器

寄存器名字
Reg# APCS 意义
R0 a1 工作寄存器
R1 a2  ··
R2 a3 ·· 
R3 a4  ··
R4 v1 必须保护
R5 v2  ··
R6 v3  ··
R7 v4  ··
R8 v5  ··
R9 v6  ··
R10 sl 栈限制
R11 fp  帧指针
R12 ip  
R13 sp  栈指针
R14 lr  连接寄存器
R15 pc  程序计数器

笔者实验总结

通过反汇编查看C语言的代码,可以知道r0-r3用作形参传递参数,倘若形参超过4个,则需要用堆栈来传递其余的参数。r0-r3在函数中间是作为中间变量的,在退出函数之后,其值也不用特别处理(试想形参也没有返回值,而中间变量是暂存数据,也不需要先保存再弹出)。由此,下结论r0-r3是可以被破坏的。

r4-r14更像是全局变量,因为假设在函数中因为中间变量不够用而使用了r4-r12中的一个或者某几个,再进入函数时将用到的寄存器保存,出去的时候再弹出,所以r4-r12在经过函数之后是保持不变的。

三、如何在程序中实现定义寄存器变量

以实验来说明定义寄存器变量的方法

head.s

 .text
.global _start
_start:
ldr sp, =
bl init
bl main
halt_loop:
b halt_loop

init.c

 register volatile int* gd_t asm("r8");
void init()
{
gd_t = (int *);
}

main.c

 register volatile int* gd_t asm ("r8");

 int main(void)
{
gd_t = (int* );
while();
return ;
}

Makefile

objs := head.o init.o main.o

test.bin : $(objs)
arm-linux-ld -Ttext 0x0 -o test_elf $^
arm-linux-objcopy -O binary -S test_elf $@
arm-linux-objdump -D -m arm test_elf > test.dis %.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $< %.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $< clean:
rm -f test.dis test.bin test_elf *.o

查看反汇编内容

test.dis

 test_elf:     file format elf32-littlearm

 Disassembly of section .text:

  <_start>:
: e3a0da01 mov sp, # ; 0x1000
: eb000001 bl <init>
: eb000002 bl <main> 0000000c <halt_loop>:
c: eafffffe b c <halt_loop> <init>:
: e3a09000 mov r8, #0 ; 0x0
: e1a0f00e mov pc, lr <main>:
: e3a08001 mov r8, #1 ; 0x1
1c: eafffffe b 1c <main+0x4>
Disassembly of section .comment: <.comment>:
: cmpmi r3, # ; 0x0
: 4728203a undefined
: 2029554e eorcs r5, r9, lr, asr #
c: 2e342e33 mrccs , , r2, cr4, cr3, {}
: smladxmi r0, r5, r0, r0
: 203a4343 eorcss r4, sl, r3, asr #
: 554e4728 strplb r4, [lr, #-]
1c: 2e332029 cdpcs , , cr2, cr3, cr9, {}
: 00352e34 eoreqs r2, r5, r4, lsr lr

寄存器全局变量与普通的全局变量使用区别

普通的全局变量使用是在一个文件中定义全局变量,然后通过“extern”将其作用域扩展到其他的文件;寄存器的全局变量是每一个使用这个全局变量的文件都要对这个全局变量定义。

为什么有这样的区别呢

普通全局变量在一个文件被定义后,就会在相应的数据段(data或者bss)开辟一个空间,这个空间编译器会给它命一个名字。例如“int a=10;”就会在data段开辟一个4字节的空间,并且编译器给这个空间命名为a。其它文件也需要调用这个全局变量时,是对这个变量进行声明,例如“extern int a;”。在连接程序阶段,连接器会把所有使用a变量的代码都定位在之前定义时在data段开辟命名为a的空间。

寄存器全局变量,它的存储空间已经确定。当对寄存器全局变量的定义进行编译处理时,不会试图从data或者bss开辟空间,而是直接使用寄存器(例如实验程序中,直接使用r8来处理gd_t)。因为不存在编译器产生连接时所需要有关全局变量的标号(编译器不会产生gd_t的标号),所以实际上也不存在“哪一个文件是定义,哪一个文件是声明的问题了”。

当init.c文件使用了寄存器全局变量gd_t,它在编译时被r8替换。当main.c也使用了gd_t,它也被r8替换。实际上,在连接过程中已经没有处理gd_t的任务。这与普通全局变量的编译、连接是有本质的区别。

为了体现这种巨大差异,再做一个实验修改init.c的内容,其他保持不变。

init.c

 register volatile int* gd_t asm("r9");
void init()
{
gd_t = (int *);
}

对应的反汇编代码

test.dis

 test_elf:     file format elf32-littlearm

 Disassembly of section .text:

  <_start>:
: e3a0da01 mov sp, # ; 0x1000
: eb000001 bl <init>
: eb000002 bl <main> 0000000c <halt_loop>:
c: eafffffe b c <halt_loop> <init>:
: e3a09000 mov r9, # ; 0x0
: e1a0f00e mov pc, lr <main>:
: e3a08001 mov r8, # ; 0x1
1c: eafffffe b 1c <main+0x4>
Disassembly of section .comment: <.comment>:
: cmpmi r3, # ; 0x0
: 4728203a undefined
: 2029554e eorcs r5, r9, lr, asr #
c: 2e342e33 mrccs , , r2, cr4, cr3, {}
: smladxmi r0, r5, r0, r0
: 203a4343 eorcss r4, sl, r3, asr #
: 554e4728 strplb r4, [lr, #-]
1c: 2e332029 cdpcs , , cr2, cr3, cr9, {}
: 00352e34 eoreqs r2, r5, r4, lsr lr

可以看到编译能够正常通过,也就是说gd_t在不同的文件中可以使用不同的寄存器,因为在连接过程中没有gd_t处理的任务(它们都被寄存器所代替)。

 四、使用全局寄存器变量需要注意的地方

1、每一个使用这个全局变量的文件都要对这个全局变量定义,而且要一样

    倘若像修改后的代码一样,同一个全局变量gd_t却在不同的文件中使用了不同的寄存器,结果不能实现全局变量的效果。

2、确保汇编程序中没有随意修改所使用寄存器的值

    寄存器全局变量有可能会在汇编程序中被破坏,可能因为寄存器不够用或者中断服务。要做的任务就是,使用前对其保存和退出使用后对其恢复。

3、不能使用r0-r3作为寄存器全局变量

因为它们遵从APCS规则,会被经常破坏,不能当做寄存器全局变量来用。而r4-r12的特性适合全局变量来使用。

 后记

本文为笔者实验以及查找资料所得结论,可能有谬误,望读者发现指正。

arm-linux-gcc编译器定义寄存器变量的更多相关文章

  1. Ubuntu12.4 64位 安装 arm linux gcc 4.3.2

    一.下载arm linux gcc 4.3.2 http://pan.baidu.com/share/link?shareid=1575352696&uk=2754759285&fid ...

  2. <转载>linux gcc编译器中使用gdb单步调试程序,程序不是顺序执行的。

    原文地址http://blog.csdn.net/abc78400123/article/details/6779108 在用gdb调试,使用s 或n单步执行程序时,发现程序不是按顺序运行的,有时莫名 ...

  3. Linux gcc编译器

    GNU CC(通常称为GCC)是GNU项目的编译器,他能够编译C.C++语言编写的程序. 使用gcc,程序员可以对编译过程有更多控制,编译过程分为3个阶段. --预处理 --汇编 --链接 程序员可以 ...

  4. c语言寄存器变量

    寄存器存在于CPU内部,运算速度非常快, 因为内存中的数据必须载入寄存器才能计算.如果直接定义一个变量为寄存器变量,则少了载入等过程自然会快.对于频繁使用的变量可以把它放在寄存器中来提速度. 对于VC ...

  5. Linux环境下的GCC编译器与GDB调试工具介绍

    假如现在我们有如下代码需要编译运行和调试.文件名为:test.c #include <stdio.h> int main() { int day, month, year, sum, le ...

  6. Linux中gcc编译器的用法

    在Linux环境下进行开发,gcc是非常重要的编译工具,所以学习gcc的基本常见用法时非常有必要的. 一.首先我们先说明下gcc编译源文件的后缀名类型 .c为后缀的文件,C语言源代码文件:  .a为后 ...

  7. linux gcc 区分32位或64位编译 && 请问arm存储,是以小端格式还是以大端格式?

    linux gcc 区分32位或64位编译   Linux系统下程序如何区分是64位系统还是32位系统 经过对include的翻查,最后确定gcc以__i386__来 进行32位编码,而以__x86_ ...

  8. Linux设置编译器环境变量

    Linux设置编译器环境变量 https://jingyan.baidu.com/article/9f7e7ec0bb22aa6f29155453.html Linux添加环境变量与GCC编译器添加I ...

  9. Linux下GCC编译器的安装

    通过apt-get方式下载的Qt5.9的gcc编译器版本只是4.8.3,无法打开一些Qt5的库头文件,所以准备在Llinux下再安装一个gcc5.3.0. 查看gcc版本 ubuntu下查看gcc的版 ...

随机推荐

  1. 验证(Verification)与确认(Validation)的差别

    验证(Verification)与确认(Validation)的差别 说法一: (2)“验证(Verification)”的涵义 通过提供客观证据对规定要求已得到满足的认定. (2)“确认(Valid ...

  2. 13 引用WINAPI

            [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SetWind ...

  3. 開始Unity3D的学习之旅

    前言:这个系列的文章纯属对自己学习的整理,非高手之作.但确实的记载了我作为一个没接触过3D游戏编程的大学生的心路历程.争取每周整理一次吧.之所以会開始学Unity3D,最基本的原因是由于在快放暑假的时 ...

  4. [HTTP] HTTP Verb

    HEAD: HEAD / HTTP/1.1 nc.exmaple.com HEAD is a interesting method, it allow you to get a header of f ...

  5. android 换肤模式总结

    由于Android的设置中并没有夜间模式的选项,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体验.目前越来越多的应用开始把夜间模式加到自家应用中,没准不久google也会把这项功能添加到 ...

  6. C#多线程编程(1):线程的启动

    转:http://blog.csdn.net/zhoufoxcn/article/details/4402999 在实例化Thread的实例,需要提供一个委托,在实例化这个委托时所用到的参数是线程将来 ...

  7. 【慕课网学习笔记】Java共享变量的可见性和原子性

    1. Java内存模型(Java Memory Model, JMM) Java的内存模型如下,所有变量都存储在主内存中,每个线程都有自己的工作内存. 共享变量:如果一个变量在多个线程中都使用到了,那 ...

  8. Log4Net详细配置

    关于Log4Net配置主要分几步 第一步:下载log4net.dll(log4net官网:http://logging.apache.org/log4net/download_log4net.cgi) ...

  9. 获取汉字拼音 Java

    两种方法:一个是使用btye数组,一个是引入jar包进行操作. 1. public class CharacterParser { private static int[] pyvalue = new ...

  10. asp:时间的计算

    DataTime dt = new DataTime();//dt为时间DataTime对象 dt.ToString();//2005-11-5 13:47:04 dt.AddYears(1).ToS ...