首页 | 新闻 | 交流 | 问吧 | 文档 | 手册 | 下载 | 博客

透析avr-gcc的中断处理原理

作者:  时间: 2009-05-14

 

透析avr-gcc的中断处理原理

作者 : Etual
邮箱 : Etual@163.com

前言:
呃,这东西没啥意义,也估计不合大众口味,所以,有兴趣向下潜的可以看看,对这没兴趣的可以关帖子了
//bow m(_ _)m

编译环境 WinAVR20071221
avr-gcc 4.2.2
binutils 2.18
avr-lib 1.64

分析源代码用的是暂时最新的库(2009-5-14)avr-libc 1.6.6
下载地址: http://ftp.twaren.net/Unix/NonGNU/avr-libc/

风格:文件路径的表达用Linux风格的正斜杠 "/"

好的,下面开始。

avr-gcc使用interrupt的话,例如时钟中断,格式是

SIGNAL(SIG_OVERFLOW0)
{
}

我们知道,C语言标准是没有中断这个东西,所以这个中断的处理由编译器的提供者来处理,例如keil编译51的
时候也是用自己的中断函数关键字来声明。所以,我们知道,这 SIGNAL 关键字应该是GCC提供的,下面的目标
就是分析这个东西究竟是怎么来的。

注意:下面说到的文件是在 WinAVR 安装目录下面寻找

在 /avr/include/avr/interrupt.h 中找到
#ifdef __cplusplus
# define SIGNAL(vector)                    \
    extern "C" void vector(void) __attribute__ ((signal, __INTR_ATTRS));    \
    void vector (void)
#else
# define SIGNAL(vector)                    \
    void vector (void) __attribute__ ((signal, __INTR_ATTRS));        \
    void vector (void)
#endif

其中 __cplusplus 是当前处于C++编译状态的GCC内部宏,我们只分析C代码,所以应该是

# define SIGNAL(vector)                    \
    void vector (void) __attribute__ ((signal, __INTR_ATTRS));        \
    void vector (void)

在同一页中找到
#if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4)
# define __INTR_ATTRS used, externally_visible
#else /* GCC < 4.1 */
# define __INTR_ATTRS used
#endif

可以从注释中看到,如果GCC版本大于等于4.1则使用第一个定义,现在用的GCC 4.2.2所以使用
# define __INTR_ATTRS used, externally_visible

在 \avr\include\avr\iom8.h 中找到
/* Timer/Counter0 Overflow */
#define TIMER0_OVF_vect            _VECTOR(9)
#define SIG_OVERFLOW0            _VECTOR(9)

根据上面两个找到的结果进行宏替换,那么
SIGNAL(SIG_OVERFLOW0)
{
}
宏替换后的结果是

void _VECTOR(9) (void) __attribute__ ((signal, used, externally_visible));
void _VECTOR(9) (void)
{

}

继续在 /avr/include/avr/sfr_defs.h中找到
#ifndef _VECTOR
#define _VECTOR(N) __vector_ ## N
#endif
这里是一个C的连接字符串的宏,将 __vector_ 和宏参数连接。
_VECTOR(9) 替换后的结果是 __vector_9

那么继续将上面的中断函数宏替换得到

void __vector_9 (void) __attribute__ ((signal, used, externally_visible));
void __vector_9 (void)
{

}

到这里为止,基本露出 SIGNAL的本来面目了,其实就是先一个声明,加上一个实现函数,函数的
名字为 __vector_9 ,其中函数的声明部分,使用了 gcc 的扩展语法__attribute__ 指定几个关
键字,其中signal指示gcc生成中断需要的代码,例如返回的时候用途 reti 代替 ret 等,
其中 used 的作用就是指示该函数是肯定被用到的,生成对应的符号,而不会被优化等和谐掉。
externally_visible也是相似效果,强制指定函数是外部可见的,而不管 -fwhole-program 参数
的影响。

具体参考GCC手册,可以到gnu网站看在线文档,或者在WinAVR的安装目录就有
/doc/gcc/HTML/gcc-4.2.2

在5.25 Declaring Attributes of Functions 一章

signal
Use this attribute on the AVR to indicate that the specified function is a signal handler.
The compiler will generate function entry and exit sequences suitable for use in a signal
handler when this attribute is present. Interrupts will be disabled inside the function.

used
This attribute, attached to a function, means that code must be emitted for the function
even if it appears that the function is not referenced. This is useful, for example, when
the function is referenced only in inline assembly.

externally_visible
This attribute, attached to a global variable or function nullify effect of -fwhole-program
command line option, so the object remain visible outside the current compilation unit

其中 __vector_9 是GCC的一个内部使用的变量号,具体依据还不能找出来,我觉得需要看GCC的
源代码和gccint文档了,这个实在有点难度,我们先认为,这是由GCC(编译为交叉编译AVR的时候)
做好的一个标号,意思很容易猜出,也就是 9号中断向量的意思,至于其他中断享有其他中断号。

在WinAVR的安装目录也最多能找到这么多东西而已,我们需要继续从 avr-lib的源代码包中查找。

注意,下面说的文件是在 avr-lib 源代码包解压后的目录里面查找,没有源代码的先去官方网站
下载,网站是 http://ftp.twaren.net/Unix/NonGNU/avr-libc/
本例子使用的是 avr-libc-1.6.6.tar.bz2

主要使用的文件是

/crt1/gcrt1.S
这个就是启动文件了,avr-gcc 自动生成中断向量,清空bss段等操作都在里面,编译后会根据不同
的期间生成不同的 crt.o 文件,例如我用的 MEGA8 的话,会生成一个其中文件,目录在WinAVR的
安装目录的 /avr/lib/avr4/crtm8.o (当然了,这是avr-libc安装后的文件了)

/common/asmdef.h ctoasm.inc gasave.inc macros.inc
这几个头文件是一下分析需要用到的。

在 /crt1/gcrt1.S 中找到
vector    __vector_9 刚才说的那个向量名在这里就出现了,
其中 vector 是一个宏,原型在上面就找到,是

    .macro    vector name
    .if (. - __vectors < _VECTORS_SIZE)
    .weak    \name
    .set    \name, __bad_interrupt
    XJMP    \name
    .endif
    .endm

这里有几点需要解释的,
. - __vectors < _VECTORS_SIZE 我想按照字面意思已经能猜到,"." 是地址指针,代表当前地址
我想写过 ld script的应该熟悉吧,这个意思就是,当前地址减去 __vectors ,这个就是所有向量
的开始地址,是否小于 _VECTORS_SIZE ,这个就是所有向量加起来的大小,如果有10个向量,那么
这个就是10了,很简单,为什么要这样来判断??
有没有留意这个文件一共定义了125个向量,实际我们不需要这么多,怎么办?那只处理1~_VECTORS_SIZE
个向量,后面的就不理它,宏编译过后,只有 1~_VECTORS_SIZE个向量存在,其他都没了,这样处理
起来是不是很方便呢?呵呵~~~

接着是一个很重要的使用
    .weak    \name
    .set    \name, __bad_interrupt
    XJMP    \name
先从简单的入手 XJMP 也是一个宏,原型在 /common/macros.inc
#if __AVR_MEGA__
  #define XJMP jmp
  #define XCALL call
#else
  #define XJMP rjmp
  #define XCALL rcall
#endif
根据意思也很容易猜到,如果是MEGA系列芯片的话,那么 XJMP 也就是 jmp 了,那么那三句就变成
    .weak    \name
    .set    \name, __bad_interrupt
    jmp    \name
其中 \ 代表宏的参数,这里看出 \name 就是宏传入的参数,不熟悉的找找 GNU AS 的手册看看GNU 汇编
宏的用法了。
.set 其实就是 .equ ,很容易理解吧?符号等效,或者给变量赋值。
那么剩下的重点就是 .weak 了。

我们需要继续在 AS手册中找答案,翻开 WinAVR安装目录 /doc/binutils/as.html/ 或者看AS的在线手册
其中在 7.116 .weak names 可以找到

This directive sets the weak attribute on the comma separated list of symbol names. If the
symbols do not already exist,they will be created.

大概意思就是,指示符指示后面跟着标号是具有 weak 属性的,如果符号不存在则建立符号。这什么意思呢?
大概我们还需要继续查找找了,翻开刚才已经打开的 GCC 手册,在
 
5.25 Declaring Attributes of Functions
weak
The weak attribute causes the declaration to be emitted as a weak symbol rather than a global.
This is primarily useful in defining library functions which can be overridden in user code,
though it can also be used with non-function declarations. Weak symbols are supported for ELF
targets, and also for a.out targets when using the GNU assembler and linker.

函数的weak属性,大概意思就是,对于 ELF 目标(也就是我们需要编译的了),声明符号的属性是是 weak
而不是global。这到底是什么意思呢,接着就有解释了,weak属性的话,非常适合在建立库文件的时候使用
用户可以很方便的用自己的代码去代替库函数。这是很好的一个功能。

说了这么多,可以回头了,用例子说话,假如这个宏
vector    __vector_9

替换出来的结果是
    .weak    __vector_9
    .set    __vector_9, __bad_interrupt
    jmp     __vector_9

生成一个符号 __vector_9 ,他的属性是 weak,初始化赋值为 __bad_interrupt(这是一个处理函数句柄)
weak的效果就是,如果你正常写程序的话,没有使用timer0中断处理函数 SIGNAL(SIG_OVERFLOW0) 的话,他
默认就是指向 __bad_interrupt ,如果你外部定义了的话,也就是我现在想使用 timer0中断,那么他就指向
__vector_9 了。
不知道有没有看过反汇编?__vector_9 和 __bad_interrupt 这些标号是不是经常看到??没错,就是从这里
来的!!!!

__bad_interrupt 的实现,如果之前理解的话,那么这里就超级简单了。
    .text
    .global    __bad_interrupt
    .func    __bad_interrupt
__bad_interrupt:
    .weak    __vector_default
    .set    __vector_default, __vectors
    XJMP    __vector_default
    .endfunc
单纯的跳到 __vector_default ,简单点,也就是复位了。如果未知中断发生的时候,统统复位掉,这个就是
avr-libc的处理,当然,我们看到这里也是 weak属性,所以很容易想到,我们可以考虑用自己的函数替换掉
嘿嘿。

如果想继续看哪个启动代码的话,可以慢慢研究一下,其中 __init 里面处理的就是非常常用的启动部分,例如
上电复位后,执行C环境初始化代码,例如设置堆栈,COPY数据段,清空BSS段,设置堆为 malloc做准备等,是
不是发现一些很神奇的东西?是的,都在这里做了。

#ifndef __AVR_ASM_ONLY__ 判断现在编译的是否是纯汇编代码,没有C,如果是的话就不需要执行C环境初始化了
所以,GCC还可以编译纯汇编的 -v-

对于.section段的一些参数,不懂的话可以参考一下as的手册的

7.93 .section name
.section name [, "flags"[, @type[,flag_specific_arguments]]]
@progbits
section contains data
x
executable section
a
ignored. (For compatibility with the ELF version)

结语:
到这里为止,整个中断的处理过程已经分析透了。可以很清晰的看到,在处理这些的,实际上时 avr-libc 的
实现代码,而GCC没有干涉。开源代码,免费只是其中一面,最大的乐趣是读代码,修改代码。(fin)