国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

make_fcountext、jump_fcontext

silencezwm / 2587人閱讀

摘要:所以子程序調用是通過棧實現的,子程序調用總是一個入口,一次返回,調用順序是明確的。棧保存的數據,指令通過寄存器控制。所以需要多個棧來維護。由于棧是從高到底,此處的意思表示預留字節的棧空間。

我們先弄清如何進行協程的切換,程序可以在某個地方掛起,跳轉到另外的流程中執行,并且可以重新在掛起處繼續運行。那如何實現呢?

我們先來看一個例子,有下面2個函數,如果在一個單線程中讓輸出結果依次是 funcA1 funcB1 funcA2 funcB2 ... ,你會怎么做呢?

void funcA(){
    int i = 0;
    while(true){
        //to do something
        
        printf("funcA%d ",i);
        i++;
    }
}

void funcB(){
    int i = 0;
    while(true){
        //to do something
        
        printf("funcB%d ",i);
        i++;
    }
}

如果從c代碼的角度來看,如果單線程運行到func1 的 while循環中,如何能調用到func2的while循環呢?必須使用跳轉。

首先想到是goto。goto是可以實現跳轉,但是goto不能實現函數間的跳轉。無法滿足這個要求。即使可以實現函數間跳轉,難道就可行嗎?

那這里不得不說下C函數調用過程

具體相見這篇文章https://blog.csdn.net/jelly_9/article/details/53239718
子程序或者稱為函數,在所有語言中都是層級調用,比如A調用B,B在執行過程中又調用了C,
C執行完畢返回,B執行完畢返回,最后是A執行完畢。
所以子程序調用是通過棧實現的,子程序調用總是一個入口,一次返回,調用順序是明確的。

程序運行有2個部分,指令,數據。棧保存的數據,指令通過寄存器(rip)控制。

2個函數內部的跳轉必須保證棧是正確,所以跳轉之前需要保存好當前的棧信息,然后跳轉。
另外我們可以得到另一個信息,在一個棧上實現多個流程直接的跳轉是不能實現的。
所以需要多個棧來維護。

那我們來看jump_fcontext是怎么實現跳轉

c語言函數聲明
int jump_fcontext(fcontext_t *ofc, fcontext_t nfc, void* vp, bool preserve_fpu);


匯編代碼如下
.text
.globl jump_fcontext
.type jump_fcontext,@function
.align 16
jump_fcontext:
    pushq  %rbp  /* save RBP */
    pushq  %rbx  /* save RBX */
    pushq  %r15  /* save R15 */
    pushq  %r14  /* save R14 */
    pushq  %r13  /* save R13 */
    pushq  %r12  /* save R12 */

    /* prepare stack for FPU */
    leaq  -0x8(%rsp), %rsp

    /* test for flag preserve_fpu */
    cmp  $0, %rcx
    je  1f

    /* save MMX control- and status-word */
    stmxcsr  (%rsp)
    /* save x87 control-word */
    fnstcw   0x4(%rsp)

1:
    /* store RSP (pointing to context-data) in RDI */
    movq  %rsp, (%rdi)

    /* 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:
    /* prepare stack for FPU */
    leaq  0x8(%rsp), %rsp

    popq  %r12  /* restrore R12 */
    popq  %r13  /* restrore R13 */
    popq  %r14  /* restrore R14 */
    popq  %r15  /* restrore R15 */
    popq  %rbx  /* restrore RBX */
    popq  %rbp  /* restrore RBP */

    /* restore return-address */
    popq  %r8

    /* use third arg as return-value after jump */
    movq  %rdx, %rax
    /* use third arg as first arg in context function */
    movq  %rdx, %rdi

    /* indirect jump to context */
    jmp  *%r8
.size jump_fcontext,.-jump_fcontext

/* Mark that we don"t need executable stack.  */
.section .note.GNU-stack,"",%progbits

寄存器的用途可以先了解下https://www.jianshu.com/p/571...

1、保存寄存器

pushq  %rbp  /* save RBP */
pushq  %rbx  /* save RBX */
pushq  %r15  /* save R15 */
pushq  %r14  /* save R14 */
pushq  %r13  /* save R13 */
pushq  %r12  /* save R12 */

“被調函數有義務保證 rbp rbx r12~r15 這幾個寄存器的值在進出函數前后一致”
rbx 是基址寄存器 作用存放存儲區的起始地址 被調用者保存
rbp (base pointer)基址指針寄存器,用于提供堆棧內某個單元的偏移地址,與rss段寄存器聯用, 
可以訪問堆棧中的任一個存儲單元,被調用者保存

2、預留fpu 8個字節空間

/* prepare stack for FPU */
leaq  -0x8(%rsp), %rsp
表示 %rsp中的內容減8。由于棧是從高到底,此處的意思表示預留8字節的??臻g。
FPU:(Float Point Unit,浮點運算單元)

3、判斷是否保存fpu

cmp  $0, %rcx
je  1f

rcx是第四個參數,判斷是否等于0。如果為0,跳轉到1標示的位置。也就是preserve_fpu 。

當preserve_fpu = true 的時候,需要執行2個指令是將浮點型運算的2個32位寄存器數據保存到第2步中預留的8字節空間。
/* save MMX control- and status-word */
stmxcsr  (%rsp)
/* save x87 control-word */
fnstcw   0x4(%rsp)

4、修改rsp 此時已經改變到其他棧
將rsp 保存到第一參數(第一個參數保存在rdi)指向的內存。fcontext_t *ofc 第一參數ofc指向的內存中保存是 rsp 的指針。第二條指令,實現了將第二個參數復制到 rsp.

1:
/* store RSP (pointing to context-data) in RDI */
movq  %rsp, (%rdi)

/* restore RSP (pointing to context-data) from RSI */
movq  %rsi, %rsp

5、判斷是否保存了fpu,如果保存了就恢復保存在nfx 棧上的 fpu相關數據到響應的寄存器。

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

6、將rsp 存儲的地址+8(8字節fpu),按順序將棧中數據恢復到寄存器中。

2:
/* prepare stack for FPU */
leaq  0x8(%rsp), %rsp

popq  %r12  /* restrore R12 */
popq  %r13  /* restrore R13 */
popq  %r14  /* restrore R14 */
popq  %r15  /* restrore R15 */
popq  %rbx  /* restrore RBX */
popq  %rbp  /* restrore RBP */

7、設置返回值,實現指令跳轉。
接下來繼續pop 數據,那棧上存的是什么呢,在c函數調用文章中可以知道,call的時候會保存rip(指令寄存器)到棧。所以此時POP的數據是rip 也就是下一條指令。這是下一條指令是nfx 棧保存的,所以這是另一個協程的下一條指令。保存到 r8。最后跳轉下一條指令就恢復到另一個協程運行 jmp *%r8。

movq %rdx, %rax 是將上一個協程A jump_fcontext第三個參數作為當前協程B jump_fcontext 的返回值,可以實現2個協程直接的數據傳遞。

movq %rdx, %rdi 如果跳轉過去的新的協程,將第三個參數作為協程B 啟動入口void func(int param)的第一參數。

/* restore return-address */
popq  %r8

/* use third arg as return-value after jump */
movq  %rdx, %rax
/* use third arg as first arg in context function */
movq  %rdx, %rdi

/* indirect jump to context */
jmp  *%r8


了解了程序是如何跳轉后,我門在看下如何創建一個協程棧呢。make_fcontext
c語言函數聲明 fcontext_t make_fcontext(void sp, size_t size, void (fn)(int));

.text
.globl make_fcontext
.type make_fcontext,@function
.align 16
make_fcontext:
    /* first arg of make_fcontext() == top of context-stack */
    movq  %rdi, %rax

    /* shift address in RAX to lower 16 byte boundary */
    andq  $-16, %rax

    /* reserve space for context-data on context-stack */
    /* size for fc_mxcsr .. RIP + return-address for context-function */
    /* on context-function entry: (RSP -0x8) % 16 == 0 */
    leaq  -0x48(%rax), %rax

    /* third arg of make_fcontext() == address of context-function */
    movq  %rdx, 0x38(%rax)

    /* save MMX control- and status-word */
    stmxcsr  (%rax)
    /* save x87 control-word */
    fnstcw   0x4(%rax)

    /* compute abs address of label finish */
    leaq  finish(%rip), %rcx
    /* save address of finish as return-address for context-function */
    /* will be entered after context-function returns */
    movq  %rcx, 0x40(%rax)

    ret /* return pointer to context-data */

finish:
    /* exit code is zero */
    xorq  %rdi, %rdi
    /* exit application */
    call  _exit@PLT
    hlt
.size make_fcontext,.-make_fcontext

/* Mark that we don"t need executable stack. */
.section .note.GNU-stack,"",%progbits

1、第一個參數是程序申請的內存地址高位(棧是從高到低),將第一個參數放到rax,將地址取16的整數倍。
andq $-16, %rax 表示低4位取0。 -16 的補碼表示為0xfffffffff0.

/* first arg of make_fcontext() == top of context-stack */
movq  %rdi, %rax

/* shift address in RAX to lower 16 byte boundary */
andq  $-16, %rax

2、預留72字節??臻g,將第3個參數(void (*fn)(int)函數指針)保存在當前偏移0x38位置(大小8字節)。

/* reserve space for context-data on context-stack */
/* size for fc_mxcsr .. RIP + return-address for context-function */
/* on context-function entry: (RSP -0x8) % 16 == 0 */
leaq  -0x48(%rax), %rax

/* third arg of make_fcontext() == address of context-function */
movq  %rdx, 0x38(%rax)

3、保存fpu 和jump_fcontext 類似總大小8字節。

/* save MMX control- and status-word */
stmxcsr  (%rax)
/* save x87 control-word */
fnstcw   0x4(%rax)

4、計算finish的絕對地址,保存到棧的0x40位置。
leaq finish(%rip), %rcx 表示finish是相對位置+rip 就是finish的函數的地址。

/* compute abs address of label finish */
leaq  finish(%rip), %rcx
/* save address of finish as return-address for context-function */
/* will be entered after context-function returns */
movq  %rcx, 0x40(%rax)

5、返回,rax 作為返回值,目前的指向可以當做新棧的棧頂,相當于rsp

ret /* return pointer to context-data */

我們回頭在看看為什么會預留72字節大小。首先知道jump_fcontext 在新棧需要 pop 的大小為,fpu(8字節)+ rbp rbx r12 ~ r15 (8*6 = 48 字節) = 56 字節。還會繼續POP rip 8 字節,所以可以看到第二步中 movq %rdx, 0x38(%rax),就是將rip 保存到這個位置。
目前已經64字節了,棧還有存儲什么呢,協程(fn 函數)運行完成后會退出調用ret,其實就是POP到 rip.所以保存是finish 函數指針 大小8字節??偣?72 字節。

make_fcontext 創建協程的棧。jump_fcontext實現跳轉。

網校內部培訓視頻by李樂 https://biglive.xueersi.com/L...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31450.html

相關文章

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<