函数之间跳转的实现方式详解:setjmp/longjmp、ucontext 与汇编

在 C 语言及系统编程中,程序的执行流通常是自上而下、函数调用有严格的栈结构。但有时我们希望打破函数调用的正常顺序,实现从一个函数跳转到另一个函数,甚至在不同栈上下文之间切换。

常见的手段有:

  1. setjmp/longjmp ------ 非局部跳转。

  2. ucontext ------ 用户级上下文切换。

  3. 内联汇编 ------ 直接操作寄存器和栈。

一、setjmp / longjmp ------ 非局部跳转

1、基本原理

  • setjmp 会保存当前的 CPU 寄存器环境(如 PC 程序计数器、SP 栈指针、通用寄存器等),保存到一个 jmp_buf 类型的结构里。

  • longjmp 可以恢复之前保存的环境,相当于把寄存器恢复到 setjmp 调用时的状态。于是程序会重新从 setjmp****返回

这就像"存档 & 读档":

  • setjmp 就是存档

  • longjmp 就是读档

2、示例代码

cpp 复制代码
#include <stdio.h>
#include <setjmp.h>

jmp_buf env;

void func(int arg) {
    printf("in func, arg=%d\n", arg);
    longjmp(env, ++arg);  // 跳回 setjmp
}

int main() {
    int ret = setjmp(env);
    if (ret == 0) {
        printf("first time setjmp, ret=%d\n", ret);
        func(ret);
    } else {
        printf("after longjmp, ret=%d\n", ret);
    }
    return 0;
}

3、运行过程

  • 第一次调用 setjmp → 返回 0,进入 if(ret == 0) 分支。

  • 调用 func(0)longjmp(env, 1)

  • 程序直接「跳回」到 setjmp 那一行,但这次返回值是 1,所以走到 else 分支。

输出大概是:

cpp 复制代码
first time setjmp, ret=0
in func, arg=0
after longjmp, ret=1

4、应用场景

  • 错误恢复机制(比如 libcsetjmp/longjmp 常用于异常处理)。

  • 简单的流程控制,模拟"非局部跳转"。

二、ucontext ------ 用户态上下文切换

1、背景:为什么会有 ucontext

在 Unix/Linux 世界里,一个老大难问题就是 "程序如何假装同时做很多事"

一台电脑的 CPU 在任意时刻,其实只能执行 一条指令流

换句话说:

👉 本质上它一次只能干一件事

但是我们每天看到的电脑:

  • 播着音乐;

  • 同时你在打字;

  • 浏览器还在刷网页。

这就是"假装同时做很多事"。

它是怎么做到的呢?这就涉及 调度

  • 内核线程 (kernel thread)

    由内核负责调度。切换时需要系统调用,涉及内核态 ↔ 用户态的切换 → 开销比较大。

    (好比你每次要切换任务,都得去找"国家公务员"帮你换身份证 → 太慢。)

  • 用户线程 (user thread)

    由用户空间自己管理,不经过内核。切换只是在用户态保存和恢复上下文 → 开销小很多。

    (就像你自己用抽屉里的身份证副本换着用 → 快!)

👉 早期的 协程 (coroutine)绿色线程 (green thread)纤程 (fiber) 都是用户线程的一种。它们需要解决两个核心问题:

  1. 存档:保存当前函数执行到哪里(包括 CPU 寄存器、栈指针、程序计数器等)。

  2. 读档:切换到另一个函数继续执行,并且能回到原来的点。

于是,系统在 POSIX.1-2001 里引入了 ucontext API,提供了一个简单的接口来实现"存档/读档"。

不过后来,ucontext 在 glibc 2.8 之后被标记为 弃用(deprecated),因为:

  • 接口设计比较古老;

  • 跨平台兼容性不好;

  • 有更现代的替代方案(如 pthread、现代协程库)。

但------

👉 它仍然是理解 协程用户态任务切换 的经典入门工具。

主要函数:

  • getcontext(ucontext_t *ucp):获取当前上下文(寄存器、栈等)。

  • setcontext(const ucontext_t *ucp):恢复上下文。

  • makecontext(ucontext_t *ucp, void (*func)(), int argc, ...):修改上下文,使其执行某个函数。

  • swapcontext(ucontext_t *oucp, const ucontext_t *ucp):保存当前上下文到 oucp,切换到 ucp

2、原理

当你调用 swapcontext(&A, &B) 时,系统做了两件事:

  1. 保存 A :把当前寄存器状态(PC、SP、通用寄存器)写入 A->uc_mcontext

  2. 恢复 B :把 B->uc_mcontext 的内容恢复到寄存器,然后 PC 指向 B 的位置。

换句话说:

👉 调用 swapcontext 后,CPU 就好像"穿越"到了另一个函数里继续跑。

3、示例代码

cpp 复制代码
#include <stdio.h>
#include <ucontext.h>

ucontext_t ctx[2], main_ctx;
int count = 0;

void func1(void) {
    while (count++ < 3) {
        printf("func1: step\n");
        swapcontext(&ctx[0], &ctx[1]);
    }
}

void func2(void) {
    while (count++ < 6) {
        printf("func2: step\n");
        swapcontext(&ctx[1], &ctx[0]);
    }
}

int main() {
    char stack1[8192];
    char stack2[8192];

    // 设置 ctx[0]
    getcontext(&ctx[0]);
    ctx[0].uc_stack.ss_sp = stack1;
    ctx[0].uc_stack.ss_size = sizeof(stack1);
    ctx[0].uc_link = &main_ctx;
    makecontext(&ctx[0], func1, 0);

    // 设置 ctx[1]
    getcontext(&ctx[1]);
    ctx[1].uc_stack.ss_sp = stack2;
    ctx[1].uc_stack.ss_size = sizeof(stack2);
    ctx[1].uc_link = &main_ctx;
    makecontext(&ctx[1], func2, 0);

    printf("start swapcontext\n");
    swapcontext(&main_ctx, &ctx[0]);
    printf("back to main\n");

    return 0;
}

4、运行过程

  • swapcontext(&main_ctx, &ctx[0]) → 保存 main 状态,切换到 func1

  • func1 打印一次,swapcontext(&ctx[0], &ctx[1]) → 切到 func2

  • func2 打印一次,切回 func1

  • 两个函数交替运行,直到循环结束,返回 main。

输出类似:

cpp 复制代码
start swapcontext
func1: step
func2: step
func1: step
func2: step
func1: step
func2: step
back to main

👉 这就是一个「协程切换」的雏形。

三、汇编方式(简略)

更底层的实现方式是用汇编直接操作栈指针和程序计数器,保存寄存器状态,实现函数间跳转。这是协程库、线程库的基础,但写法复杂,一般开发者不会直接用。

0voice · GitHub

相关推荐
起个昵称吧1 天前
立即数、栈、汇编与C函数的调用
c语言·开发语言·汇编
mysla3 天前
嵌入式学习day46-硬件—汇编
汇编·学习
zgc12453673 天前
汇编基础2
汇编
出门吃三碗饭3 天前
编译器构造:从零手写汇编与反汇编程序(一)
android·汇编
JCBP_3 天前
QT(3)
开发语言·汇编·c++·qt·算法
出门吃三碗饭3 天前
编译器构造:模拟器,汇编与反汇编
汇编
zgc12453673 天前
汇编基础1
汇编·学习
sheepwjl4 天前
《嵌入式硬件(六):ARM汇编核心内容总结》
汇编·arm开发·嵌入式硬件
武文斌774 天前
ARM工作模式、汇编学习
汇编·嵌入式硬件·学习·arm