ucontext函数族

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

我们先来看看寄存器
寄存器:寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果
我们常用的寄存器是X86-64中的其中16个64位的寄存器,它们分别是
%rax, %rbx, %rcx, %rdx, %esi, %edi, %rbp, %rsp
%r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15
其中

  • %rax作为函数返回值使用
  • %rsp栈指针寄存器, 指向栈顶
  • %rdi, %rsi, %rdx, %rcx, %r8, %r9用作函数的参数,从前往后依次对应第1、第2、…第n参数
  • %rbx, %rbp, %r12, %r13, %r14, %r15用作数据存储,遵循被调用这使用规- 则,调用子函数之前需要先备份,防止被修改。
  • %r10, %r11用作数据存储,遵循调用者使用规则,使用前需要保存原值

ucontext_t

ucontext_t是一个结构体变量,其功能就是通过定义一个ucontext_t来保存当前上下文信息的。
ucontext_t结构体定义信息如下

typedef struct ucontext{unsigned long int uc_flags;struct ucontext *uc_link;//后序上下文__sigset_t uc_sigmask;// 信号屏蔽字掩码stack_t uc_stack;// 上下文所使用的栈mcontext_t uc_mcontext;// 保存的上下文的寄存器信息long int uc_filler[5];} ucontext_t;//其中mcontext_t 定义如下
typedef struct{gregset_t __ctx(gregs);//所装载寄存器fpregset_t __ctx(fpregs);//寄存器的类型
} mcontext_t;//其中gregset_t 定义如下
typedef greg_t gregset_t[NGREG];//包括了所有的寄存器的信息

getcontext()

函数:int getcontext(ucontext_t* ucp)
功能:将当前运行到的寄存器的信息保存在参数ucp中

函数底层汇编实现代码(部分):

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)

我们知道%rdi就是函数的第一个参数,这里指的就是ucp。我们取一段代码大概解释一下
下面代码就是将%rbx内存中的信息先备份然后再将值传递保存到%rdi中

movq    %rbx, oRBX(%rdi)

我们上面部分代码就是将上下文信息和栈顶指针都保存到我们ucontext_t结构体中的gregset_t[NGREG],而gregset_t也就是我们结构体中的uc_mcontext的成员,所有调用getcontext函数后,就能将当前的上下文信息都保存在ucp结构体变量中了

setcontext()

函数:int setcontext(const ucontext_t *ucp)
功能:将ucontext_t结构体变量ucp中的上下文信息重新恢复到cpu中并执行

函数底层汇编实现代码(部分):

ENTRY(__setcontext)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

我们可以看到和getcontext中汇编代码类似,但是setcontext是将参数变量中的上下文信息重新保存到cpu中

使用演示

setcontext一般都是要配合getcontext来使用的,我们来看一下代码

#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(1);setcontext(&ctx);//回复ucp上下文return 0;
}

执行结果:在getcontext(&ctx);中,我们会将下一条执行的指令环境保存到结构体ctx中,也就是printf(“i = %d\n”, i++)指令。然后运行到setcontext(&ctx)时就会将ctx中的指令回复到cpu中,所以该代码就是让cpu去运行ctx所保存的上下文环境,所以又回到了打印的那一行代码中,所以运行是一个死循环,而i值不变是因为i是存在内存栈中的,不是存在寄存器中的,所以切换并不影响i的值

makecontext()

函数:void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...)
功能:修改上下文信息,参数ucp就是我们要修改的上下文信息结构体;func是上下文的入口函数;argc是入口函数的参数个数,后面的…是具体的入口函数参数,该参数必须为整形值

函数底层汇编实现代码(部分):

 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);}

这里就是将func的地址保存到寄存器中,把ucp上下文结构体下一条要执行的指令rip改变为func函数的地址。并且将其所运行的栈改为用户自定义的栈

使用演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>void fun()
{printf("fun()\n");
}int main()
{int i = 0;//定义用户的栈char* stack = (char*)malloc(sizeof(char)*8192);//定义两个上下文//一个是主函数的上下文,一个是fun函数的上下文ucontext_t ctx_main, ctx_fun;getcontext(&ctx_main);getcontext(&ctx_fun);printf("i = %d\n", i++);sleep(1);//设置fun函数的上下文//使用getcontext是先将大部分信息初始化,我们到时候只需要修改我们所使用的部分信息即可ctx_fun.uc_stack.ss_sp = stack;//用户自定义的栈ctx_fun.uc_stack.ss_size = 8192;//栈的大小ctx_fun.uc_stack.ss_flags = 0;//信号屏蔽字掩码,一般设为0ctx_fun.uc_link = &ctx_main;//该上下文执行完后要执行的下一个上下文makecontext(&ctx_fun, fun, 0);//将fun函数作为ctx_fun上下文的下一条执行指令setcontext(&ctx_fun);printf("main exit\n");return 0;
}

运行结果:当执行到setcontext(&ctx_fun)代码时会去运行我们之前makecontext时设置的上下文入口函数所以在打印i完后会打印fun(),然后我们设置ctx_fun上下文执行完后要执行的下一个上下文是ctx_main,所以执行完后会执行到getcontext(&ctx_fun),所以最后也是一个死循环

swapcontext()

函数:int swapcontext(ucontext_t *oucp, ucontext_t *ucp)
功能:将当前cpu中的上下文信息保存带oucp结构体变量中,然后将ucp中的结构体的上下文信息恢复到cpu中
这里可以理解为调用了两个函数,第一次是调用了getcontext(oucp)然后再调用setcontext(ucp)

函数底层汇编实现代码(部分):

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)/* 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

我们一开始就知道%rdi就是我们函数中的第一参数,%rsi就是函数中的第二个参数。汇编代码中就是将当前cpu中的上下文信息保存到函数的第一个参数中,然后再将第二个参数的上下文信息恢复到cpu中

使用演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>ucontext_t ctx_main, ctx_f1, ctx_f2;void fun1()
{printf("fun1() start\n");swapcontext(&ctx_f1, &ctx_f2);printf("fun1() end\n");
}void fun2()
{printf("fun2() start\n");swapcontext(&ctx_f2, &ctx_f1);printf("fun2 end\n");
}int main()
{char stack1[8192];char stack2[8192];getcontext(&ctx_f1);//初始化ctx_f1getcontext(&ctx_f2);//初始化ctx_f2ctx_f1.uc_stack.ss_sp = stack1;ctx_f1.uc_stack.ss_size = 8192;ctx_f1.uc_stack.ss_flags = 0;ctx_f1.uc_link = &ctx_f2;makecontext(&ctx_f1, fun1, 0);//设置上下文变量ctx_f2.uc_stack.ss_sp = stack2;ctx_f2.uc_stack.ss_size = 8192;ctx_f2.uc_stack.ss_flags = 0;ctx_f2.uc_link = &ctx_main;makecontext(&ctx_f2, fun2, 0);//保存ctx_main的上下文信息,并执行ctx_f1所设置的上下文入口函数swapcontext(&ctx_main, &ctx_f1);printf("main exit\n");return 0;
}

运行结果:定义三个上下文变量,ctx_main、ctx_f1、ctx_f2。当执行到swapcontext(&ctx_main, &ctx_f1)时会执行fun1函数,然后打印fun1() start。再执行swapcontext(&ctx_f1, &ctx_f2),也就是保存ctx_f1的上下文,然后去执行ctx_f2的上下文信息,也就是fun2函数,所以会打印fun2() start。执行到swapcontext(&ctx_f2, &ctx_f1);是会切换到fun1当时切换时的上下文环境,此时会打印fun1() end,ctx_f1上下文执行完后会执行之前设置的后继上下文,也就是ctx_f2,所以会打印fun2 end。fun2函数执行完会执行ctx_f2的后继上下文,其后继上下文为ctx_main,而此时的ctx_main的下一条指令就是printf(“main exit\n”),所以会打印main exit

linux ucontext族函数的原理及使用相关推荐

  1. linux:exec族函数, exec族函数配合fork使用,system 函数,popen 函数

    1.exec族函数 精彩博文: https://blog.csdn.net/u014530704/article/details/73848573 ​ ​ ​ path:   比如说 ./a.out ...

  2. ucontext族函数的使用及原理分析

    文章目录 ucontext介绍 寄存器介绍 ucontext_t结构体 getcontext setcontext makecontext swapcontext 使用示例 示例一.上下文的保存与恢复 ...

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

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

  4. Linux进程编程(PS: exec族函数、system、popen函数)

    目录 1.进程相关概念 程序和进程 查看系统中的进程 ps指令 top指令 进程标识符 使用getpid()获取 父进程,子进程 2.创建进程fork 进程创建发生了什么--C程序的存储空间如何分配 ...

  5. linux进程---exec族函数(execl, execlp, execv, execvp, )解释和配合fork的使用

    exec族函数函数的作用:         exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件.这里的可执行文件既可以是二 ...

  6. Linux 进程11【exec族函数(execl, execlp, execle, execv, execvp, execvpe)】

    linux进程-exec族函数(execl, execlp, execle, execv, execvp, execvpe) 原文链接:https://blog.csdn.net/u014530704 ...

  7. Linux进程5:exec族函数(execl, execlp, execle, execv, execvp, execvpe)总结及exec配合fork使用

    exec族函数(execl, execlp, execle, execv, execvp, execvpe)及exec配合fork使用 exec族函数函数的作用: 我们用fork函数创建新进程后,经常 ...

  8. Linux进程—exec族函数 execl, execlp, execle, execv, execvp

    exec族函数的作用: 我们用fork函数创建新进程后,经常会在新进程中调用exec族函数去执行新的程序:当该进程调用exec族函数时,该进程被替代为新程序,因为exec族函数并不创建新进程,所以前后 ...

  9. Linux进程 exec族函数(execl,execlp,execle,execv,execvp,execcvpe)

    exec族函数的作用 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序.当进程调用exec函数时,该进程被完全替换为新程序.因为调用exec函数并不创建新进程,所以前 ...

最新文章

  1. poj1182 and 携程预赛2第一题 带权并查集
  2. AngularJS 服务(Service)
  3. 基于 Laravel 5 构建的、支持模块化和多语言的 CMS —— AsgardCMS
  4. 二十、预处理CSS的LESS
  5. 直播 | ICLR 2021论文解读:兼听则明,信而有征:可信多模态分类
  6. 根据省市区查询对应权限下的数据
  7. C# 小闹钟 v3.0
  8. 记一次面试过程中的Python编程题
  9. 华为云OCR文字识别 免费在线体验!
  10. java多图片上传插件,Bootstrap中的fileinput 多图片上传及编辑功能
  11. android获取wifi mac地址吗,Android获取当前WiFi的MAC地址-适配所有版本
  12. python定义复数矩阵_python矩阵运算,转置,逆运算,共轭矩阵实例
  13. 软件开发人员应该了解测试和QA
  14. 网站备案后服务器到期,域名备案后服务器到期
  15. oracle根据身份证号码计算年龄
  16. 出"程序员"身上的"六宗罪"
  17. 教麦叔了解EMC、EMI、EMS的区别
  18. RT-Thread 学习文档
  19. 金蝶引出文件到服务器,金蝶迷你版如何引出帐套文件,具体操作流程
  20. chm sharp安卓版_USB Sharp下载

热门文章

  1. android 创建图片,[Android]创建图片
  2. js递归遍历json树_2020-08-26:裸写算法:树的非递归先序遍历
  3. js创建file对象 字符串 txt_js-创建对象的多种方式
  4. mysql 1261 load data_mysql使用load data导入数据文件
  5. php ci sql性能时间,Codeigniter操作数据库表的优化写法总结
  6. Mac 系统安装 ElasticSearch
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的病例管理系统
  8. 基于JAVA+SpringBoot+Mybatis+MYSQL的校园招聘管理系统
  9. 基于JAVA+SpringBoot+Mybatis+MYSQL的课程在线学习系统
  10. mac 连接hbase的图形化界面_Mac 视觉史(二):90 年代失败 Mac 操作系统大赏