brpc源码解析(十五)—— bthread栈创建和切换详解

本文深入探讨了bthread机制,解析其在Linux环境下的本质及如何在用户态完成线程切换,减少上下文切换开销。文章详细介绍了bthread的创建过程、堆栈结构及其切换原理,并辅以x86-64架构下的汇编指令进行说明。
摘要由CSDN通过智能技术生成


以前的文章讲到了bthread的相关机制,但主要是调度的规则等总体上的流程,关于bthread本身创建和切换相关的细节没有太多涉及,好久没看brpc代码了,这篇聊一下bthread是如何实现在pthread上进行创建和切换的。

一.bthread的本质

我们常说的linux线程指的是Light-weight process(轻量级进程,简成LWP), 在NPTL(Native POSIX Thread Library)的实现里,也就是pthread的实现里,是1:1的,也就是一个pthread对应一个LWP,而bthread则是M:N的,具体到实现上就是bthread运行在pthread上,并且可以在不同的pthread之间切换,一段时间内,同一个pthread可以运行不同的bthread,这很类似于协程,只不过我们常说的协程是N:1线程库,即所有的协程都运行于一个系统线程中,但除了这一点, bthead和协程很类似,它和协程一样,对于一个pthread来说,无论是bthread还是传统意义上的协程,核心都是如何在用户态完成协程或者bthread的切换,以减少上下文切换的开销。通俗地讲可以理解为如何进行各个子程序的切换。我们知道,程序在运行过程中的独有的状态主要有包含局部变量的栈和各个cpu寄存器,要切换本质上就是这些上下文的切换,目前开源生态里有不少组件提供了这些上下文的保存和切换,用于方便大家在用户态切换协程,比较有名的有boost::context,bhtread用到的则是libcontext,一个boost::context的轻量级版本。bthread的切换,具体到实现上,就是每个bthread有一块自己的栈存储空间,切换的时候就是切换栈顶指针和从栈里存取寄存器值。

二、相关汇编基础知识

为了更好的理解,我们需要先了解一下所用到的汇编的相关知识,这里简单介绍下相关的寄存器和指令。

2.1 x86-64CPU通用寄存器

通用寄存器分为寄存器分为被调用者保存寄存器,和调用者保存寄存器。假设有P调用Q的过程,如果值放在了被调用者保存寄存器中,那么需要保证它们的值在Q返回到P时与P刚调用Q时是一样的。Q如何保证这些值不变对于P来是透明的。

标号作用
%rax返回值
%rbx被调用者保存寄存器
%rcx第4个参数
%rdx第3个参数
%rsi第2个参数
%rdi第1个参数
%rbp被调用者保存寄存器
%rsp栈指针
%r8第5个参数
%r9第6个参数
%r10调用者保存寄存器
%r11调用者保存寄存器
%r12被调用者保存寄存器
%r13被调用者保存寄存器
%r14被调用者保存寄存器
%r15被调用者保存寄存器

2.2 切换用到的x86-64常用汇编指令

指令作用
pushq将寄存器的值入栈
popq值从栈pop到寄存器里
movq将一个寄存器的值保存到另一个寄存器
leaq将地址直接赋值给操作数
cmp比较两个操作数的大小,比较结果存入flag寄存器,eg:执行完ZF=1说明相等,因为零标志为1说明结果为0
je根据ZF标志以决定是否转移,ZF=1则跳转
jmp无条件跳转
stmxcsr将MXCSR寄存器中的值保存到操作数中
ldmxcsr将操作数中的值加载到MXCSR寄存器中
fnstcw把控制寄存器的内容存储到由操作数指定的字存储单元
fldcw将由操作数指定的字存储单元内容存储到控制寄存器中

三、bthread堆栈创建

bthread栈结构如下,bthread_fcontext_t是void*的别名,bthread_fcontext_t context是栈顶指针:
在这里插入图片描述

创建bthread栈的工厂类如下:
在这里插入图片描述
其中最核心的代码分别是:
(1)分配存储空间:allocate_stack_storage(&storage, *StackClass::stack_size_flag,FLAGS_guard_page_size)
(2)构造栈内部结构:context = bthread_make_fcontext(storage.bottom, storage.stacksize, entry)
其中(1)暂时不展开讨论,(2)的定义如下:
在这里插入图片描述
返回bthread_fcontext_t,也就是栈顶指针,函数有三个参数,第一个参数是bthread栈底指针,第二个是bthread栈大小,第三个是bthread的入口函数地址。

bthread_make_fcontext是直接用汇编写的,代码和解释如下:
在这里插入图片描述
可以看到,经过这些代码构造出的bthread栈结构如下:
在这里插入图片描述
中间这么多空值是为了和后面堆栈切换的结构相对应。

四、bthread堆栈切换

bthread通过调用jump_stack进行切换,如下:
在这里插入图片描述
在这里插入图片描述
jump_stack调用的是bthread_jump_fcontext,也是直接用汇编实现的,代码和解释如下:
在这里插入图片描述
其中需要注意的是popq r8,在bthread调用切换函数的时候会push rip,对于旧堆栈这个popq取到的就是这个原来push的rip的值。

切换整体上可以分为两大类,切换到一个新建的bthread和切换到一个运行过但未结束的bthread,以上处理过程都能覆盖,前面讲到的新建stack中间空置的部分就是为了对应这个处理流程,新栈和旧栈顶部的结构都是一样的,都是数据寄存器数据的区域,区别就在于下面是否有其他数据,新栈直接就到底部了,运行过的会有原来的其他数据。

参考
brpc源码学习(二)-bthread的创建与切换
x86-64中的寄存器
深入理解计算机系统》 练习题3.27-3.28 被调用者保存寄存器 栈指针

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值