定义
/*libcontext - a slightly more portable version of boost::contextCopyright Martin Husemann 2013.Copyright Oliver Kowalke 2009.Copyright Sergue E. Leontiev 2013.Copyright Thomas Sailer 2013.Minor modifications by Tomasz Wlostowski 2016.Distributed under the Boost Software License, Version 1.0.(See accompanying file LICENSE_1_0.txt or copy athttp://www.boost.org/LICENSE_1_0.txt)*/#ifndef BTHREAD_CONTEXT_H
#define BTHREAD_CONTEXT_H#include <stdint.h>
#include <stdio.h>
#include <stddef.h>#if defined(__GNUC__) || defined(__APPLE__)#define BTHREAD_CONTEXT_COMPILER_gcc#if defined(__linux__)#ifdef __x86_64__#define BTHREAD_CONTEXT_PLATFORM_linux_x86_64#define BTHREAD_CONTEXT_CALL_CONVENTION#elif __i386__#define BTHREAD_CONTEXT_PLATFORM_linux_i386#define BTHREAD_CONTEXT_CALL_CONVENTION#elif __arm__#define BTHREAD_CONTEXT_PLATFORM_linux_arm32#define BTHREAD_CONTEXT_CALL_CONVENTION#elif __aarch64__#define BTHREAD_CONTEXT_PLATFORM_linux_arm64#define BTHREAD_CONTEXT_CALL_CONVENTION#elif __loongarch64#define BTHREAD_CONTEXT_PLATFORM_linux_loongarch64#define BTHREAD_CONTEXT_CALL_CONVENTION#endif#elif defined(__MINGW32__) || defined (__MINGW64__)#if defined(__x86_64__)#define BTHREAD_CONTEXT_COMPILER_gcc#define BTHREAD_CONTEXT_PLATFORM_windows_x86_64#define BTHREAD_CONTEXT_CALL_CONVENTION#elif defined(__i386__)#define BTHREAD_CONTEXT_COMPILER_gcc#define BTHREAD_CONTEXT_PLATFORM_windows_i386#define BTHREAD_CONTEXT_CALL_CONVENTION __cdecl#endif#elif defined(__APPLE__) && defined(__MACH__)#if defined (__i386__)#define BTHREAD_CONTEXT_PLATFORM_apple_i386#define BTHREAD_CONTEXT_CALL_CONVENTION#elif defined (__x86_64__)#define BTHREAD_CONTEXT_PLATFORM_apple_x86_64#define BTHREAD_CONTEXT_CALL_CONVENTION#elif defined (__aarch64__)#define BTHREAD_CONTEXT_PLATFORM_apple_arm64#define BTHREAD_CONTEXT_CALL_CONVENTION#endif#endif#endif#if defined(_WIN32_WCE)
typedef int intptr_t;
#endiftypedef void* bthread_fcontext_t;#ifdef __cplusplus
extern "C"{
#endifintptr_t BTHREAD_CONTEXT_CALL_CONVENTION
bthread_jump_fcontext(bthread_fcontext_t * ofc, bthread_fcontext_t nfc,intptr_t vp, bool preserve_fpu = false);
bthread_fcontext_t BTHREAD_CONTEXT_CALL_CONVENTION
bthread_make_fcontext(void* sp, size_t size, void (* fn)( intptr_t));#ifdef __cplusplus
};
#endif#endif // BTHREAD_CONTEXT_H
所谓bthread,或者说一众fiber的本质,就是能绕过系统调用(线程切换),实现执行状态的切换(每个程序,都是一个状态机,cpu就是无情的不断执行指令的机器),而context,就是保存当前执行状态的struct,该空间是在用户态被创建,所以同线程下的context间的切换,一般不涉及系统调用。
bthread的context提供两个方法:
bthread_make_fcontext
和 bthread_jump_fcontext
, 用来创建一个context,以及保存当前context并跳转到另一个context。
简单实例
#include <bthread/context.h>
#include <iostream>
#include <bthread/bthread.h>::bthread_fcontext_t fc1;
::bthread_fcontext_t fc2;
::bthread_fcontext_t fc_tmp;
intptr_t g_num1 = 0;
intptr_t g_num2 = 0;static void jumper1(intptr_t param) {for(int i = 0; i < 9; i++) {LOG(INFO) << "context1: g_num1 = " << ++g_num1 << ", then jump to context2";::bthread_jump_fcontext(&fc1, fc2, 0);}// 最后结尾,jump到原始上下文中,继续执行LOG(INFO) << "context1: g_num1 = " << ++g_num1 << ", then jump to main";::bthread_jump_fcontext(&fc1, fc_tmp, 0);
}
static void jumper2(intptr_t param) {for(int i = 0; i < 10; i++) {LOG(INFO) << "context2: g_num2 = " << ++g_num2 << ", then jump to context1";::bthread_jump_fcontext(&fc2, fc1, 0);}
}int main() {static const std::size_t stack_size = 8192;// 创建两个上下文void *sp1 = ::malloc(stack_size);fc1 = ::bthread_make_fcontext((char*)sp1 + stack_size, stack_size, jumper1);void *sp2 = ::malloc(stack_size);fc2 = ::bthread_make_fcontext((char*)sp2 + stack_size, stack_size, jumper2); // 将当前上下文保存到fc_tmp,并切换上下为fc2::bthread_jump_fcontext(&fc_tmp, fc2, 0);LOG(INFO) << "done";::free(sp1);::free(sp2);return 0;
}
结果:
root@ac2342fc89f6:/home/brpc/example/demo/build# ./context
I0410 20:34:45.199594 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 1, then jump to context1
I0410 20:34:45.199747 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 1, then jump to context2
I0410 20:34:45.199760 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 2, then jump to context1
I0410 20:34:45.199767 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 2, then jump to context2
I0410 20:34:45.199771 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 3, then jump to context1
I0410 20:34:45.199777 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 3, then jump to context2
I0410 20:34:45.199780 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 4, then jump to context1
I0410 20:34:45.199787 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 4, then jump to context2
I0410 20:34:45.199789 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 5, then jump to context1
I0410 20:34:45.199792 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 5, then jump to context2
I0410 20:34:45.199799 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 6, then jump to context1
I0410 20:34:45.199802 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 6, then jump to context2
I0410 20:34:45.199804 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 7, then jump to context1
I0410 20:34:45.199807 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 7, then jump to context2
I0410 20:34:45.199809 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 8, then jump to context1
I0410 20:34:45.199812 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 8, then jump to context2
I0410 20:34:45.199820 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 9, then jump to context1
I0410 20:34:45.199822 74060 0 /home/brpc/example/demo/context.cpp:21] context1: g_num1 = 9, then jump to context2
I0410 20:34:45.199825 74060 0 /home/brpc/example/demo/context.cpp:30] context2: g_num2 = 10, then jump to context1
I0410 20:34:45.199828 74060 0 /home/brpc/example/demo/context.cpp:25] context1: g_num1 = 10, then jump to main
可以发现,整个上下文的切换,均在同一个线程上完成,切换的速度也相当快,几纳秒到几十纳秒即可完成。
上述代码的逻辑也非常简单,在堆上申请两块内存来作为context(类比thread的栈),然后开始不断来回切换:保存jumper1的状态,并切换回jumper2; 保存jumper2的状态,并切换回jumper1。
这两个函数的逻辑是由汇编写的,bthread_make_fcontext
大致的逻辑是构造一个栈结构(实际上在OS视角,就是用户申请的堆内存)。
bthread_jump_fcontext
则是保存当前函数调用的状态,或者说上下文(即各种寄存器、栈指针等等),加载另一个函数调用的上下文。
一旦切换context,cpu就会按照context对应的位置,开始无情地执行指令,直到被再次切换。
tips:一个小细节,如果jumper1最终不切回main的context,从 LOG(INFO) << "done";
开始就不会被执行。
这两个函数的具体实现细节可移步brpc了解(参考链接)。
参考
https://brpc.apache.org/zh/docs/blogs/sourcecodes/bthread/