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 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), %rcx
movq %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), %rsp
movq oRBX(%rdi), %rbx
movq oRBP(%rdi), %rbp
movq oR12(%rdi), %r12
movq oR13(%rdi), %r13
movq oR14(%rdi), %r14
movq oR15(%rdi), %r15
/* The following ret should return to the address set with
getcontext. Therefore push the address on the stack. */
movq oRIP(%rdi), %rcx
pushq %rcx
movq oRSI(%rdi), %rsi
movq oRDX(%rdi), %rdx
movq oRCX(%rdi), %rcx
movq oR8(%rdi), %r8
movq 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 is
an historic accident and would be done differently today. For
x86-64 all integer values are passed as 64-bit values and
therefore extending the API to copy 64-bit values instead of
32-bit ints makes sense. It does not break existing
functionality and it does not violate the standard which says
that 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;//信號屏蔽字掩碼,一般設為0
ctx_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), %rcx
movq %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), %rsp
movq oRBX(%rsi), %rbx
movq oRBP(%rsi), %rbp
movq oR12(%rsi), %r12
movq oR13(%rsi), %r13
movq oR14(%rsi), %r14
movq oR15(%rsi), %r15
/* The following ret should return to the address set with
getcontext. Therefore push the address on the stack. */
movq oRIP(%rsi), %rcx
pushq %rcx
/* Setup registers used for passing args. */
movq oRDI(%rsi), %rdi
movq oRDX(%rsi), %rdx
movq oRCX(%rsi), %rcx
movq oR8(%rsi), %r8
movq 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_f1
getcontext(&ctx_f2);//初始化ctx_f2
ctx_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


轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/286634.html
標籤:其他
