boost 协程中 arm64 和 x86_64 架构汇编实现

boost 协程上下文切换 make_arm64_aapcs_elf_gas.S 和 jump_arm64_aapcs_elf_gas.S 以及 make_x86_64_aapcs_elf_gas.S 和 jump_x86_64_aapcs_elf_gas.S 汇编源码分析

源码目录:boost-1.58.0\libs\context\src\asm

make_arm64_aapcs_elf_gas.S

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
83
84
85
/*
Copyright Edward Nevill 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/*******************************************************
* *
* ------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| *
* ------------------------------------------------- *
* | d8 | d9 | d10 | d11 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ------------------------------------------------- *
* | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| *
* ------------------------------------------------- *
* | d12 | d13 | d14 | d15 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
* ------------------------------------------------- *
* | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| *
* ------------------------------------------------- *
* | x19 | x20 | x21 | x22 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
* ------------------------------------------------- *
* | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| *
* ------------------------------------------------- *
* | x23 | x24 | x25 | x26 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
* ------------------------------------------------- *
* | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| *
* ------------------------------------------------- *
* | x27 | x28 | FP | LR | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 40 | 41 | 42 | 43 | | | *
* ------------------------------------------------- *
* | 0xa0| 0xa4| 0xa8| 0xac| | | *
* ------------------------------------------------- *
* | PC | align | | | *
* ------------------------------------------------- *
* *
*******************************************************/

.cpu generic+fp+simd
.text
.align 2
.global make_fcontext
.type make_fcontext, %function
make_fcontext:
# shift address in x0 (allocated stack) to lower 16 byte boundary
and x0, x0, ~0xF

# reserve space for context-data on context-stack
sub x0, x0, #0xb0

# third arg of make_fcontext() == address of context-function
# store address as a PC to jump in
str x2, [x0, #0xa0]

# save address of finish as return-address for context-function
# will be entered after context-function returns (LR register)
adr x1, finish
str x1, [x0, #0x98]

ret x30 // return pointer to context-data (x0)

finish:
# exit code is zero
mov x0, #0
# exit application
bl _exit

.size make_fcontext,.-make_fcontext
# Mark that we don't need executable stack.
.section .note.GNU-stack,"",%progbits

jump_arm64_aapcs_elf_gas.S

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
Copyright Edward Nevill 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
/*******************************************************
* *
* ------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| *
* ------------------------------------------------- *
* | d8 | d9 | d10 | d11 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ------------------------------------------------- *
* | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| *
* ------------------------------------------------- *
* | d12 | d13 | d14 | d15 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
* ------------------------------------------------- *
* | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| *
* ------------------------------------------------- *
* | x19 | x20 | x21 | x22 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
* ------------------------------------------------- *
* | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| *
* ------------------------------------------------- *
* | x23 | x24 | x25 | x26 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
* ------------------------------------------------- *
* | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| *
* ------------------------------------------------- *
* | x27 | x28 | FP | LR | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 40 | 41 | 42 | 43 | | | *
* ------------------------------------------------- *
* | 0xa0| 0xa4| 0xa8| 0xac| | | *
* ------------------------------------------------- *
* | PC | align | | | *
* ------------------------------------------------- *
* *
*******************************************************/

.cpu generic+fp+simd
.text
.align 2
.global jump_fcontext
.type jump_fcontext, %function
jump_fcontext:
# prepare stack for GP + FPU
sub sp, sp, #0xb0

# Because gcc may save integer registers in fp registers across a
# function call we cannot skip saving the fp registers.
#
# Do not reinstate this test unless you fully understand what you
# are doing.
#
# # test if fpu env should be preserved
# cmp w3, #0
# b.eq 1f

# save d8 - d15
stp d8, d9, [sp, #0x00]
stp d10, d11, [sp, #0x10]
stp d12, d13, [sp, #0x20]
stp d14, d15, [sp, #0x30]

1:
# save x19-x30
stp x19, x20, [sp, #0x40]
stp x21, x22, [sp, #0x50]
stp x23, x24, [sp, #0x60]
stp x25, x26, [sp, #0x70]
stp x27, x28, [sp, #0x80]
stp x29, x30, [sp, #0x90]

# save LR as PC
str x30, [sp, #0xa0]

# store RSP (pointing to context-data) in first argument (x0).
# STR cannot have sp as a target register
mov x4, sp
str x4, [x0]

# restore RSP (pointing to context-data) from A2 (x1)
mov sp, x1

# # test if fpu env should be preserved
# cmp w3, #0
# b.eq 2f

# load d8 - d15
ldp d8, d9, [sp, #0x00]
ldp d10, d11, [sp, #0x10]
ldp d12, d13, [sp, #0x20]
ldp d14, d15, [sp, #0x30]

2:
# load x19-x30
ldp x19, x20, [sp, #0x40]
ldp x21, x22, [sp, #0x50]
ldp x23, x24, [sp, #0x60]
ldp x25, x26, [sp, #0x70]
ldp x27, x28, [sp, #0x80]
ldp x29, x30, [sp, #0x90]

# use third arg as return value after jump
# and as first arg in context function
mov x0, x2

# load pc
ldr x4, [sp, #0xa0]

# restore stack from GP + FPU
add sp, sp, #0xb0

ret x4
.size jump_fcontext,.-jump_fcontext
# Mark that we don't need executable stack.
.section .note.GNU-stack,"",%progbits

make_x86_64_sysv_elf_gas.S

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
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/

/****************************************************************************************
* *
* ---------------------------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ---------------------------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
* ---------------------------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ---------------------------------------------------------------------------------- *
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
* ---------------------------------------------------------------------------------- *
* | R15 | RBX | RBP | RIP | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 16 | 17 | | *
* ---------------------------------------------------------------------------------- *
* | 0x40 | 0x44 | | *
* ---------------------------------------------------------------------------------- *
* | EXIT | | *
* ---------------------------------------------------------------------------------- *
* *
****************************************************************************************/

.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

jump_x86_64_sysv_elf_gas.S

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/*
Copyright Oliver Kowalke 2009.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/

/****************************************************************************************
* *
* ---------------------------------------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ---------------------------------------------------------------------------------- *
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
* ---------------------------------------------------------------------------------- *
* | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ---------------------------------------------------------------------------------- *
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
* ---------------------------------------------------------------------------------- *
* | R15 | RBX | RBP | RIP | *
* ---------------------------------------------------------------------------------- *
* ---------------------------------------------------------------------------------- *
* | 16 | 17 | | *
* ---------------------------------------------------------------------------------- *
* | 0x40 | 0x44 | | *
* ---------------------------------------------------------------------------------- *
* | EXIT | | *
* ---------------------------------------------------------------------------------- *
* *
****************************************************************************************/

.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

x86_64 架构上下文切换

实现协程最核心的部分就是栈切换了,其他的和非阻塞 io 的编程方式没什么区别。

栈切换,libc 中有一个实现,swapcontext,但是已经被标准移除了,未来是否可用不得而知,自己实现需要写汇编代码,这是一个很困难的任务,因为既要熟悉不同 cpu 指令集又要熟悉不同平台的标准,好在从 boost library 的协程实现中找到了已经写好了的栈切换汇编代码,利用这些汇编代码可以在 c 语言中实现栈切换。

这段代码是在 s_task 协程库中发现的,s_task 很好,还可以和 libuv 结合,如果没有特殊要求,可以直接使用了,但是如果想根据自己工作中的业务逻辑做定制,还是需要掌握原理,并且不清楚原理,可能不能用最恰当的方式使用,实现最好的设计,出了问题也不知道该怎么查和用什么办法查。

附上 s_task 和 boost library 的地址,感兴趣的可以去研究一下。

https://github.com/xhawk18/s_task

https://github.com/boostorg/context

栈切换代码在源码的 asm 目录中,实际上在 c 语言中对应两个函数,

1
2
3
4
5
6
7
8
typedef void* fcontext_t;
typedef struct {
fcontext_t fctx;
void* data;
} transfer_t;

extern transfer_t jump_fcontext( fcontext_t const to, void * vp);
extern fcontext_t make_fcontext( void * sp, size_t size, void (* fn)( transfer_t) );

复制代码

这两个函数是什么意思,怎么用,看了 s_task 中的代码,但是开始的时候还是没看懂,于是想从汇编的角度入手,最终通过 x86_64 的汇编代码 (make_x86_64_sysv_elf_gas.S jump_x86_64_sysv_elf_gas.S) 弄清楚了这两个函数的用法,结论在本文最末尾,如果只想看结果,可以跳到最后面。

直接看代码注释吧。

make_x86_64_sysv_elf_gas.S

复制代码

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
  1 /*
2 Copyright Oliver Kowalke 2009.
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or copy at
5 http://www.boost.org/LICENSE_1_0.txt)
6 */
7
8 // 栈空间图,栈顶在低地址,注意和jump_x86_64_sysv_elf_gas.S对比着看,里面代表的是内存(栈,运行现场)中的数据
9 // 不代表寄存器,途中标的寄存器的意思是这些寄存器在保存现场的时候会保存到对应的内存地址中
10 /****************************************************************************************
11 * *
12 * ---------------------------------------------------------------------------------- *
13 * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
14 * ---------------------------------------------------------------------------------- *
15 * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
16 * ---------------------------------------------------------------------------------- *
17 * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
18 * ---------------------------------------------------------------------------------- *
19 * ---------------------------------------------------------------------------------- *
20 * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
21 * ---------------------------------------------------------------------------------- *
22 * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
23 * ---------------------------------------------------------------------------------- *
24 * | R15 | RBX | RBP | RIP | *
25 * ---------------------------------------------------------------------------------- *
26 * *
27 ****************************************************************************************/
28
29 .file "make_x86_64_sysv_elf_gas.S"
30 .text
31 .globl make_fcontext
32 .type make_fcontext,@function
33 .align 16
34 make_fcontext:
35 // make_fcontext的第一个参数保存到rax中,rax现在开始代表了这个运行环境的栈顶,
36 // rax也是本函数的返回值,
37 /* first arg of make_fcontext() == top of context-stack */
38 movq %rdi, %rax
39
40 // 16对齐, 规定的
41 /* shift address in RAX to lower 16 byte boundary */
42 andq $-16, %rax
43
44 // -0x40就是将栈顶指针(栈顶寄存器,这里不是rsp,是rax,rsp是当前运行环境使用的)移动到图中的0位置,即分配栈空间
45 // 递减栈是向下分配的
46 /* reserve space for context-data on context-stack */
47 /* on context-function entry: (RSP -0x8) % 16 == 0 */
48 leaq -0x40(%rax), %rax
49
50 // size那个参数这里没有使用,第三个参数是fn,要执行的函数
51 // 这里把fn的地址放到了0x28的位置,恢复到寄存器就是rbx
52 /* third arg of make_fcontext() == address of context-function */
53 /* stored in RBX */
54 movq %rdx, 0x28(%rax)
55
56 // 这两个寄存器不清楚
57 /* save MMX control- and status-word */
58 stmxcsr (%rax)
59 /* save x87 control-word */
60 fnstcw 0x4(%rax)
61
62 // 从英文注释看,实现的是将trampoline的地址保存在0x38的位置,即rip保存的地方,返回之后接着运行的地址
63 // 所以这个地方实际上是设置了跳转过来之后执行的指令的位置,jump_fcontext认为自己跳到了
64 // 一个暂停过的地方,即保存过现场的地方,恢复现场继续执行,而这里是首次运行,所以要模拟这种场景
65 // 也就是说启动一个任务之后会先运行trampoline那里。
66 /* compute abs address of label trampoline */
67 leaq trampoline(%rip), %rcx
68 /* save address of trampoline as return-address for context-function */
69 /* will be entered after calling jump_fcontext() first time */
70 movq %rcx, 0x38(%rax)
71
72 // 这里是把finish处的地址放到0x30中, 是一个技巧,见trampoline处
73 /* compute abs address of label finish */
74 leaq finish(%rip), %rcx
75 /* save address of finish as return-address for context-function */
76 /* will be entered after context-function returns */
77 movq %rcx, 0x30(%rax)
78
79 // make_fcontext函数返回
80 ret /* return pointer to context-data */
81
82 trampoline:
83 /* store return address on stack */
84 /* fix stack alignment */
85 // 从jump_x86_64_sysv_elf_gas.S中可以看出,跳到这里之前已经从栈空间中恢复了rbp,也就是说
86 // 现在rbp保存的是finish的地址,因为rbp是从0x30恢复的,0x30前面已经保存了finish的地址
87 // 现在的栈顶rsp呢,是在图中0x40的位置,见jump_x86_64_sysv_elf_gas.S中的 leaq 0x40(%rsp), %rsp
88 // 现在push rbp是把fish的地址push到了0x38的位置,也就是本应该保存返回地址rip的地方,
89 // 也就是说fn运行结束返回到make_fcontext的finish处继续运行,就好像是make_fcontext调用
90 // 的fn一样,当然只是好像而已。可见fn返回了整个进程就退出了,这是make_fcontext指定的。
91 push %rbp
92
93 // rbx是设置的fn的地址,跳到fn运行,首次运行fn任务才会走到这里,中间暂停了
94 // 是恢复从暂停的地方的下一个指令开始执行。
95 /* jump to context-function */
96 jmp *%rbx
97
98 finish:
99 // 这里的代码就是退出进程了
100 /* exit code is zero */
101 xorq %rdi, %rdi
102 /* exit application */
103 call _exit@PLT
104 hlt
105 .size make_fcontext,.-make_fcontext
106
107 /* Mark that we don't need executable stack. */
108 .section .note.GNU-stack,"",%progbits

jump_x86_64_sysv_elf_gas.S

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
  1 /*
2 Copyright Oliver Kowalke 2009.
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or copy at
5 http://www.boost.org/LICENSE_1_0.txt)
6 */
7
8 // 栈空间图,栈顶在低地址,注意和make_x86_64_sysv_elf_gas.S对比着看,里面代表的是内存(栈,运行现场)中的数据
9 // 不代表寄存器,途中标的寄存器的意思是这些寄存器在保存现场的时候会保存到对应的内存地址中
10 /****************************************************************************************
11 * *
12 * ---------------------------------------------------------------------------------- *
13 * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
14 * ---------------------------------------------------------------------------------- *
15 * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
16 * ---------------------------------------------------------------------------------- *
17 * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
18 * ---------------------------------------------------------------------------------- *
19 * ---------------------------------------------------------------------------------- *
20 * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
21 * ---------------------------------------------------------------------------------- *
22 * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
23 * ---------------------------------------------------------------------------------- *
24 * | R15 | RBX | RBP | RIP | *
25 * ---------------------------------------------------------------------------------- *
26 * *
27 ****************************************************************************************/
28
29 .file "jump_x86_64_sysv_elf_gas.S"
30 .text
31 .globl jump_fcontext
32 .type jump_fcontext,@function
33 .align 16
34 jump_fcontext:
35 // 保存当前的运行现场,caller-saved registers调用者保存,这里只保存callee-saved registers
36 leaq -0x38(%rsp), %rsp /* prepare stack */
37
38 #if !defined(BOOST_USE_TSX)
39 stmxcsr (%rsp) /* save MMX control- and status-word */
40 fnstcw 0x4(%rsp) /* save x87 control-word */
41 #endif
42
43 movq %r12, 0x8(%rsp) /* save R12 */
44 movq %r13, 0x10(%rsp) /* save R13 */
45 movq %r14, 0x18(%rsp) /* save R14 */
46 movq %r15, 0x20(%rsp) /* save R15 */
47 movq %rbx, 0x28(%rsp) /* save RBX */
48 movq %rbp, 0x30(%rsp) /* save RBP */
49 // 这里为什么没有使用0x38位置的8个字节,因为这里面存的是返回地址,即本函数返回的时候
50 // 要继续运行的指令的地址,在调用jump_fcontext的时候已经保存了,所以这里不用保存
51 // 看make_fcontext可以看到,那里使用了0x38,因为那个fcontext不是通过调用jump_fcontext
52 // 生成的,而是人为制造的。
53 //
54 // 保存现场完毕
55
56 // 函数的调用者会在被调用函数退出的时候读取rax作为返回值
57 // transfer_t需要两个寄存器保存,另一个是rdx
58 // 但注意这里的返回值不是本次jump_fcontext调用的返回值,
59 // 而是另一个栈空间的jump_fcontext,本次调用是不返回的,
60 // 实际上所有的jump_fcontext都是不返回的,都是别的地方
61 // 跳过去,看起来好像是它返回
62 //
63 // 函数调用就好比是一维世界,从哪里进,再从哪里出,而jump_fcontext好比是二维的世界,
64 // 从“天上”跑了,再从“天上”掉下来。
65 /* store RSP (pointing to context-data) in RAX */
66 movq %rsp, %rax
67
68 // 把jump_fcontext第一个参数给rsp,第一个参数即运行上下文,别的地方
69 // 保存现场或make_fcontext生成的栈顶地址,这里放到
70 // rsp中,即完成了栈空间的切换,下条命令开始就是在另一个
71 // 栈空间运行了,已经跳走完成, 跳之前做了什么呢,保存现场
72 // 并且把现场放到rax中,跳过去之后的返回值会返回跳之前的现场
73 /* restore RSP (pointing to context-data) from RDI */
74 movq %rdi, %rsp
75
76 // 这里开始是一个新的栈空间了,也就是一个新的任务
77 // 分两种情况,
78 // 1. 如果是恢复一个任务的运行,那么rsp是它调用jump_fcontext切换走
79 // 的时候的现场,现在开始恢复,这时0x38位置是返回地址,也就是这个
80 // 任务在上一次调用jump_fcontext(暂停,切换到其他任务)的时候保存
81 // 的调用jump_fcontext后面的那个指令对应的地址,即从暂停的位置
82 // 继续执行。
83 // 2. 如果是新制造出来的任务,0x38保存的是make_fcontext中的trampoline
84 // 也就是会跳到那里执行。
85 //
86 // 目前只是放到了r8中,还没有开始执行,后面的过程是继续恢复现场。
87 movq 0x38(%rsp), %r8 /* restore return-address */
88
89 #if !defined(BOOST_USE_TSX)
90 ldmxcsr (%rsp) /* restore MMX control- and status-word */
91 fldcw 0x4(%rsp) /* restore x87 control-word */
92 #endif
93
94 // 保存和恢复的过程是对称的,正是栈的特点
95 movq 0x8(%rsp), %r12 /* restore R12 */
96 movq 0x10(%rsp), %r13 /* restore R13 */
97 movq 0x18(%rsp), %r14 /* restore R14 */
98 movq 0x20(%rsp), %r15 /* restore R15 */
99 movq 0x28(%rsp), %rbx /* restore RBX */
100 movq 0x30(%rsp), %rbp /* restore RBP */
101
102 // 恢复另一个任务保存现场之前的rsp(栈顶)
103 leaq 0x40(%rsp), %rsp /* prepare stack */
104
105 // 这里面有条件编译,似乎是和32位和64位相关,这里按照上面的分支来分析
106 /* return transfer_t from jump */
107 #if !defined(_ILP32)
108 /* RAX == fctx, RDX == data */
109 // rsi是jump_fcontext的第二个参数vp,这里赋值给rdx,rax rdx
110 // 里面存储的是返回值transfer_t的内容,rax是前面我们保存现场
111 // 获得的,也就是说是切换之前的现场,rdx呢,也是切换之前传的,
112 // 也就是说切换之后返回的内容都是切换之前的信息。
113 // 一般网上查到的都是说rax是返回值,但是这里返回结构体,里面
114 // 是两个8字节,到底是怎么传递的很少有地方说,我也是网上查到的
115 // 这种情况是用两个寄存器保存的,rdx也会用来保存返回值,有一个文档可以参考
116 // System V Application Binary Interface AMD64 Architecture Processor Supplement
117 movq %rsi, %rdx
118 #else
119 /* RAX == data:fctx */
120 salq $32, %rsi
121 orq %rsi, %rax
122 #endif
123 /* pass transfer_t as first arg in context function */
124 #if !defined(_ILP32)
125 /* RDI == fctx, RSI == data */
126 #else
127 /* RDI == data:fctx */
128 #endif
129
130 // rdi是函数调用的第一个参数,rsi是第二个参数
131 // 这里把rax放到rdi中,是为了把切换之前保存的现场作为第一个参数传给要执行的函数
132 // 这是给第一次运行准备的,fn的参数transfer_t的两个成员,一个是rdi,一个是rsi
133 // rsi就是切换之前调用的(即本次调用)那个jump_fcontext传递的第二个参数vp
134 //
135 // 如果不是第一次调用呢,会不会有影响,不会,rdi是caller-saved register,调用者
136 // 负责保存,如果即将切换到的那个任务的对应的函数需要rdi的值,它会自己保存的,
137 // 从jump_fcontext出来之后它会自己恢复,对它来讲就是调用了jump_fcontext这个函数,
138 // 然后过了一段时间返回了,中间的过程完全不知情。 天上走了,又从天上回来。
139 movq %rax, %rdi
140
141 // 前面提到了将返回地址放到r8中,现在跳过去开始执行了
142 /* indirect jump to context */
143 jmp *%r8
144 .size jump_fcontext,.-jump_fcontext
145
146 /* Mark that we don't need executable stack. */
147 .section .note.GNU-stack,"",%progbits

简单测试一下

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
 1 #include <stdio.h>
2
3 char stack0[10240];//一个printf会占用1千多字节的栈空间!!!!!
4 char stack1[10240];
5
6 typedef void* fcontext_t;
7 typedef struct {
8 fcontext_t fctx;
9 void* data;
10 } transfer_t;
11
12 extern transfer_t jump_fcontext(fcontext_t const to, void * vp);
13 extern fcontext_t make_fcontext(void * sp, size_t size, void (* fn)( transfer_t));
14
15 fcontext_t fcmain, fc0, fc1;
16
17 void fn0(transfer_t t){
18 printf("%s %d\n", __func__, __LINE__);
19
20 fcontext_t *p = t.data;
21 *p = t.fctx;
22
23 // 切换fn1
24 transfer_t ret = jump_fcontext(fc1, &fc0);
25 p = ret.data;
26 *p = ret.fctx;
27 printf("%s %d\n", __func__, __LINE__);
28 // 切换main
29 jump_fcontext(fcmain, &fc0);
30 printf("never back\n");
31 }
32
33 void fn1(transfer_t t){
34 printf("%p\n", t.fctx);
35 printf("%s %d\n", __func__, __LINE__);
36 fcontext_t *p = t.data;
37 *p = t.fctx;
38 // 切换fn0
39 jump_fcontext(fc0, &fc1);
40 printf("never back\n");
41 }
42
43 int main(int argc, char **argv) {
44
45 fc0 = make_fcontext(stack0 + sizeof stack0, sizeof stack0, fn0);
46 fc1 = make_fcontext(stack1 + sizeof stack1, sizeof stack1, fn1);
47
48 printf("%s %d\n", __func__, __LINE__);
49 // 切换fn0
50 jump_fcontext(fc0, &fcmain);
51 printf("%s %d\n", __func__, __LINE__);
52
53 return 0;
54 }
1
2
3
4
5
6
7
8
 % gcc -O t.c asm/make_gas.S asm/jump_gas.S
% ./a.out
main 48
fn0 18
0x5591d8183800
fn1 35
fn0 27
main 51

结论:

make_fcontext 中的 sp 是栈顶指针,size 是栈空间的大小,虽然 cpu 可能支持两种方向,但目前我还没有听过栈方向递增的系统,如果是递减栈,应该把栈顶指针设置成最高地址,比如申请了一段内存作为函数栈,大小 1024,地址在 void *p 中,那么应该这样用

1
make_fcontext(p+1024, 1024, fn);

fn 是协程任务启动的时候运行的函数,和线程创建的时候指定的函数类似。make_fcontext 的返回值就是一个运行 (栈空间) 上下文,它将被用于 jump_context 中的 to 参数,这样可以启动这个任务。启动任务之后调用 fn,fn 是 jump_context 中调用的,函数参数传递的实际上是两部分,第一部分,fcontext_t 类型,是栈切换之前的运行上下文,第二个参数是

jump_fcontext 中传递的 vp 参数。jump_fcontext 的返回值也分为两部分,第一部分是栈切换之前的运行上下文,第二部分是传递的参数 vp(这个 vp 不是本任务 jump_fcontext 调用的 vp,而是其他任务切换到本任务的时候调用 jump_fcontext 传递的 vp),这是因为 jump_fcontext 不是普通的函数,普通的函数在一个栈空间上执行,而 jump_fcontext 是跨越栈空间的,函数调用前,要保存现场,然后再恢复,jump_fcontext 也要保存现场,但是恢复并不是在这个函数调用中恢复的,这个调用已经一去不复返了,只负责跳走,不负责回来,甚至可能永远也回不来了,与其说它回来了,不如说是别的地方跳过来了,jump_fcontext “返回” 的就是跳过来的那个人的信息,它的运行上下文 (保存着它的运行现场,cpu 寄存器,栈空间),还有它调用 jump_fcontext 跳过来时传递的参数 vp。

一个任务只要执行,它的运行上下文就是在变化的,也就是说只要一个任务一运行,那么保存它的运行上下文的变量 (fcontext_t) 就失效了,也就是每运行一次就要更新一次,什么时候更新呢,就是本次运行暂停的时候更新,也就是调用 jump_fcontext 跳到其他地方的时候,也就是说目标任务要更新本任务的运行上下文变量,目标任务激活的时候表现为从它的 jump_fcontext 返回 (实际上不是它返回,而是本务跳到那去执行),返回值中的 fcontext_t 就是跳之前的运行上下文,也就是本任务的最新的运行上下文,为了更新本任务的运行上下文变量,需要把本任务的运行上下文变量的地址传递给目标任务,这需要借助 jump_fcontext 的 vp 参数,它也会出现在目标任务返回的 transfer_t 中,这就是参数 vp 和返回值 transfer_t 的作用吧,当然借助 vp 还可以传递更多需要交互的信息。

每一个普通任务都是通过 make_fcontext 创造出来的,但主任务 (main 函数) 不是,主任务切换到了其他任务执行,如果想切换回主任务,就必须获取主任务的运行上下文,fn 函数本来只需要自己运行的数据就够了,而它的参数 transfer_t 还带一个 fcontext,就是干这件事用的,它是最新的切换之前的运行上下文,谁启动的它,就更新谁,一个任务切换到一个曾经运行过的任务的时候一定是跳到 jump_fcontext 的下条指令,可以通过” 返回” 的 transfer_t 来实现更新,但是对于新任务,不会从 jump_fcontext 继续执行,不会得到” 返回” 的 transfer_t,就需要利用参数传递的 transfer_t 更新。

主任务启动 -> task0 -> jump_fcontext , task0 恢复执行,这时候返回的 transfer_t 不一定是主任务的运行上下文,因为它是切换 task0 任务,使 task0 再次运行的那个任务的运行上下文,那个任务未必是主任务,主任务只是第一次启动 task0 的时候的任务,并不一定是后续激活 task0 的任务。

如果 fn 函数运行完成,没有通过 jump_fcontext 切换任务,直接返回,那么结果是整个进程都退出了,因为它返回之后是返回到了 make_fcontext 的一段代码中,那段代码的操作是退出进程。从 gdb 里用 bt 可以看到,一个任务的调用者是 make_fcontext,而不是启动它或再次激活它的时候对应的函数,这是因为 make_fcontext 里面设置这个函数的时候同时设置好了它的返回地址,所以如果不想退出进程,一个任务完成的时候需要跳到其他任务,然后释放保存本任务的栈空间资源,需要注意的是释放是要在别的地方释放 (s_task 库中有一个 join 任务的地方),而不能在跳转之前释放,因为跳转的时候要保存当前运行上下文到当前的栈空间,如果释放了再跳转会造成非法地址的错误。