函数指针的使用

嵌入式的中断向量表中保存的是函数的指针,那么如何正确的通过C语法给向量表元素赋值呢?

嵌入式的中断向量表中保存的是函数的指针,那么如何正确的通过C语法给向量表元素赋值呢?

在中断向量表中,要用到函数指针的赋值。比如在 x210 中,异常向量表寄存器如下:

#define exception_vector_table_base     0xD0037400
#define exception_reset                 (exception_vector_table_base + 0x00)
#define exception_undef                 (exception_vector_table_base + 0x04)
#define exception_sotf_int              (exception_vector_table_base + 0x08)
#define exception_prefetch              (exception_vector_table_base + 0x0C)
#define exception_data                  (exception_vector_table_base + 0x10)
#define exception_irq                   (exception_vector_table_base + 0x18)
#define exception_fiq                   (exception_vector_table_base + 0x1C)

void system_init_exception(void);
void reset_exception(void);
void undef_exception(void);
void soft_exception(void);
void prefetch_exception(void);
void data_exception(void);

这些都是寄存器的地址,如何把函数地址正确的赋值给这些地址呢?

如何把函数赋值给寄存器

  1. 首先定义一个函数指针类型,方便后面写代码

     typedef void (*T_FUNC)(void);
    
  2. 定义向量表元素以函数指针的方式访问

    也就是把向量表地址 exception_reset 变成一个变量,比如一个指针 ptr,而 *ptr 就是把指针变量化

     #define r_exception_reset       (*(volatile T_FUNC *)exception_reset)
     #define r_exception_undef       (*(volatile T_FUNC *)exception_undef)
     #define r_exception_sotf_int    (*(volatile T_FUNC *)exception_sotf_int)
     #define r_exception_prefetch    (*(volatile T_FUNC *)exception_prefetch)
     #define r_exception_data        (*(volatile T_FUNC *)exception_data)
     #define r_exception_irq         (*(volatile T_FUNC *)exception_irq)
     #define r_exception_fiq         (*(volatile T_FUNC *)exception_fiq)
    
  3. 然后把函数指针赋值给对应的变量

     r_exception_reset    = (T_FUNC)&reset_exception;
     r_exception_undef    = (T_FUNC)&undef_exception;
     r_exception_sotf_int = (T_FUNC)&soft_exception;
     r_exception_prefetch = (T_FUNC)&prefetch_exception;
     r_exception_data     = (T_FUNC)&data_exception;
     r_exception_irq      = (T_FUNC)&IRQ_handle;
     r_exception_fiq      = (T_FUNC)&IRQ_handle;
    

如何把函数赋值给函数指针数组

我们知道,在中断时,x210 会把对应的中断函数赋值给中断寄存器,然后我们只需要去对应的中断寄存器中取出函数指针,然后执行即可。可是如何正确的使用函数指针数组呢?

#define VIC0_BASE                   (0xF2000000)
#define VIC1_BASE                   (0xF2100000)
#define VIC2_BASE                   (0xF2200000)
#define VIC3_BASE                   (0xF2300000)

#define     VIC0VECTADDR            (VIC0_BASE + 0x100)
#define     VIC0ADDR                (VIC0_BASE + 0xf00)
#define     VIC1VECTADDR            (VIC1_BASE + 0x100)
#define     VIC1ADDR                (VIC1_BASE + 0xf00)
#define     VIC2VECTADDR            (VIC2_BASE + 0x100)
#define     VIC2ADDR                (VIC2_BASE + 0xf00)
#define     VIC3VECTADDR            (VIC3_BASE + 0x100)
#define     VIC3ADDR                (VIC3_BASE + 0xf00)

x210 有一个中断向量组基地址寄存器VICnVECTADDR,每个 VICnVECTADDR 是一个数组,每组存放着 32 个中断号对应的中断处理函数地址,还有一个中断地址寄存器 VICnADDR,当有中断触发时 x210 会把对应的 VICnVECTADDR[i] 中的函数指针放到 VICnADDR 中,我们要读取的就是 VICnADDR。 那么如何正确定义这些寄存器的类型呢?

  1. 定义成函数指针类型

    把 VIC0VECTADDR 这个数看成一个存放函数指针的地址,即地址化(指针化)。我们知道创建一个指针的方法是 (TYPE *)p,所以指针化寄存器地址如下:

    虽然 T_FUNC p 也是创建一个函数指针,但要把一个地址声明为一个函数指针还是要用 (T_FUNC *) 的方式,不能直接用 (T_FUNC)

     #define VIC0VECTADDR (T_FUNC *)(VIC0_BASE + 0x100)
     #define VIC0ADDR     (T_FUNC *)(VIC0_BASE + 0xf00)
    
  2. 加 volatile 关键字

    因为寄存器地址会经常改变,所以要加 volatile 关键字,否则会出现各种问题

     #define VIC0VECTADDR (volatile T_FUNC *)(VIC0_BASE + 0x100)
    
  3. 指针变量化

    为了更符合使用习惯,把上面的地址变量化(左值化)

     #define VIC0VECTADDR (*(volatile T_FUNC *)(VIC0_BASE + 0x100))
    
  4. 把函数指针放到对应的寄存器地址中

    把 intnum 号中断处理函数 handler,放中断向量组 VICnVECTADDR 的 num 号寄存器中

    注意,这里 VIC0VECTADDR 是一个基地址,即有 VIC0VECTADDR0, VIC0VECTADDR1, …. 很多中断地址寄存器

     void intc_setvectaddr(unsigned long intnum, T_FUNC handler)
     {
       T_FUNC *base[4] = {(T_FUNC*)&VIC0VECTADDR, 
                 (T_FUNC *)&VIC1VECTADDR, 
                 (T_FUNC *)&VIC2VECTADDR, 
                 (T_FUNC *)&VIC3VECTADDR};
       int num = 0;
       if (intnum < 32) {
         num = 0;
       } else if (intnum < 64) {
         num = 1;
         intnum -= 32;
       } else if (intnum < 96) {
         num = 2;
         intnum -= 64;
       } else if (intnum < 200) {
         num = 3;
         intnum -= 96;
       }
       base[num][intnum] = handler;
     }
    

模拟的结果

下面做了一个实验,其中 ADDRn 用来模拟中断向量组响应寄存器 VICnADDR,当有中断触发时,应对的中断函数指针会放到这个响应寄存器中。

注意 func1&func1 的区别

#include <stdio.h>

typedef void (*T_FUNC)(void);

unsigned long ADDR0;
unsigned long ADDR1;
unsigned long ADDR2;
unsigned long ADDR3;

#define rVIC0ADDR  (*((T_FUNC*)&ADDR0))
#define rVIC1ADDR  (*((T_FUNC*)&ADDR1))
#define rVIC2ADDR  (*((T_FUNC*)&ADDR2))
#define rVIC3ADDR  (*((T_FUNC*)&ADDR3))

void func0(void)
{
    printf("this is func0\n");
    return;
}
void func1(void)
{
    printf("f[1]: aaaaaaa\n");
}
void func2(void)
{
    int i = 0;
    i = 2;
    printf("[func2]: 这是函数2\n");
}
void func3(void)
{
    int d = 3;
    for (d = 3; d < 10; d++);
    printf("[func4]\n");
}


int main(void)
{

    rVIC0ADDR = (T_FUNC)&func0;
    rVIC1ADDR = (T_FUNC)&func1;
    rVIC2ADDR = (T_FUNC)&func2;
    rVIC3ADDR = (T_FUNC)&func3;


    T_FUNC base[4] = {rVIC0ADDR, rVIC1ADDR, rVIC2ADDR, rVIC3ADDR};
    T_FUNC isr = NULL;

    isr = base[2];
    printf("ADDR0: %#lx\n", ADDR0);
    printf("ADDR1: %#lx\n", ADDR1);
    printf("ADDR2: %#lx\n", ADDR2);
    printf("ADDR3: %#lx\n", ADDR3);
    printf("&ADDR0: %p\n", &ADDR0);
    printf("&ADDR1: %p\n", &ADDR1);
    printf("&ADDR2: %p\n", &ADDR2);
    printf("&ADDR3: %p\n", &ADDR3);
    printf("&func0: %p\n", &func0);
    printf("&func1: %p\n", &func1);
    printf("&func2: %p\n", &func2);
    printf("&func3: %p\n", &func3);
    printf("func0: %p\n", func0);
    printf("func1: %p\n", func1);
    printf("func2: %p\n", func2);
    printf("func3: %p\n", func3);
    printf("sizeof(func0): %lu\n", sizeof(func0));
    printf("sizeof(func1): %lu\n", sizeof(func1));
    printf("sizeof(func2): %lu\n", sizeof(func2));
    printf("sizeof(func3): %lu\n", sizeof(func3));
    printf("sizeof(&func0): %lu\n", sizeof(&func0));
    printf("sizeof(&func1): %lu\n", sizeof(&func1));
    printf("sizeof(&func2): %lu\n", sizeof(&func2));
    printf("sizeof(&func3): %lu\n", sizeof(&func3));
    printf("rVIC0ADDR: %p\n", rVIC0ADDR);
    printf("rVIC1ADDR: %p\n", rVIC1ADDR);
    printf("rVIC2ADDR: %p\n", rVIC2ADDR);
    printf("rVIC3ADDR: %p\n", rVIC3ADDR);
    printf("isr = base[2] = %p\n", isr);
    printf("base[2]: %p\n", base[2]);

}

执行结果如下:

wilson@ubuntu:/mnt/hgfs/share_directory/x210$ ./a.out
ADDR0: 0x4005d6
ADDR1: 0x4005e7
ADDR2: 0x4005f8
ADDR3: 0x40061b
&ADDR0: 0x601068
&ADDR1: 0x601058
&ADDR2: 0x601060
&ADDR3: 0x601050
&func0: 0x4005d6
&func1: 0x4005e7
&func2: 0x4005f8
&func3: 0x40061b
func0: 0x4005d6
func1: 0x4005e7
func2: 0x4005f8
func3: 0x40061b
sizeof(func0): 1
sizeof(func1): 1
sizeof(func2): 1
sizeof(func3): 1
sizeof(&func0): 8
sizeof(&func1): 8
sizeof(&func2): 8
sizeof(&func3): 8
rVIC0ADDR: 0x4005d6
rVIC1ADDR: 0x4005e7
rVIC2ADDR: 0x4005f8
rVIC3ADDR: 0x40061b
isr = base[2] = 0x4005f8
base[2]: 0x4005f8

函数指针与指向函数指针的指针

  • 函数指针的类型是 void (*)(void),它的大小是 1 字节,所以它是非常规指针
  • 指向函数指针的指针类型是 void (**)(void),它的大小是 8 字节(一个指针的大小)
  • 所以函数指针的加法没有意义
// 定义一个函数类型 T_FUNC
typedef void(*T_FUNC)(void);

int main(void)
{
	T_FUNC pfunc = (T_FUNC)0x1000000; // 函数指针
	T_FUNC *ppfunc = (T_FUNC *)0x1000000; // 指向函数指针的指针
	void (*vpfunc)(void) = (void (*)(void))0x1000000;

	printf("pfunc: %p\n", pfunc); //pfunc: 0x1000000
	printf("*pfunc: %p\n", *pfunc); //*pfunc: 0x1000000
	printf("ppfunc: %p\n", ppfunc); //ppfunc: 0x1000000
	printf("pfunc + 1: %p\n", pfunc+1); //pfunc + 1: 0x1000001
	printf("ppfunc + 1: %p\n", ppfunc+1); //ppfunc + 1: 0x1000008
	printf("vfunc: %p\n", vpfunc); //vfunc: 0x1000000
	printf("vfunc + 1: %p\n", vpfunc + 1); //vfunc + 1: 0x1000001
}

执行结果

pfunc: 0x1000000
*pfunc: 0x1000000
ppfunc: 0x1000000
pfunc + 1: 0x1000001
ppfunc + 1: 0x1000008
vfunc: 0x1000000
vfunc + 1: 0x1000001

要特别注意

pfunc + 1: 0x1000001 #(T_FUNC)0x1000000 + 1
ppfunc + 1: 0x1000008 # (T_FUNC *)0x1000000 + 1

当定义一个函数指针数组时,这个函数指针数组的基地址类型就一定要是指向函数指针的指针类型,即 T_FUNC * 而不能是 T_FUNC,前者是一个常规指针,后者是一个函数指针。