日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

      
          
      歡迎訪問 生活随笔!

      生活随笔

      當前位置: 首頁 > 运维知识 > linux >内容正文

      linux

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

      發布時間:2023/12/19 linux 18 豆豆
      生活随笔 收集整理的這篇文章主要介紹了 linux ucontext 类型,协程:posix::ucontext用户级线程实现原理分析 | WalkerTalking 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

      在聽完leader的課程后,對其中協程的實現方式有了基本的了解,無論的POSIX的ucontex,boost::fcontext,還是libco,都是通過保存和恢復寄存器狀態,來進行各個協程上下文的保存和切換。所以有了這篇對ucontext實現原理的分析。

      文章首先初略的溫習了一下匯編的一些基礎知識,其次就是ucontext源碼分析,最后是一個ucontext示例極其調試過程。

      歡迎批評指正

      1. 匯編基礎

      8086/8088的cpu的寄存器都是16位的,我相信這是我們最熟悉的CPU,因為大學的時候大家的匯編語言課程千篇一律都是講這一代cpu的,到了80x86的時候,cpu都是32位的,再后來都是64位的時代了。

      1.1 寄存器分類

      下面是一些對開發者來說,相對重要的寄存器,也是分析ucontext源碼的全部寄存器:

      register.png

      這些寄存器是最基本也是匯編中直接使用的寄存器最多的。

      其中AX,BX,CX,DX這些寄存器用來保存操作數和運算結果等信息,從而節省讀取操作數所需占用總線和訪問存儲器的時間,可以認為隨便用。

      指針寄存器:SP,BP,用于維護和訪問堆棧存儲單元。

      BP為基指針(Base Pointer)寄存器,用它可直接存取堆棧中的數據;

      SP為堆棧指針(Stack Pointer)寄存器,用它只可訪問棧頂。

      變址寄存器:寄存器SI,DI稱為變址寄存器(Index Register),它們主要用于存放存儲單元在段內的偏移量,

      指令指針寄存器

      指令指針IP(Instruction Pointer)是存放下次將要執行的指令在代碼段的偏移量。在具有預取指令功能的系統中,下次要執行的指令通常已被預取到指令隊列中,除非發生轉移情況。

      這里沒有列出所有的寄存器,比如說各個段寄存器CS,DS,ES,SS,GS,訪問進程各個段空間的基本地址。這些寄存器伴隨這每一條指令的執行,所以很重要,但這里不詳解,因為這些寄存器的使用,對匯編代碼的開發者說,很多時候是透明的。

      1.2.指令格式

      下面列出了簡單的匯編指令,知道這些指令的含義看匯編代碼就不會有什么問題了:

      指令類型

      名稱

      通用數據傳輸指令

      mov, push,pop, lea

      算術指令

      add,adc,inc,sub,sbb,dec,neg,cmp,mul,imul,div,idiv

      邏輯指令

      and,or,xor,not,shl,shr,test

      控制轉移指令

      jmp,call,ret,retf

      這里要說明的是:在like unix系統上的匯編語法格式采用AT&T 格式,和Wins下面的intel風格有很大差異,這里只列出三點:

      AT&T格式和Intel格式的指令的源操作數和目的操作數的順序是相反的

      AT&T格式

      Intel格式

      mov %rax %rdi

      mov rdi rax

      上面指令的含義是將rax寄存器的值存入rdi寄存器中

      AT&T格式寄存器操作數前面要加上‘%’,Intel格式不需要,從上面一點可以看出;

      AT&T格式指令如果要控制操作數長度是通過指令來進行的,Intel格式是通過在操作數前加限定來進行:

      AT&T格式

      Intel格式

      movb val, %al

      mov al byte ptr val

      所以你會在AT&T格式的指令中,看到各種mov:movb,movw,movl,movq,分別代表8bits, 16bits, 32bits, 64bits。同樣其他指令也是這樣。

      為了能夠清晰的看懂ucontext族的glibc的匯編實現,這里還要說明幾點:

      匯編中的尋址方式:立即數尋址, 直接尋址,間接尋址,變址尋址;下面是AT&T指令格式示例:

      尋址方式

      指令

      AT&T格式

      立即數尋址

      movl $0x123, %edx

      數字->寄存器

      直接尋址

      movl 0x123, %edx

      0x123指向內存數據->寄存器

      間接尋址

      movl (%ebx), %edx

      ebx寄存器指向內存數據-> edx寄存器

      變址尋址

      movl 4(%ebx), %edx

      ebx+4指向內存數據-> edx寄存器

      lea指令,裝入有效地址到寄存器;

      跳轉指令:call,ret

      cpu執行call跳轉指令時,cpu做了如下操作:1

      2

      3

      4

      5

      6rsp = rsp – 8

      rsp = rip

      //即跳轉之前會將下一條要執行語句指令地址壓入棧頂

      //call等同于以下兩條語句,但call本身就一條指令

      push %rip

      jmp 標號

      類似ret指令會將棧頂的內容彈出到rip寄存器中,繼續執行:1

      2

      3

      4rip = rsp

      rsp = rsp – 8

      //等同于

      pop %rip

      1.3. gcc關于寄存器的使用

      GCC中對這些寄存器的調用規則如下:

      rax 作為函數返回值使用。

      rsp 棧指針寄存器,指向棧頂;

      rdi,rsi,rdx,rcx,r8,r9 用作函數參數,依次對應第1參數,第2參數。。。當參數超過6個,才會通過壓棧的方式傳參數。

      rbx,rbp,r12,r13,r14,r15 用作數據存儲,遵循被調用者使用規則,簡單說就是隨便用,調用子函數之前要備份它,以防他被修改;

      r10,r11 用作數據存儲,遵循調用者使用規則,簡單說就是使用之前要先保存原值;

      要想看懂linux下的匯編源碼,這些規則是很很很重要的,否則下面ucontext族的匯編實現會看的一臉蒙逼.

      2. ucontext分析

      協程切換的時候,保存當前協程的上下文,主要是各個寄存器和信號狀態。我們先看看POSIX標準提供的用于用戶級線程切換的接口ucontext族函數:1

      2

      3

      4

      5

      6

      7

      8

      9/* Userlevel context. */

      typedef struct ucontext

      {

      unsigned long int uc_flags;

      struct ucontext *uc_link;//后繼上下文

      stack_t uc_stack;//用戶自定義棧

      mcontext_t uc_mcontext;//保存當前上下文,即各個寄存器的狀態

      __sigset_t uc_sigmask;//保存當前線程的信號屏蔽掩碼

      } ucontext_t;

      ucontext提供的一套api接口,有以下四個個:1

      2

      3

      4int getcontext(ucontext_t *ucp);

      int setcontext(const ucontext_t *ucp);

      void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

      int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

      具體功能:getcontext獲取線程的當前上下文;setcontext相反是從ucp中恢復出上下文;makecontext是修改ucp指向的上下文環境,swapcontext是保存當前上下文,并切換到新的上下文。下面看他們的具體實現:

      2.1. getcontext實現

      我們看看getcontext的glibc實現:1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52ENTRY(__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)

      leaq8(%rsp), %rcx/* Exclude the return address. */

      movq%rcx, oRSP(%rdi)

      /* We have separate floating-point register content memory on the

      stack. We use the __fpregs_mem block in the context. Set the

      links up correctly. */

      leaqoFPREGSMEM(%rdi), %rcx

      movq%rcx, oFPREGS(%rdi)

      /* Save the floating-point environment. */

      fnstenv(%rcx)

      fldenv(%rcx)

      stmxcsr oMXCSR(%rdi)

      /* Save the current signal mask with

      rt_sigprocmask (SIG_BLOCK, NULL, set,_NSIG/8). */

      leaqoSIGMASK(%rdi), %rdx

      xorl%esi,%esi

      #if SIG_BLOCK == 0

      xorl%edi, %edi

      #else

      movl$SIG_BLOCK, %edi

      #endif

      movl$_NSIG8,%r10d

      movl$__NR_rt_sigprocmask, %eax

      syscall

      cmpq$-4095, %rax/* Check %rax for error. */

      jaeSYSCALL_ERROR_LABEL/* Jump to error handler if error. */

      /* All done, return 0 for success. */

      xorl%eax, %eax

      ret

      PSEUDO_END(__getcontext)

      getcontext的匯編代碼中,第一部分就是保存當前上下文中的各個寄存器到第一個參數rdi中,即ucontext_t中,其中目標操作數(%rdi)前面的oRBX,oRBP…的含義如下,

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16sysdeps/unix/sysv/linux/x86_64/ucontext_i.sym

      #define ucontext(member)offsetof (ucontext_t, member)

      #define mcontext(member)ucontext (uc_mcontext.member)

      #define mreg(reg)mcontext (gregs[REG_##reg])

      oRBPmreg (RBP)

      oRSPmreg (RSP)

      oRBXmreg (RBX)

      oR8mreg (R8)

      oR9mreg (R9)

      oR10mreg (R10)

      oR11mreg (R11)

      oR12mreg (R12)

      oR13mreg (R13)

      oR14mreg (R14)

      所以oRBP = offsetof(ucontext_t, un_mcontext.greps[REG_RBP]),即ucontext_t結構中用于保存各個寄存器的相對位移。

      getcontext中,第一部分保存各個寄存器狀態值如下:

      getcontext.png

      進入getcontext之后

      首先保存rbx,rbp,r12,r13,r14,r15,這6個數據寄存器,因為他們遵循被調用者使用,所以需要保存,

      然后是保存rdi,rsi,rdx,rcx,r8,r9這6個寄存器,因為它用于保存函數參數,也是遵循被調用者使用。但大家發現沒有,getcontext只有一個ucontext參數,所以保存后面5個寄存器是多余的。

      其次,讀取rsp寄存器指向的進程stack棧頂中的RIP值,該棧頂的值,是在調用getcontext時,即執行call指令時,默認會做的事情:將下一條指令地址push進棧頂空間。讀取后會將該值保存到ucontext中,當恢復時,恢復到RIP寄存器中。

      再次,將棧頂指針加8,即獲得調用getcontext()之前的棧頂指定,并保存到ucontext中,當恢復時,恢復到RSP寄存器中。

      getcontext的第二部分設置浮點計數器, 第三部分就是保存當前線程的信號屏蔽掩碼;

      2.2. makecontext實現1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67void

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

      }

      makecontext用于修改已經獲取的上下文信息,其支持將運行stack切換為用戶自定義棧,并可以修改ucontext上下文中保存的RIP指針,這樣當恢復此ucontext的上下文時,就會將RIP寄存器的恢復為ucontext中的RIP字段值,跳到指定的代碼處進行執行,這也是協程運行的基本要求。

      makecontex的glibc實現中,

      首先是對用戶自定義棧進行處理,將sp移動到棧底(棧空間是遞減的),然后進行對齊,并預留出8字節的trampoline空間(防止相互遞歸的發生)。

      然后,將傳入的上下文ucontext中的rip字段設置為fun函數的地址,rbx字段指向繼承上下文,rsp字段指向自定義棧的棧頂

      其次就是將start_context和uc_link,存入棧中

      最后,是將makecontext的參數存入ucontext的上下文中,對于多余的參數,進行壓棧操作。

      修改后的ucontext上下文如下:makecontext.png

      makecontext支持后繼上下文的功能,即當前ucontext執行完畢后,會執行ucontext中設置的uc_link所指向的另一個ucontext,這個功能就是通過__start_context()來實現的,上面的圖中可知,makecontext()中將用戶自定義棧的棧頂push進了start_context,當makecontext()修改的上下文執行結束后,會將棧頂的start_context指針 pop到當RIP寄存器中,然后執行,下面是__start_context()的glibc匯編源碼:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22ENTRY(__start_context)

      /* This removes the parameters passed to the function given to

      'makecontext' from the stack. RBX contains the address

      on 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, %rdi

      je2f/* If it is zero exit. */

      call__setcontext

      /* If this returns (which can happen if the syscall fails) we'll

      exit the program with the return error value (-1). */

      movq%rax,%rdi

      2:

      callHIDDEN_JUMPTARGET(exit)

      /* The 'exit' call should never return. In case it does cause

      the process to terminate. */

      hlt

      END(__start_context)

      該代碼首先就是將當前寄存器中的rbx值賦給rsp寄存器,我們知道rbx的值,是從ucontext的rbx字段中恢復出來的,其是指向棧頂的uc_link,所以,就是將當前的棧頂指針指向uc_link,即pop出了makecontext時,傳入的所有參數,然后會調用setcontext()來恢復后繼上下文的環境,參數rdi就是uc_link的值。整個流程如下圖:

      startcontext.png

      2.3. swapcontext實現1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      71

      72

      73

      74

      75

      76

      77

      78

      79

      80

      81

      82

      83ENTRY(__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)

      leaq8(%rsp), %rcx/* Exclude the return address. */

      movq%rcx, oRSP(%rdi)

      /* We have separate floating-point register content memory on the

      stack. We use the __fpregs_mem block in the context. Set the

      links up correctly. */

      leaqoFPREGSMEM(%rdi), %rcx

      movq%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 with

      rt_sigprocmask (SIG_BLOCK, newset, oldset,_NSIG/8). */

      leaqoSIGMASK(%rdi), %rdx

      leaqoSIGMASK(%rsi), %rsi

      movl$SIG_SETMASK, %edi

      movl$_NSIG8,%r10d

      movl$__NR_rt_sigprocmask, %eax

      syscall

      cmpq$-4095, %rax/* Check %rax for error. */

      jaeSYSCALL_ERROR_LABEL/* Jump to error handler if error. */

      /* Restore destroyed registers. */

      movq%r12, %rsi

      /* Restore the floating-point context. Not the registers, only the

      rest. */

      movqoFPREGS(%rsi), %rcx

      fldenv(%rcx)

      ldmxcsr oMXCSR(%rsi)

      /* Load the new stack pointer and the preserved registers. */

      movqoRSP(%rsi), %rsp

      movqoRBX(%rsi), %rbx

      movqoRBP(%rsi), %rbp

      movqoR12(%rsi), %r12

      movqoR13(%rsi), %r13

      movqoR14(%rsi), %r14

      movqoR15(%rsi), %r15

      /* The following ret should return to the address set with

      getcontext. Therefore push the address on the stack. */

      movqoRIP(%rsi), %rcx

      pushq%rcx

      /* Setup registers used for passing args. */

      movqoRDI(%rsi), %rdi

      movqoRDX(%rsi), %rdx

      movqoRCX(%rsi), %rcx

      movqoR8(%rsi), %r8

      movqoR9(%rsi), %r9

      /* Setup finally %rsi. */

      movqoRSI(%rsi), %rsi

      /* Clear rax to indicate success. */

      xorl%eax, %eax

      ret

      PSEUDO_END(__swapcontext)

      swapcontext就是在getcontext的基礎上,將參數二中上下文中保存的各個寄存器字段恢復到當前進程的各個寄存器中,和getcontext的流程相反。就不細說,有興趣的可以自己看。

      3. ucontext示例

      下面從最簡單的代碼來解析ucontext切換的過程:1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37#include

      #include

      #include

      #include

      ucontext_t uc, ucm;

      void foo()

      {

      printf("%s\n", __FUNCTION__);

      }

      int main()

      {

      // allocate stack

      size_t co_stack_size = 64*1024;

      char * co_stack = (char *)malloc(co_stack_size);

      memset(co_stack, 0, co_stack_size);

      //get current context

      getcontext(&uc);

      // make ucontext to run foo

      uc.uc_stack.ss_sp = co_stack;

      uc.uc_stack.ss_size = co_stack_size;

      uc.uc_link = &ucm;

      makecontext(&uc, &foo, 0);

      // switching back-and-forth for 100 times

      for (int i = 0; i < 100; i++)

      {

      swapcontext(&ucm, &uc);

      }

      free(co_stack);

      return 0;

      }

      getcontext調用時,進程上下文信息,以及ucontext的變化情況:

      ucontext_eg_1.png

      在調用getcontext之前,進程棧空間只有兩個值stack_size和p_stack,當執行getcontext后,會將下一調指令地址即RIP寄存器值push入棧, 然后進入getcontext中后,會將當前進程的上下文全部保存到傳入的ucontext參數中,以及上面說的進程的信號屏蔽掩碼。在getcontext操作中保存的兩個最重要的寄存器信息就是rip和rsp了,分別用于恢復上下后所要執行的指令地址和棧頂指針。

      當執行makecontext后:當前進程上下文沒有任何變化,只是對傳入的ucontext上下文進行操作,變化如下:

      ucontext_eg_2.png

      當執行到swapcontext時,當前進程的棧空間會切換到uc自定義的棧空間,且會從uc上下文uc_mcontext字段中恢復出各個寄存器的值,

      其中rbx用于uc執行完畢后,執行其后繼上下文;

      rip中指向下一條要執行的指令,即函數foo()的地址

      rsp指向uc自定義棧空間的棧頂;

      foo函數執行完畢后,會將uc自定義棧的局部變量全部彈出,然后棧空間又恢復到剛進入foo()的狀態,此時會彈出棧頂的start_context()到RIP中,其做完函數執行完畢的下一條指令,下面start_context()的執行就是將自定義stack的uc_link所指向的上下文恢復執行。所以此時進程的上下文狀態就會回到swapcontext的時候的下一條語句繼續執行。

      下面是執行的結果:1

      2

      3foo

      foo

      Segmentation fault (core dumped)

      為什么會coredump呢,我是想他輸出100個foo的,那就gdb調試看看自定義棧出了什么問題:

      gdb進入后在swapcontext之前打斷點,如下:自定義棧的起始地址為0x602010,

      ucontext_gdb_1.png

      自定義棧大小為:1size_t co_stack_size = 64*1024;

      所以uc上下文的自定義棧底為0x612010,棧頂的位置在0x611ff8,在makecontext()后,自定義棧內部壓入了uc上下文的后繼上下文ucm和用于跳轉到后繼上下文的函數__start_context()地址。

      當swapcontext執行后,進程切換到uc上下文執行,執行foo()函數,foo()執行結束之前,uc自定義棧的數據如下:

      ucontext_gdb_2.png

      可以看到foo()ret之前,自定義站棧頂指向0x611ff0,其內容是沒有swapcontext之前棧頂指針,這里不管它,因為foo()執行完之前會pop出該內容,然后棧頂指針就是指向0x611ff8,然后再執行ret指令。ret指令,會首先從棧頂pop出內容到RIP寄存器,進行下一條指令的執行:前面說了0x611ff8是__start_context()地址。

      ucontext_gdb_3.png

      進入__start_context的匯編代碼后,棧頂指針已經指向0x612000,如上圖,此時棧頂就是后繼上下文ucm的地址;然后將棧頂pop到rdi中,然后會通過調用setcontext,將ucm上限為恢復到進程的寄存器中,在call調用setcontext時,同時會壓入下一條指令的地址,就是0x7ffff7a5db6e,如下:

      ucontext_gdb_4.png

      這里我們就會發現一點:uc的自定義棧的數據已經完全被破壞,所以,當執行完后繼上下文ucm,然后在swapcontext()中,再次切換到foo()后,uc的自定義棧已經完全不是第一次使用的狀態,當再次進行后繼上下文執行時,core在了后繼上下文的回復過程中,因為此時的0x612000已經不在執行ucm,而是一個__start_context()指令地址。

      uc上下文執行過程中,只有自定義棧會在運行時被修改,uc 的ucontext_t數據結構是不會發生改變的, 為了能夠讓該代碼達到預期:進行如下修改就好了。

      1

      2

      3

      4

      5for (int i = 0; i < 100; i++)

      {

      swapcontext(&ucm, &uc);

      makecontext(&uc, &foo, 0);

      }

      [參考]

      總結

      以上是生活随笔為你收集整理的linux ucontext 类型,协程:posix::ucontext用户级线程实现原理分析 | WalkerTalking的全部內容,希望文章能夠幫你解決所遇到的問題。

      如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

      主站蜘蛛池模板: 久久精品一区二区三区黑人印度 | 久久精品久久久久久久 | 国产精品网站在线 | 少妇久久久久久被弄高潮 | 亚洲国产视频在线观看 | 国产精品麻豆入口 | 我不卡av | 日本黄频| 久久黄色影视 | 久久亚洲综合国产精品99麻豆精品福利 | 国产精品国产三级国产播12软件 | 蜜桃视频在线入口www | 亚洲啪啪免费视频 | 俄罗斯色片| 久久久久性 | 亚洲伦理视频 | 国产精品电影一区二区 | 91国产免费观看 | 欧洲丰满少妇做爰 | 日本xxxx裸体xxxx出水 | 黑人导航 | 亚洲精品天天 | 国产又粗又长又黄 | 欧美成人精品三级网站 | 国产福利片在线观看 | 四虎影院黄色 | 亚洲一区二区视频 | 国产精品分类 | 欧美日韩激情网 | 久操视频网 | 国产馆av | 一曲二曲三曲在线观看中文字幕动漫 | 免费无码av片在线观看 | 91在线日韩| 九九精品在线观看视频 | 国产人妻人伦精品1国产盗摄 | av天天草| www在线观看免费视频 | 蜜桃视频一区二区三区在线观看 | 久久激情婷婷 | 全部毛片永久免费看 | 亚洲欧美日韩中文在线 | 免费观看一区二区三区毛片 | 亚洲国产精品女人 | 插插网站 | 岛国av免费 | 成人在线视频免费播放 | 亚洲午夜精品久久久久久app | 在线观看欧美一区二区 | 性色网站 | 手机av免费看 | 国产精品videossex久久发布 | 免费日韩在线 | 98精品国产 | 日韩欧美视频在线 | 日日夜夜噜噜噜 | 黄色日韩 | 天天干天天插天天射 | 91婷婷射| 久久国产精品系列 | 天堂精品在线 | 欧美激情亚洲综合 | 久久夜夜夜 | 亚洲免费看黄 | 精品国产免费一区二区三区 | av网站在线观看不卡 | 狠狠躁天天躁综合网 | 国产视频一区二区三区在线观看 | 1024视频污 | 成人夜晚看av | 国产av成人一区二区三区 | 黄网站免费在线 | 国产精品3p视频 | 亚洲午夜精品久久久久久浪潮 | 色香蕉在线视频 | 亚洲麻豆| 成人h视频| 亚洲两性视频 | 日韩国产精品一区二区三区 | 一区二区网 | 日韩在线一区二区 | 韩国精品av | 老司机精品视频在线播放 | 欧美国产在线看 | 国产做爰全过程免费视频 | 免费久久| 亚洲欧美一区二区三区孕妇 | 围产精品久久久久久久 | 老司机精品视频网站 | 在线播放波多野结衣 | 亚洲偷 | 青青青操 | av瑟瑟 | 在线观看亚洲一区 | 百合sm惩罚室羞辱调教 | 国产91视频播放 | 婷婷综合一区 | 一区二区三区三区在线 | 国产精彩视频在线 |