定义
cpp
/*
libcontext - a slightly more portable version of boost::context
Copyright 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 at
http://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;
#endif
typedef void* bthread_fcontext_t;
#ifdef __cplusplus
extern "C"{
#endif
intptr_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。
简单实例
cpp
#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;
}
结果:
cpp
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了解(参考链接)。