In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/03 Report--
In the context switching of libgo, we do not realize the tasks of creating and maintaining stack space, saving and switching CPU register execution state information, but use Boost.Context directly. As the underlying support library of many collaborative programs, the performance of Boost.Context has been optimized all the time.
What Boost.Context does is that the abstract state information of current execution (stack space, stack pointer, CPU register and state register, IP instruction pointer) can be saved in the traditional thread environment, then the current execution state is suspended, and the execution process of the program jumps to other locations to continue execution. This basic construction can be used to open up user-mode threads, thus building more advanced operation interfaces such as co-programs. At the same time, because the switch is in user space, the resource consumption is very small, and all the information of stack space and execution state is saved at the same time, so the functions in it can be nested freely.
Quoted from https://yq.aliyun.com/ziliao/43404
1. Fcontext_t
Libgo/context/fcontext.h
The underlying implementation of Boost.Context is to save the state of the co-program through the fcontext_t structure, create the co-program using make_fcontext, and use jump_fcontext to realize the co-program switching. In the libgo protocol, these two interface functions are referenced directly. The internal implementation of boost is not discussed here. If you are interested, you can check it in the link above.
/ / all contents and statements in Boost.Context are consistent with extern "C" {typedef void* fcontext_t; typedef void (* fn_t) (intptr_t); / * * context of switching from ofc to nfc * * / intptr_t jump_fcontext (fcontext_t * ofc, fcontext_t nfc,intptr_t vp, bool preserve_fpu = false) / * * create upper and lower query objects * * / fcontext_t make_fcontext (void* stack, std::size_t size, fn_t fn);}
In addition, a series of stack functions are provided
Struct StackTraits {static stack_malloc_fn_t& MallocFunc (); static stack_free_fn_t& FreeFunc (); / / get the number of protection pages currently set at the top of the stack static int & GetProtectStackPageSize (); / / A protection static bool ProtectStack for the contents of the protection page (void* stack, std::size_t size, int pageSize) / / cancel the memory protection of the protected page, and static void UnprotectStack (void* stack, int pageSize) will be called only after destructing.}
When the user goes to manage the collaborative stack, a little attention will arise when the access stack is out of bounds. Read-only operation is fine, but if a write operation is performed, the whole program will collapse directly, so stack protection is still necessary.
Stack protection
Libgo protects stack pairs, using mprotect system call implementation. When we create a N-byte pair stack space for the protocol, we will protect part of the space at the top of the stack, so the size of the allocated stack should be greater than the number of memory pages to be protected plus one.
Why is the protection stack always on a page basis? Because mprotect is set on a page-by-page basis, if there is no alignment of addresses, you should do it first and then do it later.
The size of bool StackTraits::ProtectStack (void* stack, std::size_t size, int pageSize) {/ / protocol stack should be greater than (number of protected memory pages + 1) if (! pageSize) return false; if ((int) size 0xf7235000 void* protect_page_addr = ((std::size_t) stack & 0xfff)? (void*) (std::size_t) stack & std::size_t 0xfff) + 0x1000): stack / / use mprotect system call to implement stack protection. PROT_NONE indicates that the memory space is inaccessible if (- 1 = = mprotect (protect_page_addr, getpagesize () * pageSize, PROT_NONE)) {DebugPrint (dbg_task, "origin_addr:%p, align_addr:%p, page_size:%d, protect_page:%u, protect stack stack error:% s", stack, protect_page_addr, getpagesize (), pageSize, strerror (errno)) Return false;} else {DebugPrint (dbg_task, "origin_addr:%p, align_addr:%p, page_size:%d, protect_page:%u, protect stack success.", stack, protect_page_addr, pageSize, getpagesize ()); return true;} remove stack protection
Undoing stack protection is called only when the protocol space is freed.
Void StackTraits::UnprotectStack (void * stack, int pageSize) {if (! pageSize) return; void * protect_page_addr = ((std::size_t) stack & 0xfff)? (void*) (std::size_t) stack & std::size_t 0xfff) + 0x1000): stack / / allow the block to be readable and writable if (- 1 = = mprotect (protect_page_addr, getpagesize () * pageSize, PROT_READ | PROT_WRITE)) {DebugPrint (dbg_task, "origin_addr:%p, align_addr:%p, page_size:%d, protect_page:%u, protect stack stack error:% s", stack, protect_page_addr, getpagesize (), pageSize, strerror (errno)) } else {DebugPrint (dbg_task, "origin_addr:%p, align_addr:%p, page_size:%d, protect_page:%u, protect stack success.", stack, protect_page_addr, pageSize, getpagesize ());}} mprotect system call instructions # include int mprotect (void * addr, size_t len, int prot) Addr: should be its memory address per page len: protected memory page size, so the protected address range should be [addr, addr+len-1] prot: protection type PROT_NONE The memory cannot be accessed at all. PROT_READ The memory can be read. PROT_WRITE The memory can be modified. PROT_EXEC The memory can be executed.2. Context
Libgo/context/context.h
Context is a context object encapsulated in libgo, and each co-program has a unique copy.
Class Context {public: / * * Construction * * / Context (fn_t fn, intptr_t vp, std::size_t stackSize); / / context switching interface ALWAYS_INLINE void SwapIn (); ALWAYS_INLINE void SwapTo (Context & other); ALWAYS_INLINE void SwapOut (); fcontext_t& GetTlsContext (); private: fcontext_t ctx_; fn_t fn_ / / intptr_t vp_; / / the co-program Task object pointer char* stack_ = nullptr; / / stack space uint32_t stackSize_ = 0 to which the current context belongs; / / stack size int protectPage_ = 0; / / number of protection pages}
There is no explanation for this class except for private members. Most of the work is done in the constructor, including opening up stack space, creating context, setting protection pages, and so on.
Default configuration
The number of pages set for stack protection pages, as well as the default stack size, are configured in CoroutineOptions. In the coroutine.h file
# define co_opt:: co::CoroutineOptions::getInstance ()
Therefore, you can use the co_opt object directly to modify the default configuration.
Can be referenced
Test/gtest_unit/protect.cpp3. Assembler to realize context switching
Implemented by the assembly
The Chinese notes after the double slash are newly added by myself.
The function implemented by assembly is actually
Intptr_t jump_fcontext (fcontext_t * ofc, fcontext_t nfc,intptr_t vp, bool preserve_fpu = false)
The assembly code is as follows:
.text / / declares that jump_fcontext is a globally visible symbol. Globl jump_fcontext.type jump_fcontext,@function.align 16jump_fcontext: / / saves the data storage register of the current protocol Save pushq% rbp / * save RBP * / pushq% rbx / * save RBX * / pushq% R15 / * save R15 * / pushq% R14 / * save R14 * / pushq% R13 / * save R13 * / pushq% R12 / * save R12 * / rsp stack top register down 8 bytes Reserve / * prepare stack for FPU floating point operation register * / leaq-0x8 (% rsp) for the new coprogram FPU floating point operation.% rsp / /% rcx is the fourth parameter of the function. Je determines that if it is equal to, jump to the place marked as 1. F (forword) / / fpu is the floating point operation register / * test for flag preserve_fpu * / cmp $0 % rcx je 1f / / Save MXCSR contents rsp register / * save MMX control- and status-word * / stmxcsr (% rsp) / / Save the current FPU status word to the location of rsp+4 / * save x87 control-word * / fnstcw 0x4 (% rsp) 1: / / Save the current stack top location to rdi / * store RSP (pointing to context-data) in RDI * / movq% rsp (% rdi) / / modify the address on top of the stack Move 8 bytes up to the address of the new protocol / * restore RSP (pointing to context-data) from RSI * / movq% rsi,% rsp / * test for flag preserve_fpu * / cmp $0,% rcx je 2f / * restore MMX control- and status-word * / ldmxcsr (% rsp) / * restore x87 control-word * / fldcw 0x4 (% rsp) 2: / / rsp stack top register Revert to reserved space for FPU floating point operations / * prepare stack for FPU * / leaq 0x8 (% rsp) % rsp / / restore popq% R12 / * restrore R12 * / popq% R13 / * restrore R13 * / popq% R14 / * restrore R14 * / popq% R15 / * restrore R15 * / popq% rbx / * restrore RBX * / popq% rbp / * restrore RBP * / / put the return address in the R8 register / * restore Return-address * / popq% R8 / / the task to which the original protocol belongs is stored in the rax register / * use third arg as return-value after jump * / movq% rdx as the function return value. % rax / / put the task address of the current protocol to the location of the first parameter (that is, replace the context address of the current protocol) / * use third arg as first arg in context function * / movq% rdx,% rdi / / jump to the return address / * indirect jump to context * / jmp *% r8.size jump_fcontext,.-jump_fcontext switching process
Take switching from co-program A to co-program B as an example:
Intptr_t jump_fcontext (fcontext_t * ofc, fcontext_t nfc, intptr_t vp, bool preserve_fpu = false)
Instruction description # directive text: specifies that the subsequent compiled content is placed in the code snippet [executable]; global: tells the compiler that it is followed by a globally visible name [either a variable or a function name] Align num: to align the directive, num must be an integer power of 2 to tell the assembler that the memory variables under this directive must start allocating registers from the next address divisible by Num
All registers of X86-64 are 64-bit, with only identifiers changing compared to 32-bit systems, such as% ebp- >% rbp
X86-64 added% r8~%r15 8 registers; # X86-64 register states that% rax uses% rsp stack pointer register as the function return value, pointing to% rdi,%rsi,%rdx,%rcx,%r8,%r9 at the top of the stack as the function parameter, corresponding to the first parameter and the second parameter in turn. % rbx,%rbp,%r12,%r13, is used for data storage. It follows the rules used by the callee. To put it simply, back it up before invoking the subfunction to prevent it from being modified by% R10Magi% R11 as data storage and following the caller's rules. To put it simply, save the original value before using it.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.