文章目录

  • 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族函数的使用及原理分析相关推荐

  1. linux ucontext族函数的原理及使用

    ucontext函数族 这里的context族是偏向底层的,其实底层就是通过汇编来实现的,但是我们使用的时候就和平常使用变量和函数一样使用就行,因为大佬们已经将它们封装成C库里了的 我们先来看看寄存器 ...

  2. linux ucontext 类型,协程:posix::ucontext用户级线程实现原理分析 | WalkerTalking

    在听完leader的课程后,对其中协程的实现方式有了基本的了解,无论的POSIX的ucontex,boost::fcontext,还是libco,都是通过保存和恢复寄存器状态,来进行各个协程上下文的保 ...

  3. web压测工具http_load原理分析

    一.前言 http_load是一款测试web服务器性能的开源工具,从下面的网址可以下载到最新版本的http_load: http://www.acme.com/software/http_load/  ...

  4. 《PHP与MySQL程序设计(第四版)》中第73页4.2.6节分期还贷计算器数学原理分析

    -----------------------------<PHP与MySQL程序设计(第四版)>中第73页4.2.6节分期还贷计算器数学原理分析. <PHP与MySQL程序设计(第 ...

  5. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  6. 【Kotlin】Kotlin 中使用 Lambda 表达式替代对象表达式原理分析 ( 尾随 Lambda - Trailing Lambda 语法 | 接口对象表达式 = 接口#函数类型对象 )

    文章目录 一.尾随 Lambda - Trailing Lambda 语法 二.Kotlin 中使用 Lambda 表达式替代对象表达式原理 1.Lambda 替换对象表达式 2.原理分析 3.示例分 ...

  7. jieba分词_从语言模型原理分析如何jieba更细粒度的分词

    jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...

  8. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用 1...

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionPro ...

  9. Retrofit原理分析

    Retrofit原理分析 温故而知新 还记得retrofit的使用方法嘛,下面我们来回顾一下 接口定义 public interface GitHubService {@GET("users ...

最新文章

  1. [Share]10 Free EBooks for Web Designers
  2. spring-cloud-config安全问题
  3. caffe在ubuntu18.04下编译
  4. 辅助类BinaryTreeNodeIndex(用index索引代替指针)
  5. 最长递增子序列和网易去除最少使从左向右递增又递减问题
  6. ArcGIS AddIN之工具不可用
  7. 完美解决html中select的option不能隐藏的问题。
  8. python 暂停程序 等待用户输入_Python-基础02-程序与用户交互
  9. 重置PL/SQL Developer工具栏布局
  10. mysql oldaltertable_MySQL5.6 ALTER TABLE 分析和测试
  11. 102 二叉树层序遍历Binary Tree Level Order Traversal @ Python
  12. C语言学生管理系统(链表实现)
  13. Arcpy基础入门-1、如何使用arcpy
  14. 农村三资管理平台app_农村集体经济管理平台、“三资”信息公开手机APP招标公告...
  15. 数据库实验报告-实验一:SQL语言
  16. ong拼音汉字_拼音ong到底怎么读?
  17. 三维重建| iPad Pro2020 专业3D扫描应用程序 3D Scanner App(App Store可免费下载)
  18. 1.1.从命令行读入一个字符串,表示一个年份,输出该年的世界杯冠军是哪支球队。如果该 年没有举办世界杯,则输出:没有举办世界杯。
  19. Centos8.4服务器安全加固方案
  20. Ubuntu编辑文档和查看文档

热门文章

  1. Spring AOP源码解析(三)—— AOP引入(续)
  2. Java平台扩展机制#3:SLF4J怪招
  3. 利用python进行数据分析第二版pdf百度云_参考《利用Python进行数据分析(第二版)》高清中文PDF+高清英文PDF+源代码...
  4. 访问动态页面很慢 PHP,PHP动态网页程序优化及高效提速问题
  5. sqlconfigdatasource mysql_MFC odbc 连接MySQL 的 (SQLConfigDataSource动态DSN)
  6. 贴片电容耐压值一般都是多少?
  7. 六种排序算法的JavaScript实现以及总结
  8. SpringMVC @RequestBody ajax传递对象数组
  9. Java转iOS-第一个项目总结(2)
  10. Windows Phone 7项目实战之记事本(二)