函数之间跳转的实现方式详解: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

相关推荐
我在人间贩卖青春6 天前
汇编之伪指令
汇编·伪指令
我在人间贩卖青春6 天前
汇编之伪操作
汇编·伪操作
济6176 天前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
myloveasuka6 天前
汇编TEST指令
汇编
我在人间贩卖青春6 天前
汇编编程驱动LED
汇编·点亮led
我在人间贩卖青春6 天前
汇编和C编程相互调用
汇编·混合编程
myloveasuka7 天前
寻址方式笔记
汇编·笔记·计算机组成原理
请输入蚊子7 天前
《操作系统真象还原》 第六章 完善内核
linux·汇编·操作系统·bochs·操作系统真像还原
myloveasuka7 天前
指令格式举例
汇编·笔记·计算机组成原理
我在人间贩卖青春8 天前
汇编之分支跳转指令
汇编·arm·分支跳转