libc从版本2.0开始,增加了对setjmp和longjmp的支持,这2个函数是实现协程的核心函数。
我们来看一下RISC-V平台上这两个函数的底层是如何实现的。
libc源码:https://sourceware.org/git/?p=glibc.git;a=summary
git clone https://sourceware.org/git/glibc.git
setjmp/setjmp.h定义两个函数的原型
setjmp(jmp_buf __env);
longjmp(struct __jmp_buf env, int _val);
typedef struct __jmp_buf_tag jmp_buf[1];
/* Store the calling environment in ENV, also saving the signal mask.
Return 0. */
extern int setjmp (jmp_buf __env) __THROWNL;
/* Jump to the environment saved in ENV, making the
`setjmp' call there return VAL, or 1 if VAL is 0. */
extern void longjmp (struct __jmp_buf_tag __env[1], int __val)
__THROWNL __attribute__ ((__noreturn__));
#if defined __USE_MISC || defined __USE_XOPEN
/* Same. Usually `_longjmp' is used with `_setjmp', which does not save
the signal mask. But it is how ENV was saved that determines whether
`longjmp' restores the mask; `_longjmp' is just an alias. */
extern void _longjmp (struct __jmp_buf_tag __env[1], int __val)
__THROWNL __attribute__ ((__noreturn__));
#endif
typedef struct __jmp_buf_tag sigjmp_buf[1];
riscv平台的具体实现位于目录:sysdeps/unix/sysv/linux/riscv/
setjmp.h头文件,定义了__jmp_buf类型的结构体。
bits/setjmp.h
#ifndef _RISCV_BITS_SETJMP_H
#define _RISCV_BITS_SETJMP_H
typedef struct __jmp_buf_internal_tag
{
/* Program counter. */
long int __pc;
/* Callee-saved registers. */
long int __regs[12];
/* Stack pointer. */
long int __sp;
/* Callee-saved floating point registers. */
#if defined __riscv_float_abi_double
double __fpregs[12];
#elif !defined __riscv_float_abi_soft
# error unsupported FLEN
#endif
} __jmp_buf[1];
#endif /* _RISCV_BITS_SETJMP_H */
汇编代码如下:
setjmp.S 保存寄存器到内存中,a0中保存的是setjmp(env) 函数传递过来的_env, _env的是__jmp_buf类型的结构体,其中有变量用于保存寄存器。
#include <sysdep.h>
#include <sys/asm.h>
ENTRY (_setjmp)
li a1, 0
j HIDDEN_JUMPTARGET (__sigsetjmp)
END (_setjmp)
ENTRY (setjmp)
li a1, 1
/* Fallthrough */
END (setjmp)
ENTRY (__sigsetjmp)
REG_S ra, 0*SZREG(a0)
REG_S s0, 1*SZREG(a0)
REG_S s1, 2*SZREG(a0)
REG_S s2, 3*SZREG(a0)
REG_S s3, 4*SZREG(a0)
REG_S s4, 5*SZREG(a0)
REG_S s5, 6*SZREG(a0)
REG_S s6, 7*SZREG(a0)
REG_S s7, 8*SZREG(a0)
REG_S s8, 9*SZREG(a0)
REG_S s9, 10*SZREG(a0)
REG_S s10,11*SZREG(a0)
REG_S s11,12*SZREG(a0)
REG_S sp, 13*SZREG(a0)
#ifndef __riscv_float_abi_soft
FREG_S fs0, 14*SZREG+ 0*SZFREG(a0)
FREG_S fs1, 14*SZREG+ 1*SZFREG(a0)
FREG_S fs2, 14*SZREG+ 2*SZFREG(a0)
FREG_S fs3, 14*SZREG+ 3*SZFREG(a0)
FREG_S fs4, 14*SZREG+ 4*SZFREG(a0)
FREG_S fs5, 14*SZREG+ 5*SZFREG(a0)
FREG_S fs6, 14*SZREG+ 6*SZFREG(a0)
FREG_S fs7, 14*SZREG+ 7*SZFREG(a0)
FREG_S fs8, 14*SZREG+ 8*SZFREG(a0)
FREG_S fs9, 14*SZREG+ 9*SZFREG(a0)
FREG_S fs10,14*SZREG+10*SZFREG(a0)
FREG_S fs11,14*SZREG+11*SZFREG(a0)
#endif
#if !IS_IN (libc) && IS_IN (rtld)
/* In ld.so we never save the signal mask. */
li a0, 0
ret
#else
/* Make a tail call to __sigjmp_save; it takes the same args. */
j __sigjmp_save
#endif
END (__sigsetjmp)
hidden_def (__sigsetjmp)
weak_alias (_setjmp, __GI__setjmp)
__longjmp.S 从内存中恢复寄存器
#include <sysdep.h>
#include <sys/asm.h>
ENTRY (__longjmp)
REG_L ra, 0*SZREG(a0)
REG_L s0, 1*SZREG(a0)
REG_L s1, 2*SZREG(a0)
REG_L s2, 3*SZREG(a0)
REG_L s3, 4*SZREG(a0)
REG_L s4, 5*SZREG(a0)
REG_L s5, 6*SZREG(a0)
REG_L s6, 7*SZREG(a0)
REG_L s7, 8*SZREG(a0)
REG_L s8, 9*SZREG(a0)
REG_L s9, 10*SZREG(a0)
REG_L s10,11*SZREG(a0)
REG_L s11,12*SZREG(a0)
REG_L sp, 13*SZREG(a0)
#ifndef __riscv_float_abi_soft
FREG_L fs0, 14*SZREG+ 0*SZFREG(a0)
FREG_L fs1, 14*SZREG+ 1*SZFREG(a0)
FREG_L fs2, 14*SZREG+ 2*SZFREG(a0)
FREG_L fs3, 14*SZREG+ 3*SZFREG(a0)
FREG_L fs4, 14*SZREG+ 4*SZFREG(a0)
FREG_L fs5, 14*SZREG+ 5*SZFREG(a0)
FREG_L fs6, 14*SZREG+ 6*SZFREG(a0)
FREG_L fs7, 14*SZREG+ 7*SZFREG(a0)
FREG_L fs8, 14*SZREG+ 8*SZFREG(a0)
FREG_L fs9, 14*SZREG+ 9*SZFREG(a0)
FREG_L fs10,14*SZREG+10*SZFREG(a0)
FREG_L fs11,14*SZREG+11*SZFREG(a0)
#endif
seqz a0, a1
add a0, a0, a1 # a0 = (a1 == 0) ? 1 : a1
ret
END (__longjmp)
sys/asm.h
定义一些辅助性的宏定义、ENTRY 这些
#ifndef _SYS_ASM_H
#define _SYS_ASM_H
/* Macros to handle different pointer/register sizes for 32/64-bit code. */
#if __riscv_xlen == 64
# define PTRLOG 3
# define SZREG 8
# define REG_S sd
# define REG_L ld
#elif __riscv_xlen == 32
# define PTRLOG 2
# define SZREG 4
# define REG_S sw
# define REG_L lw
#else
# error __riscv_xlen must equal 32 or 64
#endif
#if !defined __riscv_float_abi_soft
/* For ABI uniformity, reserve 8 bytes for floats, even if double-precision
floating-point is not supported in hardware. */
# if defined __riscv_float_abi_double
# define FREG_L fld
# define FREG_S fsd
# define SZFREG 8
# else
# error unsupported FLEN
# endif
#endif
/* Declare leaf routine. */
#define LEAF(symbol) \
.globl symbol; \
.align 2; \
.type symbol,@function; \
symbol: \
cfi_startproc;
/* Mark end of function. */
#undef END
#define END(function) \
cfi_endproc; \
.size function,.-function
/* Stack alignment. */
#define ALMASK ~15
#endif /* sys/asm.h */
risc-v的寄存器参见:5.4 RISC-V寄存器 - 知乎
后面标着Callee的寄存器就是上面需要保存和恢复的寄存器。
参考:
risc-v汇编指令编写指南: https://shakti.org.in/docs/risc-v-asm-manual.pdf
risc-v汇编手册: riscv-asm-manual/riscv-asm.md at master · riscv-non-isa/riscv-asm-manual · GitHub