Cortex-M单片机中 setjmp、longjmp原理探究及实现

一、原理

C语言中包含头文件 <setjump.h> 即可实现跨函数跳转,通常用于异常处理,在运行代码出现异常时可以自动跳转到调用setjump函数的位置。

1、实验

#include "setjmp.h"

jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        longjmp(g_jmp_buf,1);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为
setjmp输出
可见,当b==0时产生了异常,程序从div__()函数跳转回setjmp()函数的位置输出了"something wrong",而不是计算结果。
setjmp和longjmp函数的第一个参数是jmp_buf,这个变量用于存储上下文,setjmp.h中的描述为:

/* size as defined in the ATEPCS /
/
an array type suitable for holding the data /
/
needed to restore a calling environment. */

2、猜想

猜测实现方式是在setjmp函数中保存当前的上下文,也就是cpu的寄存器值,然后在调用longjmp函数时跳转到setjmp函数中再恢复上下文,这与多线程的实现方法类似,只是多线程是把上下文保存到栈中,setjmp可以保存到全局变量或堆中,只需要保证jmp_buf的生命周期在调用setjm和longjmp期间是有效的即可。

二、实现

以下代码在Keil5环境下可以编译通过。
定义jmp_buf数据类型:

typedef u32 my_jmp_buf[48];

定义setjmp和longjmp函数:

    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8


my_setjmp PROC
	EXPORT	my_setjmp
	STR		r0,[r0]
	STR		r1,[r0,#4]
	STR		r2,[r0,#8]
	STR		r3,[r0,#12]
	STR		r4,[r0,#16]
	STR		r5,[r0,#20]
	STR		r6,[r0,#24]
	STR		r7,[r0,#28]
	STR		r8,[r0,#32]
	STR		r9,[r0,#36]
	STR		r10,[r0,#40]
	STR		r11,[r0,#44]
	STR		r12,[r0,#48]
	STR		r13,[r0,#52]
	STR		r14,[r0,#56]
	;存入PC指针
	LDR		r1,=return_from_longjmp
	STR		r1,[r0,#60]
	MOV		r0,#0
	BX		r14
	
	;从longjmp返回时会从这里开始执行
return_from_longjmp
	STR		r1,[r0]
	LDR		r1,[r0,#4]
	LDR		r2,[r0,#8]
	LDR		r3,[r0,#12]
	LDR		r4,[r0,#16]
	LDR		r5,[r0,#20]
	LDR		r6,[r0,#24]
	LDR		r7,[r0,#28]
	LDR		r8,[r0,#32]
	LDR		r9,[r0,#36]
	LDR		r10,[r0,#40]
	LDR		r11,[r0,#44]
	LDR		r12,[r0,#48]
	LDR		r13,[r0,#52]
	LDR		r14,[r0,#56]
	LDR		r0,[r0]
	BX		r14
	
	ENDP



my_longjmp	PROC
	EXPORT	my_longjmp
	LDR		r2,[r0,#60]
	BX		r2
	ENDP



    ALIGN   4

    END

三、调用


typedef u32 my_jmp_buf[48];


extern int my_setjmp(my_jmp_buf);

extern void my_longjmp(my_jmp_buf,int);

my_jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        my_longjmp(g_jmp_buf,1);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(my_setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为:
my_setjmp输出
可以看到现象与使用库函数的现象相同。

四、总结

标准库中规定longjmp不能使setjmp返回0:

After longjmp is completed, program execution continues as if
the corresponding call to setjmp had just returned the value
specified by val. The longjmp function cannot cause setjmp to
return the value 0; if val is 0, setjmp returns the value 1.

而在下写的函数my_longjmp是可以使my_setjmp返回0的,这会造成一个奇怪的无限循环,例如:

typedef u32 my_jmp_buf[48];


extern int my_setjmp(my_jmp_buf);

extern void my_longjmp(my_jmp_buf,int);

my_jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        my_longjmp(g_jmp_buf,0);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(my_setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为:
setjmp无限循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值