ucontext族函数的使用及原理分析
文章目錄
- ucontext介紹
- 寄存器介紹
- ucontext_t結構體
- getcontext
- setcontext
- makecontext
- swapcontext
- 使用示例
- 示例一、上下文的保存與恢復(getcontext、setcontext)
- 示例二、上下文的修改(makecontext)
- 示例三、上下文的切換(swapcontext)
ucontext介紹
寄存器介紹
以上是ucontext使用到的所有寄存器,下面對他們做一些簡單的介紹。
- %rax作為函數返回值使用
- %rsp棧指針寄存器, 指向棧頂
- %rdi, %rsi, %rdx, %rcx, %r8, %r9用作函數的參數,從前往后依次對應第1、第2、…第n參數
- %rbx, %rbp, %r12, %r13, %r14, %r15用作數據存儲,遵循被調用這使用規則,調用子函數之前需要先備份,防止被修改。
- %r10, %r11用作數據存儲,遵循調用者使用規則,使用前需要保存原值
ucontext_t結構體
typedef struct ucontext{unsigned long int uc_flags;struct ucontext *uc_link;// 當前上下文執行完了,恢復運行的上下文 stack_t uc_stack;// 該上下文中使用的棧mcontext_t uc_mcontext;// 保存當前上下文,即各個寄存器的狀態__sigset_t uc_sigmask;// 保存當前線程的信號屏蔽掩碼struct _libc_fpstate __fpregs_mem;} ucontext_t; //描述整個上下文 typedef struct{gregset_t gregs;//用于裝載寄存器/* Note that fpregs is a pointer. */fpregset_t fpregs;//所有寄存器的類型__extension__ unsigned long long __reserved1 [8]; } mcontext_t;----------------------------- //所包含的具體上下文信息 struct _libc_fpxreg { unsigned short int significand[4];unsigned short int exponent;unsigned short int padding[3]; };struct _libc_xmmreg { __uint32_t element[4]; };struct _libc_fpstate { /* 64-bit FXSAVE format. */__uint16_t cwd;__uint16_t swd;__uint16_t ftw;__uint16_t fop;__uint64_t rip;__uint64_t rdp;__uint32_t mxcsr;__uint32_t mxcr_mask;struct _libc_fpxreg _st[8];struct _libc_xmmreg _xmm[16];__uint32_t padding[24]; };---------------------- //裝載所有寄存器的容器 __extension__ typedef long long int greg_t;/* Number of general registers. */ #define NGREG 23/* Container for all general registers. */ typedef greg_t gregset_t[NGREG];getcontext
int getcontext(ucontext_t *ucp);將當前的寄存器信息保存到變量ucp中。
下面看看匯編代碼
/* int __getcontext (ucontext_t *ucp)Saves the machine context in UCP such that when it is activated,it appears as if __getcontext() returned again.This implementation is intended to be used for *synchronous* contextswitches only. Therefore, it does not have to save anythingother than the PRESERVED state. */ENTRY(__getcontext)/* Save the preserved registers, the registers used for passingargs, and the return address. */movq %rbx, oRBX(%rdi)movq %rbp, oRBP(%rdi)movq %r12, oR12(%rdi)movq %r13, oR13(%rdi)movq %r14, oR14(%rdi)movq %r15, oR15(%rdi)movq %rdi, oRDI(%rdi)movq %rsi, oRSI(%rdi)movq %rdx, oRDX(%rdi)movq %rcx, oRCX(%rdi)movq %r8, oR8(%rdi)movq %r9, oR9(%rdi)movq (%rsp), %rcxmovq %rcx, oRIP(%rdi)leaq 8(%rsp), %rcx /* Exclude the return address. */movq %rcx, oRSP(%rdi)/* We have separate floating-point register content memory on thestack. We use the __fpregs_mem block in the context. Set thelinks up correctly. */leaq oFPREGSMEM(%rdi), %rcxmovq %rcx, oFPREGS(%rdi)/* Save the floating-point environment. */fnstenv (%rcx)fldenv (%rcx)stmxcsr oMXCSR(%rdi)/* Save the current signal mask withrt_sigprocmask (SIG_BLOCK, NULL, set,_NSIG/8). */leaq oSIGMASK(%rdi), %rdxxorl %esi,%esi #if SIG_BLOCK == 0xorl %edi, %edi #elsemovl $SIG_BLOCK, %edi #endifmovl $_NSIG8,%r10dmovl $__NR_rt_sigprocmask, %eaxsyscallcmpq $-4095, %rax /* Check %rax for error. */jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. *//* All done, return 0 for success. */xorl %eax, %eaxret PSEUDO_END(__getcontext)weak_alias (__getcontext, getcontext)這段代碼主要執行了幾個工作,將當前的寄存器數據存入到%rdi也就是第一個參數ucp中,緊接著調整棧頂指針%rsp。然后設置浮點計算器,保存當前線程的信號屏蔽掩碼。
setcontext
int setcontext(const ucontext_t *ucp);將變量ucp中保存的寄存器信息恢復到CPU中。
匯編代碼
/* int __setcontext (const ucontext_t *ucp)Restores the machine context in UCP and thereby resumes executionin that context.This implementation is intended to be used for *synchronous* contextswitches only. Therefore, it does not have to restore anythingother than the PRESERVED state. */ENTRY(__setcontext)/* Save argument since syscall will destroy it. */pushq %rdicfi_adjust_cfa_offset(8)/* Set the signal mask withrt_sigprocmask (SIG_SETMASK, mask, NULL, _NSIG/8). */leaq oSIGMASK(%rdi), %rsixorl %edx, %edxmovl $SIG_SETMASK, %edimovl $_NSIG8,%r10dmovl $__NR_rt_sigprocmask, %eaxsyscallpopq %rdi /* Reload %rdi, adjust stack. */cfi_adjust_cfa_offset(-8)cmpq $-4095, %rax /* Check %rax for error. */jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. *//* Restore the floating-point context. Not the registers, only therest. */movq oFPREGS(%rdi), %rcxfldenv (%rcx)ldmxcsr oMXCSR(%rdi)/* Load the new stack pointer, the preserved registers andregisters used for passing args. */cfi_def_cfa(%rdi, 0)cfi_offset(%rbx,oRBX)cfi_offset(%rbp,oRBP)cfi_offset(%r12,oR12)cfi_offset(%r13,oR13)cfi_offset(%r14,oR14)cfi_offset(%r15,oR15)cfi_offset(%rsp,oRSP)cfi_offset(%rip,oRIP)movq oRSP(%rdi), %rspmovq oRBX(%rdi), %rbxmovq oRBP(%rdi), %rbpmovq oR12(%rdi), %r12movq oR13(%rdi), %r13movq oR14(%rdi), %r14movq oR15(%rdi), %r15/* The following ret should return to the address set withgetcontext. Therefore push the address on the stack. */movq oRIP(%rdi), %rcxpushq %rcxmovq oRSI(%rdi), %rsimovq oRDX(%rdi), %rdxmovq oRCX(%rdi), %rcxmovq oR8(%rdi), %r8movq oR9(%rdi), %r9/* Setup finally %rdi. */movq oRDI(%rdi), %rdi/* End FDE here, we fall into another context. */cfi_endproccfi_startproc/* Clear rax to indicate success. */xorl %eax, %eaxret PSEUDO_END(__setcontext)setcontext的操作與getcontext類似,他將ucp中所保存的上下文信息給取出來,放入當前的寄存器中,使得當前的上下文環境恢復的與ucp一致
makecontext
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);修改上下文信息,設置上下文入口函數func,agrc為參數個數,后面跟著的函數參數必須要是整型值。并且在makecontext之前,需要為上下文設置棧空間ucp->stack以及設置后繼上下文ucp->uc_link。
匯編代碼
void__makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...){extern void __start_context (void);greg_t *sp;unsigned int idx_uc_link;va_list ap; int i;/* Generate room on stack for parameter if needed and uc_link. */sp = (greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp+ ucp->uc_stack.ss_size);sp -= (argc > 6 ? argc - 6 : 0) + 1;/* Align stack and make space for trampoline address. */sp = (greg_t *) ((((uintptr_t) sp) & -16L) - 8); idx_uc_link = (argc > 6 ? argc - 6 : 0) + 1;/* Setup context ucp. *//* Address to jump to. */ucp->uc_mcontext.gregs[REG_RIP] = (uintptr_t) func;/* Setup rbx.*/ucp->uc_mcontext.gregs[REG_RBX] = (uintptr_t) &sp[idx_uc_link];ucp->uc_mcontext.gregs[REG_RSP] = (uintptr_t) sp; /* Setup stack. */sp[0] = (uintptr_t) &__start_context;sp[idx_uc_link] = (uintptr_t) ucp->uc_link;va_start (ap, argc);/* Handle arguments.The standard says the parameters must all be int values. This isan historic accident and would be done differently today. Forx86-64 all integer values are passed as 64-bit values andtherefore extending the API to copy 64-bit values instead of32-bit ints makes sense. It does not break existingfunctionality and it does not violate the standard which saysthat passing non-int values means undefined behavior. */for (i = 0; i < argc; ++i)switch (i){case 0:ucp->uc_mcontext.gregs[REG_RDI] = va_arg (ap, greg_t);break;case 1:ucp->uc_mcontext.gregs[REG_RSI] = va_arg (ap, greg_t);break;case 2:ucp->uc_mcontext.gregs[REG_RDX] = va_arg (ap, greg_t);break;case 3:ucp->uc_mcontext.gregs[REG_RCX] = va_arg (ap, greg_t);break;case 4:ucp->uc_mcontext.gregs[REG_R8] = va_arg (ap, greg_t);break;case 5:ucp->uc_mcontext.gregs[REG_R9] = va_arg (ap, greg_t);break;default:/* Put value on stack. */sp[i - 5] = va_arg (ap, greg_t);break;}va_end (ap);}這段代碼的主要內容其實就是為用戶的自定義棧進行處理,將當前運行棧切換為用戶的自定義棧,并且將用戶傳入的入口函數放入rip中,rbx指向后繼上下文,rsp指向棧頂。
ENTRY(__start_context)/* This removes the parameters passed to the function given to'makecontext' from the stack. RBX contains the addresson the stack pointer for the next context. */movq %rbx, %rsp/* Don't use pop here so that stack is aligned to 16 bytes. */movq (%rsp), %rdi /* This is the next context. */testq %rdi, %rdije 2f /* If it is zero exit. */call __setcontext/* If this returns (which can happen if the syscall fails) we'llexit the program with the return error value (-1). */movq %rax,%rdi2:call HIDDEN_JUMPTARGET(exit)/* The 'exit' call should never return. In case it does causethe process to terminate. */hlt END(__start_context)makecontext通過調用__start_context()來實現后繼上下文的功能,其實就是將后繼上下文作為setcontext的參數,調用setcontext將當前上下文設置到后繼上下文的狀態
swapcontext
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);切換上下文,保存當前上下文到oucp中,然后激活ucp中的上下文
匯編代碼
/* int __swapcontext (ucontext_t *oucp, const ucontext_t *ucp);Saves the machine context in oucp such that when it is activated,it appears as if __swapcontextt() returned again, restores themachine context in ucp and thereby resumes execution in thatcontext.This implementation is intended to be used for *synchronous* contextswitches only. Therefore, it does not have to save anythingother than the PRESERVED state. */ENTRY(__swapcontext)/* Save the preserved registers, the registers used for passing args,and the return address. */movq %rbx, oRBX(%rdi)movq %rbp, oRBP(%rdi)movq %r12, oR12(%rdi)movq %r13, oR13(%rdi)movq %r14, oR14(%rdi)movq %r15, oR15(%rdi)movq %rdi, oRDI(%rdi)movq %rsi, oRSI(%rdi)movq %rdx, oRDX(%rdi)movq %rcx, oRCX(%rdi)movq %r8, oR8(%rdi)movq %r9, oR9(%rdi)movq (%rsp), %rcxmovq %rcx, oRIP(%rdi)leaq 8(%rsp), %rcx /* Exclude the return address. */movq %rcx, oRSP(%rdi)/* We have separate floating-point register content memory on thestack. We use the __fpregs_mem block in the context. Set thelinks up correctly. */leaq oFPREGSMEM(%rdi), %rcxmovq %rcx, oFPREGS(%rdi)/* Save the floating-point environment. */fnstenv (%rcx)stmxcsr oMXCSR(%rdi)/* The syscall destroys some registers, save them. */movq %rsi, %r12/* Save the current signal mask and install the new one withrt_sigprocmask (SIG_BLOCK, newset, oldset,_NSIG/8). */leaq oSIGMASK(%rdi), %rdxleaq oSIGMASK(%rsi), %rsimovl $SIG_SETMASK, %edimovl $_NSIG8,%r10dmovl $__NR_rt_sigprocmask, %eaxsyscallcmpq $-4095, %rax /* Check %rax for error. */jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. *//* Restore destroyed registers. */movq %r12, %rsi/* Restore the floating-point context. Not the registers, only therest. */movq oFPREGS(%rsi), %rcxfldenv (%rcx)ldmxcsr oMXCSR(%rsi)/* Load the new stack pointer and the preserved registers. */movq oRSP(%rsi), %rspmovq oRBX(%rsi), %rbxmovq oRBP(%rsi), %rbpmovq oR12(%rsi), %r12movq oR13(%rsi), %r13movq oR14(%rsi), %r14movq oR15(%rsi), %r15/* The following ret should return to the address set withgetcontext. Therefore push the address on the stack. */movq oRIP(%rsi), %rcxpushq %rcx/* Setup registers used for passing args. */movq oRDI(%rsi), %rdimovq oRDX(%rsi), %rdxmovq oRCX(%rsi), %rcxmovq oR8(%rsi), %r8movq oR9(%rsi), %r9/* Setup finally %rsi. */movq oRSI(%rsi), %rsi/* Clear rax to indicate success. */xorl %eax, %eaxret PSEUDO_END(__swapcontext)從匯編代碼可以看出來,swapcontext的主要操作其實就是整合了getcontext和setcontext。首先將當前的上下文環境保存到ousp中,緊接著將當前的上下文環境設置為usp中的上下文環境。
使用示例
示例一、上下文的保存與恢復(getcontext、setcontext)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <ucontext.h>int main() {int i = 0;ucontext_t ctx;getcontext(&ctx);//在該位置保存上下文printf("i = %d\n", i++);sleep(2);setcontext(&ctx);//將上下文恢復至設置時的狀態,完成死循環return 0; }通過在第三行的地方設置上下文,每次執行完計數后,將上下文環境恢復至getcontext的位置,實現循環計數。
示例二、上下文的修改(makecontext)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <ucontext.h>void fun( void ) {printf("fun()\n"); }int main( void ) {int i = 1;char *stack = (char*)malloc(sizeof(char)*8192);ucontext_t ctx_main, ctx_fun;getcontext(&ctx_main);//保存ctx_main上下文getcontext(&ctx_fun);//保存ctx_fun上下文printf("i=%d\n", i++);sleep(1);//設置上下文的棧信息ctx_fun.uc_stack.ss_sp = stack;ctx_fun.uc_stack.ss_size = 8192;ctx_fun.uc_stack.ss_flags = 0;ctx_fun.uc_link = &ctx_main;//設置ctx_main為ctx_fun的后繼上下文makecontext(&ctx_fun, fun, 0); // 修改上下文信息,設置入口函數與參數setcontext(&ctx_fun);//恢復ctx_fun上下文printf("main exit\n"); }
進入死循環,每次ctx_fun執行完fun函數后就會跳轉到后繼上下文ctx_main的保存位置的下一句,然后繼續開始計數,當走到setcontext的時候再次跳轉至fun函數,進入死循環,如下圖。
示例三、上下文的切換(swapcontext)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <ucontext.h>ucontext_t ctx_main, ctx_f1, ctx_f2;void fun1( void ) {printf("fun1() start\n");swapcontext(&ctx_f1, &ctx_f2);//切換至f2上下文printf("fun1() end\n"); }void fun2( void ) {printf("fun2() start\n");swapcontext(&ctx_f2, &ctx_f1);//切換回f1上下文printf("fun2() end\n"); }int main( void ) {char stack1[1024*8];char stack2[1024*8];getcontext(&ctx_f1);getcontext(&ctx_f2);ctx_f1.uc_stack.ss_sp = stack1;ctx_f1.uc_stack.ss_size = 1024*8;ctx_f1.uc_stack.ss_flags = 0;ctx_f1.uc_link = &ctx_f2;//f1設置后繼上下文為f2makecontext(&ctx_f1, fun1, 0);//設置入口函數ctx_f2.uc_stack.ss_sp = stack2;ctx_f2.uc_stack.ss_size = 1024*8;ctx_f2.uc_stack.ss_flags = 0;ctx_f2.uc_link = &ctx_main;//f2后繼上下文為主流程makecontext(&ctx_f2, fun2, 0);//設置入口函數swapcontext(&ctx_main, &ctx_f1);//保存ctx_main,從主流程上下文切換至ctx_f1上下文printf("main exit\n"); }
首先從主流程ctx_main切換至ctx_fun1的入口函數fun1,執行完fun1 start后切換至ctx_fun2的入口函數fun2。接著執行fun2 start,然后再次切換回ctx_fun1,執行fun1 end,此時fun1上下文執行結束,跳轉至后繼上下文ctx_fun2,執行fun2 end。接著fun2也執行結束,跳轉至后繼上下文主流程ctx_main,執行main exit退出。
總結
以上是生活随笔為你收集整理的ucontext族函数的使用及原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL(四)复合查询与联合查询
- 下一篇: 【项目介绍】协程——C语言实现的用户态非