Add some blogs to Chinese pages (#76) master
authorHU <uestc.hugo@gmail.com>
Fri, 17 Jun 2022 01:58:29 +0000 (09:58 +0800)
committerGitHub <noreply@github.com>
Fri, 17 Jun 2022 01:58:29 +0000 (09:58 +0800)
* adding security and committer guide docs

* remove .DS_Store files

* bugfix committer docs

* update brpc users page

* fix all docs link-jump problems

* update getting_start and json2pb docs

* add some blogs in chinese pages

Co-authored-by: XiguoHu <huxiguo@baidu.com>
content/zh/docs/blogs/shares/_index.md [new file with mode: 0644]
content/zh/docs/blogs/shares/brpc guide for beginners/index.md [new file with mode: 0644]
content/zh/docs/blogs/sourcecodes/_index.md [new file with mode: 0644]
content/zh/docs/blogs/sourcecodes/bthread/index.md [new file with mode: 0644]
static/images/docs/bthread_jump_fcontext.png [new file with mode: 0644]
static/images/docs/bthread_make_fcontext.png [new file with mode: 0644]

diff --git a/content/zh/docs/blogs/shares/_index.md b/content/zh/docs/blogs/shares/_index.md
new file mode 100644 (file)
index 0000000..da4ef11
--- /dev/null
@@ -0,0 +1,5 @@
+---
+title: "分享"
+linkTitle: "分享"
+weight: 30
+---
\ No newline at end of file
diff --git a/content/zh/docs/blogs/shares/brpc guide for beginners/index.md b/content/zh/docs/blogs/shares/brpc guide for beginners/index.md
new file mode 100644 (file)
index 0000000..ee4e315
--- /dev/null
@@ -0,0 +1,167 @@
+---
+title: "brpc初学者指南"
+linkTitle: "brpc初学者指南"
+weight: 2
+date: 2021-11-30
+---
+(作者简介:lorinlee,是brpc新进committer,在字节跳动负责图数据库相关工作)
+
+Apache brpc(incubating)  是一款优秀的工业级 C++ RPC 框架,其兼具高性能、兼容多种协议、周边工具完善等多种优点于一身,已在国内多个大厂广泛使用。
+
+有不少同学对brpc感兴趣,但还没有找到合适的路径,不知道如何学习。笔者是来自字节跳动的研发工程师,也曾遇到相同的困扰,后来通过自己的一些摸索,成为了brpc committer,期间也总结了一些经验,希望对大家有所帮助。
+
+因此本文期望结合作者的经历,通过一些简单可行的步骤,帮助大家迈出探索brpc的脚步,学习它的设计与实现方式,以对大家日常的学习和工作有所帮助。也欢迎大家在熟悉brpc之后,参与到brpc的贡献中,比如文档完善、解决issue、bugfix、feature开发等等。
+
+## 文档
+初始接触brpc,文档是最好的入手材料。brpc的文档在国内开源项目中广受赞誉,原因是其不仅介绍了丰富的技术知识,还蕴含了设计者深入的思考与选择。
+
+首先推荐阅读:[bRPC Overview](https://brpc.apache.org/docs/overview/),这篇文档不仅综述性地介绍了brpc,它更是一篇索引,关键的技术都有超链接至对应的细节文档,阅读完这篇文档以及所有的链接,将能够对brpc有完整的认识。
+
+另外,有些文档虽然在上面链接中已经涵盖了,但我依然想在这里再单独分类推荐一下:
+
+1. IO:RPC框架作为网络通信框架,IO模型是非常重要的一环,其定义了客户端如何发送消息,服务端如何接受消息,这篇文档详细介绍了brpc对于这一过程的实现。常见的RPC框架会区分IO线程和Worker线程,但这样的设计是有弊端的。brpc则不对线程做区分,IO逻辑和业务逻辑全都在bthread中,通过调度器平衡任务在线程之间的负载。另外,brpc支持多种协议,并支持灵活的拓展,可以参考 protocol。
+
+2. 线程模型,bthread:RPC框架另一个重要的部分是线程模型,brpc自研了用户态调度的M:N线程库bthread,既能达到优异的性能,又不会损失代码的可维护性。
+
+3. bthread_id:这里的bthread_id并不是bthread本身的id(bthread_t),而是用于对同一个request的多次重试、超时处理等并发事件进行同步,以及在连接复用(单连接)的场景,维护request和response的映射。
+
+4. IOBuf:RPC框架需要处理大量接收的消息、发送的消息,其中消息占用的内存分配在很多场景都可能会成为一大开销,brpc通过IOBuf减少内存分配、拷贝等开销。
+
+5. Timer:RPC场景需要处理大量的时间事件,主要是对于超时的控制,比如对于一个请求,需要在其超时后及时终止并结束。一般的系统在吞吐上升的过程中,Timer的压力会不断增加,其拓展性会成为性能瓶颈。而brpc对Timer做了细致的设计,使timer操作几乎对RPC性能没有影响。
+
+6. MemoryManagement:brpc实现了内存池对一些对象进行管理以提升性能,并且可以对这些对象附加version,简化了共享对象的回收逻辑。
+
+7. bvar:系统服务通常需要采集大量的指标,用于系统监控、性能优化等,但通常大量的指标采集会严重影响性能,有些做法是通过采样来降低开销,但会导致指标不够准确。brpc的bvar则能够高效的收集大量指标。
+
+## 源码
+文档能够快速了解系统的核心设计思路,但如果要深入理解系统,还是需要对源码进行阅读。brpc是一款优秀的C++项目,其源码是值得大家阅读学习的。这里简单为大家把源码进行一些介绍,方便大家阅读。
+
+整体的源码阅读过程,推荐先按照某些关键的执行流程来阅读,梳理其中的各个环节步骤,就能够理解每个模块在整个系统所处的位置;随后单独对重要的模块研读其实现,就能够比较好地掌握整个系统。
+
+RPC框架一般会用在两个端,客户端和服务端,两者有些流程是共用的,有些是不同的。所以可以将简单的echo服务为出发点,分别对客户端和服务端的流程进行阅读。
+
+### 客户端
+对于客户端,典型的流程比如:客户端的初始化,发起RPC请求,处理应答,以及处理超时等逻辑。这里对一些关键的过程进行简述,大家可以在阅读过程中搜寻下述的一些关键函数名,找到代码对应的位置:
+
+1. 客户端的初始化:Channel::Init,这里会做全局的初始化(GlobalInitializeOrDie),NamingService线程、LoadBalancer的初始化等。
+
+2. 发起RPC请求:Channel::CallMethod,这里会分配call_id(bthread_id),序列化请求内容(_serialize_request -> Protocol::SerializeRequest),注册BackupRequest/Timeout的事件,选择要发送消息的Socket,打包协议内容(_pack_request -> Protocol::PackRequest),消息写入fd(Socket::Write)。
+
+3. 处理应答:客户端在创建Socket时,会将对应的fd放在EventDispatcher中,并注册对应的回调函数(InputMessenger::OnNewMessages)。在Linux系统下,当fd有可读数据,EventDispatcher::Run中会通过epoll_wait拿到对应fd,调用Socket::StartInputEvent,并最终回调至InputMessenger::OnNewMessages。在InputMessenger::OnNewMessages中,会从fd中读取数据,按请求粒度切分数据,并将每个请求通过协议定义的处理函数中进行执行(Protocol::ProcessResponse),并最终调用至Controller::OnVersionedRPCReturned结束RPC,或执行重试等。
+
+4. 处理超时:超时事件是注册在Timer线程中的,在RPC超时后,Timer线程会回调HandleTimeout,并经由bthread_id的机制回调至Controller::HandleSocketFailed,最终也会到达Controller::OnVersionedRPCReturned,在这里结束RPC或执行重试等。
+
+### 服务端
+对于服务端,典型的流程包括:服务的启动,收到RPC请求,执行处理,返回应答等逻辑。这里也对一些关键的过程进行简述,方便大家找到对应的代码位置:
+
+1. 服务启动:Server::Start,这里会做一些初始化(Server::InitializeOnce),数据存储工厂初始化(SessionData,ThreadLocalData,bthread_local),创建Acceptor用于接受客户端连接。
+
+2. 收到RPC请求:服务端收到的请求也是通过EventDispatcher进行派发的。在收到客户端连接请求时,EventDispatcher会回调Acceptor::OnNewConnections进行新连接建立,并将新连接也托管至EventDispatcher。在连接收到数据后,会由EventDispatcher回调至InputMessenger::OnNewMessages,从fd中读取数据,对数据按请求粒度进行切分,并调用协议中注册的处理函数(Protocol::ProcessRequest)进行处理。(客户端和服务端在这里注册的回调是不同的)。
+
+3. 执行处理:请求的处理逻辑与协议有关,不过大体的逻辑是相似的,这里以baidu_std协议为例简述一下处理流程。baidu_std协议的请求处理逻辑在ProcessRpcRequest中,其会设置一些上下文信息,并发控制与限流,反序列化协议头和元数据,根据元数据找到具体的请求信息,反序列化请求体,注册RPC结束(done->Run())的回调函数(SendRpcResponse),并调用用户注册的Service对应方法进行处理。
+
+4. 返回应答:在用户处理函数处理结束后,通常会调用done->Run()结束RPC,此时会回调上面注册的函数SendRpcResponse,将用户写好的Response,连同协议头和元数据一起序列化,写入fd中。
+
+### 源码目录
+这里再对brpc的源码目录做个基本的介绍,方便大家有个整体认识。首先推荐大家阅读brpc中的示例,其能够帮助大家对brpc的使用场景和使用方法有所了解,并在后续对核心系统源码阅读的过程能够带着使用场景阅读。
+
+example目录下,主要有几个目录:
+
+1. echo_c++:同步客户端的echo示例
+
+2. asynchronous_echo_c++:异步客户端的echo示例
+
+3. backup_request_c++:客户端开启backup_request示例
+
+4. cancel_c++:客户端取消rpc示例
+
+5. parallel_echo_c++:客户端同时访问多个服务示例
+
+6. partition_echo_c++:客户端分区调用的示例
+
+7. dynamic_partition_echo_c++:客户端动态分区调用的示例
+
+8. selective_echo_c++:客户端多Channel间负载均衡示例
+
+9. multi_threaded_echo_c++:客户端多线程echo示例
+
+10. multi_threaded_echo_fns_c++:客户端多线程+服务端多server示例
+
+11. auto_concurrency_limiter:服务端开启自适应限流并验证其有效性
+
+12. cascade_echo_c++:服务端级联调用示例
+
+13. session_data_and_thread_local:服务端使用SessionData和ThreadLocalData
+
+14. grpc_c++:grpc协议示例
+
+15. http_c++:http协议示例
+
+16. memcache_c++:memcache客户端示例
+
+17. redis_c++:redis协议示例
+
+18. thrift_extension_c++:thrift协议示例
+
+19. streaming_echo_c++:大块数据传输示例
+
+
+而brpc系统核心的实现在src目录中,其主要有几个目录:
+
+1. brpc  
+brpc目录下主要是RPC框架的实现,内容相对比较多,涵盖了网络通信框架、支持的各种协议、builtin服务等,以及包括负载均衡、服务发现、限流等。
+
+2. bthread  
+bthread目录下主要是用户态调度的M:N线程库的实现。其中包括bthread调度器、bthread同步原语、timer线程等。
+
+3. butil  
+butil目录下是丰富的工具库,包括了对文件、网络、时间、线程、内存、字符串、日志、容器等等各种基础组件的封装,以及一些第三方库。
+
+4. bvar  
+bvar目录下是指标收集的实现。
+
+## 实践
+俗话说:“纸上得来终觉浅,绝知此事要躬行”。在对源码有一定的阅读之后,就推荐大家具体地解决一些问题,这样能够对系统可以有更好地理解。
+
+### Issue
+首先可以解决github issue。brpc有很多的用户,用户会在使用过程中遇到不同的问题,并发布在github issue中。通过对这些issue的解答,不仅能够加深对系统的认识,并且能够帮助到其他人,是一件非常有意义的事情。
+
+当然,大家在使用过程中遇到的问题,也欢迎提在issue中,比如对系统改进的一些想法、发现的一些bug等。issue对比群聊的好处在于之前的问题可以追溯,同样的问题讨论一遍之后便会有完整的记录,如果后来的人遇到类似的问题,可以直接查阅前面的issue自行解决。
+
+此外,之前的issue中对一些问题的讨论,也是非常好的学习材料。其中有些issue依然没有结论,期待着大家参与交流。
+
+brpc社区有很多的issue,对于issue的管理,会给一些issue打上对应的lebel,目前label的类型有如下几种:
+
+1. bug:确认为bug的问题
+
+2. discussion:讨论
+
+3. enhancement:待改进的问题,需要优化
+
+4. feature:新特性,需要开发
+
+5. good first issue:适合brpc初学者解决的问题
+
+6. help wanted:适合对brpc有一些了解的同学解决的问题
+
+7. official:由brpc官方提出的问题
+
+8. regression:兼容性问题
+
+9. security:安全性问题
+
+10. wontfix:不再修复的问题
+
+初学的同学可以选择 "good first issue" 中的问题进行解决,以及 "help wanted" 中的也可以尝试。
+
+### PR
+另外的实践方式就是提PR了。任何对项目有益的PR,不论它改进程度的大小,都是非常好的。可做的比如编写/翻译文档、修复bug、开发feature、代码重构、性能优化、修复typo等等。
+
+通过提PR,大家会实际动手参与到brpc的建设中,会对系统有更好地理解,并且会更广泛地帮助到所有使用brpc的用户;而brpc项目也会因大家的贡献而一点点进步。
+
+如果想提PR,但对做什么有些迷茫,可以参考一些label下的issue,比如 "good first issue","help wanted" 等,都比较适合初学者。当慢慢熟悉后,可以尝试解决 "enhancement","official","feature","bug" 等label下的问题。
+
+## 结语
+brpc是一个优秀的C++项目,值得大家学习它的设计与实现。但brpc同时也是相对有些复杂的项目,其有13w+行代码,需要一定的学习过程。
+
+本文推荐了大家一些学习的入手点,希望能帮助大家更好地入门,并在学习过程中有丰厚的收获。当然,也期待大家在熟悉brpc之后,能够参与到brpc的贡献中,帮助到大量brpc的使用者。
diff --git a/content/zh/docs/blogs/sourcecodes/_index.md b/content/zh/docs/blogs/sourcecodes/_index.md
new file mode 100644 (file)
index 0000000..8c0c72f
--- /dev/null
@@ -0,0 +1,5 @@
+---
+title: "源码解析"
+linkTitle: "源码解析"
+weight: 40
+---
\ No newline at end of file
diff --git a/content/zh/docs/blogs/sourcecodes/bthread/index.md b/content/zh/docs/blogs/sourcecodes/bthread/index.md
new file mode 100644 (file)
index 0000000..df295cd
--- /dev/null
@@ -0,0 +1,268 @@
+---
+title: "bRPC源码解析·bthread机制"
+linkTitle: "bRPC源码解析·bthread机制"
+weight: 2
+date: 2022-06-16
+---
+## bthread简介
+官方文档:https://brpc.apache.org/zh/docs/bthread/bthread/  
+
+bthread是bRPC使用的M:N线程库,类似协程,即用户态线程,也因此bthread的切换不会陷入内核,不会进行一系列内存同步等耗时操作,从bthread_benchmark中可以看到bthread的创建时间和调度时间相较pthread有着数量级的提升,将大量的bthread映射至少量的内核线程pthread上执行,降低内核上下文切换开销,在充分利用多核的同时,具有更好的cache locality。
+
+为了实现协程需要协程栈、协程的初始化,以及协程间的切换,接下来逐一分析一下这几个过程。在分析之前需要首先补充一点汇编知识以方便我们阅读其中的汇编代码。
+
+## 基础汇编知识
+首先语法习惯,代码中的是AT&T风格的汇编语言,gdb看反汇编默认的风格也是AT&T。
+
+### AT&T风格的汇编语言
+* 立即数:$ 开头
+* 寄存器:% 开头
+* 取地址里面的值:偏移量(%寄存器)
+* 整形操作通用后缀:[B] Byte、[W] Word 2Byte、[L] Long 4Byte、[Q] QuadWord 8Byte
+* 浮点操作通用后缀:[S] Singled 4、[D] Double 8、[T] Extended 16(修饰精度: precision)
+
+### 函数的调用约定
+* 整型参数依次存放在 %rdi、%rsi、%rdx、%rcx、%8、%9
+* 浮点参数依次存放在%xmm0 - %xmm7中
+* 寄存器不够用时,参数放到栈中
+* 被调用的函数可以使用任何寄存器,但它必须保证%rbx、%rbp、%rsp和%12-%15恢复到原来的值
+* 返回值存放在%rax中
+
+### 调用函数前
+* 调用方要将参数放到寄存器中
+* 然后把%10、%11的值保存到栈中
+* 然后调用call 跳转到函数执行
+* 返回后,恢复%10、%11
+* 从%eax中取出返回值
+
+### x86_64含有16个64为整数寄存器
+* %rsi、%rdi 用于字符串处理
+* %rsp、%rbp 栈相关,栈从高地址到低地址 %rsp-->栈顶,push和pop会改变 %rbp-->栈基址
+* %8 ~ %15
+
+|寄存器|解释|
+|:---|:---|
+|// 创建部分||
+|%rax|临时寄存器;参数可变时传递关于 SSE 寄存器用量的信息;第 1 个返回值寄存器|
+|%rdi|用来给函数传递第 1 个参数|
+|%rsi|用来给函数传递第 2 个参数|
+|%rdx|用来给函数传递第 3 个整数参数|
+|%rcx|用来给函数传递第 4个整数参数|
+|%rsp|堆栈顶指针|
+|%rbp|堆栈基指针|
+|%rip|指令寄存器|
+|// 切换部分||
+|%rbx|被调者保存的寄存器;或用作基指针|
+|%r12-%r15|被调者保存的寄存器|
+
+## 协程栈的结构
+### 协程栈
+首先看下协程栈的结构:context指向协程栈顶,stacktype表示栈的类型(大小),storage为栈空间。
+``` c++
+/* bthread/bthread/stack.h */
+struct ContextualStack {
+    bthread_fcontext_t context;
+    StackType stacktype;
+    StackStorage storage;
+};
+
+enum StackType {
+    STACK_TYPE_MAIN = 0,
+    STACK_TYPE_PTHREAD = BTHREAD_STACKTYPE_PTHREAD,
+    STACK_TYPE_SMALL = BTHREAD_STACKTYPE_SMALL,
+    STACK_TYPE_NORMAL = BTHREAD_STACKTYPE_NORMAL,
+    STACK_TYPE_LARGE = BTHREAD_STACKTYPE_LARGE
+};
+
+struct StackStorage {
+    int stacksize;
+    int guardsize;
+    // Assume stack grows upwards.
+    // http://www.boost.org/doc/libs/1_55_0/libs/context/doc/html/context/stack.html
+    void* bottom;
+    unsigned valgrind_stack_id;
+
+    // Clears all members.
+    void zeroize() {
+        stacksize = 0;
+        guardsize = 0;
+        bottom = NULL;
+        valgrind_stack_id = 0;
+    }
+};
+```
+
+### 协程的初始化
+栈分配时(allocate_stack_storage(StackStorage* s, int stacksize_in, int guardsize_in))会通过mmap匿名映射一段空间,然后将高地址位赋值给bottom。
+``` c++
+/* bthread/bthread/stack_inl.h */
+template <typename StackClass> struct StackFactory {
+    struct Wrapper : public ContextualStack {
+        explicit Wrapper(void (*entry)(intptr_t)) {
+            if (allocate_stack_storage(&storage, *StackClass::stack_size_flag,
+                                       FLAGS_guard_page_size) != 0) {
+                storage.zeroize();
+                context = NULL;
+                return;
+            }
+```
+然后创建bthread协程栈,返回值为协程栈顶context,函数入参分别为协程栈底,栈大小,以及这个bthread要执行的函数entry。
+``` c++
+            context = bthread_make_fcontext(storage.bottom, storage.stacksize, entry);
+            stacktype = (StackType)StackClass::stacktype;
+        }
+        ...
+    };
+    ...
+};
+```
+``` c++
+#if defined(BTHREAD_CONTEXT_PLATFORM_linux_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc)
+__asm (
+".text\n"
+".globl bthread_make_fcontext\n"
+".type bthread_make_fcontext,@function\n"
+".align 16\n"
+"bthread_make_fcontext:\n"
+"    movq  %rdi, %rax\n"
+"    andq  $-16, %rax\n"
+"    leaq  -0x48(%rax), %rax\n"
+"    movq  %rdx, 0x38(%rax)\n"
+"    stmxcsr  (%rax)\n"
+"    fnstcw   0x4(%rax)\n"
+"    leaq  finish(%rip), %rcx\n"
+"    movq  %rcx, 0x40(%rax)\n"
+"    ret \n"
+"finish:\n"
+"    xorq  %rdi, %rdi\n"
+"    call  _exit@PLT\n"
+"    hlt\n"
+".size bthread_make_fcontext,.-bthread_make_fcontext\n"
+".section .note.GNU-stack,\"\",%progbits\n"
+);
+
+#endif
+```
+
+#### 代码中的汇编指令解释
+MXCSR状态管理指令(State Management Instructions),LDMXCSR与STMXCSR,用于控制MXCSR寄存器状态。
+* LDMXCSR指令从存储器中加载MXCSR寄存器状态
+* STMXCSR指令将MXCSR寄存器状态保存到存储器中
+
+|汇编指令|解释|
+|:---|:---|
+|movq  %rdi, %rax|"movq" is a move of a quadword (64-bit value),copy %src %dst,将第一个参数‘storage.bottom’拷贝到%rax寄存器|
+|andq  $-16, %rax|与&操作,将%rax的值,即高位地址‘bottom’ & -16(补码:0xfffffff0),将地址取为16的整数倍,进行16字节对齐。|
+|leaq  -0x48(%rax), %rax|leaq 取64位地址指令,将%rax中保存的高位地址向下偏移72个字节后,再保存到%rax寄存器,即留出 0x48 个字节用于存放上下文 Context Data|
+|movq  %rdx, 0x38(%rax)|将rdx保存至rax + 56,rdx为第三个参数,即函数fn的入口地址entry|
+|stmxcsr  (%rax)|存储 MMX 控制字和状态字,将MXCSR寄存器状态保存到rax所在位置|
+|fnstcw   0x4(%rax)|存储 x87 控制字,将 FPU 控制字的当前值存储到%rax + 4|
+|leaq  finish(%rip), %rcx|计算finish标志的绝对地址,将其保存至%rcx寄存器中|
+|movq  %rcx, 0x40(%rax)|将rcx保存到rax + 64,finish刚好位于启动函数上方 ---> 启动函数执行完以后就会执行finish处的代码,而finish会call _exit结束进程。|
+|ret|rax就是上述的基点(栈顶),返回的类型为fcontext_t|
+|finish:||
+|xorq  %rdi, %rdi|退出码是0|
+|call  _exit@PLT|call _exit结束进程|
+
+初始化过程可由下图直观表示:
+![bthread_make_fcontext](/images/docs/bthread_make_fcontext.png)
+
+### 协程的切换部分
+实现协程上下文切换有很多种方法,本质要做的都是保存和恢复寄存器和栈信息。
+``` c++
+inline void jump_stack(ContextualStack* from, ContextualStack* to) {
+    bthread_jump_fcontext(&from->context, to->context, 0/*not skip remained*/);
+}
+```
+调用时会将当前的上下文保存到ofc中,并切换到目标上下文nfc进行执行
+``` c++
+intptr_t BTHREAD_CONTEXT_CALL_CONVENTION
+bthread_jump_fcontext(bthread_fcontext_t * ofc, bthread_fcontext_t nfc,
+                      intptr_t vp, bool preserve_fpu = false);
+#if defined(BTHREAD_CONTEXT_PLATFORM_linux_x86_64) && defined(BTHREAD_CONTEXT_COMPILER_gcc)
+__asm (
+".text\n"
+".globl bthread_jump_fcontext\n"
+".type bthread_jump_fcontext,@function\n"
+".align 16\n"
+"bthread_jump_fcontext:\n"
+"    pushq  %rbp  \n"
+"    pushq  %rbx  \n"
+"    pushq  %r15  \n"
+"    pushq  %r14  \n"
+"    pushq  %r13  \n"
+"    pushq  %r12  \n"
+"    leaq  -0x8(%rsp), %rsp\n"
+"    cmp  $0, %rcx\n"
+"    je  1f\n"
+"    stmxcsr  (%rsp)\n"
+"    fnstcw   0x4(%rsp)\n"
+"1:\n"
+"    movq  %rsp, (%rdi)\n"
+"    movq  %rsi, %rsp\n"
+"    cmp  $0, %rcx\n"
+"    je  2f\n"
+"    ldmxcsr  (%rsp)\n"
+"    fldcw  0x4(%rsp)\n"
+"2:\n"
+"    leaq  0x8(%rsp), %rsp\n"
+"    popq  %r12  \n"
+"    popq  %r13  \n"
+"    popq  %r14  \n"
+"    popq  %r15  \n"
+"    popq  %rbx  \n"
+"    popq  %rbp  \n"
+"    popq  %r8\n"
+"    movq  %rdx, %rax\n"
+"    movq  %rdx, %rdi\n"
+"    jmp  *%r8\n"
+".size bthread_jump_fcontext,.-bthread_jump_fcontext\n"
+".section .note.GNU-stack,\"\",%progbits\n"
+);
+
+#endif
+```
+#### 代码中的汇编指令解释
+|汇编指令|解释|
+|:---|:---|
+|pushq  %rbp|将寄存器rbp-r12依此push到当前协程栈中保存|
+|pushq  %rbx|-|
+|pushq  %r15|-|
+|pushq  %r14|-|
+|pushq  %r13|-|
+|pushq  %r12|-|
+|leaq  -0x8(%rsp), %rsp|rsp栈顶下移8字节 --->prepare stack for FPU 浮点运算寄存器|
+|cmp  $0, %rcx|比较rcx和0,因为rcx为0,所以zf为1,rcx:第四个参数 preserve_fpu|
+|je  1f|因为zf为1,所以跳转|
+|stmxcsr  (%rsp)|存储 MMX 控制字和状态字,将MXCSR寄存器状态保存到rsp所在位置|
+|fnstcw   0x4(%rsp)|存储 x87 控制字,将 FPU 控制字的当前值存储到rsp + 4|
+|1f:||
+|movq  %rsp, (%rdi)|将rsp保存至rdi中,rsp指向当前协程栈栈顶(原上下文),rdi为第一个入参,即ofc|
+|movq  %rsi, %rsp|将rsi保存到rsp中,rsi为第二个参数,即nfc(目标上下文),此时栈顶指针rsp指向了新的协程栈|
+|cmp  $0, %rcx|比较rcx和0,因为rcx为0,所以zf为1|
+|je  2f|因为zf为1,所以跳转|
+|ldmxcsr  (%rsp)||
+|fldcw  0x4(%rsp)||
+|2f:||
+|leaq  0x8(%rsp), %rsp\n"|将rsp上移8字节|
+|popq  %r12|将协程栈中r12-rbp依次pop到对应寄存器|
+|popq  %r13|-|
+|popq  %r14|-|
+|popq  %r15|-|
+|popq  %rbx|-|
+|popq  %rbp|-|
+|popq  %r8|-|
+|movq  %rdx, %rax|将rdx保存到rax,rdx为第三个参数,rax为返回值|
+|movq  %rdx, %rdi|将rdx保存到rdi,rdi为第一个入参,因此将作为新协程运行的入参|
+|jmp  *%r8|跳转到r8对应的寄存器运行|
+
+在协程切换过程中有两种情况,第一种为新协程是通过bthread_make_fcontext函数刚刚创建的栈,另一种是已经运行过的栈,这两种过程分别如下图所示:
+![bthread_jump_fcontext](/images/docs/bthread_jump_fcontext.png)
+
+参考资料
+1. https://zhuanlan.zhihu.com/p/148314164
+2. http://wiki.baidu.com/display/RPC/bthread
+3. http://jinke.me/2018-09-14-coroutine-context-switch/
+4. http://cons.mit.edu/fa18/x86-64-architecture-guide.html
+5. https://blog.csdn.net/thisinnocence/article/details/50936470
+6. http://wiki.baidu.com/pages/viewpage.action?pageId=1303526733
diff --git a/static/images/docs/bthread_jump_fcontext.png b/static/images/docs/bthread_jump_fcontext.png
new file mode 100644 (file)
index 0000000..0927ba5
Binary files /dev/null and b/static/images/docs/bthread_jump_fcontext.png differ
diff --git a/static/images/docs/bthread_make_fcontext.png b/static/images/docs/bthread_make_fcontext.png
new file mode 100644 (file)
index 0000000..d3163d6
Binary files /dev/null and b/static/images/docs/bthread_make_fcontext.png differ