【網絡通信 -- 直播】SRS 源碼分析 -- 協程基礎知識點總結(一)
【1】協程簡介
IO 同步操作的邏輯代碼
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csMzYU1ENnpnTzkkeYhnRzwEMW1mY1RzRapnTtxkb5ckYplTeMZTTINGMShUYfRHelRHLwEzX39GZhh2css2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3PH5EUuUjM3UTM1ATMxIDNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
IO 異步操作的邏輯代碼
IO 異步操作與 IO 同步操作對比
協程主要解決的問題,提供同時具有異步性能與同步代碼邏輯的解決方案;C/C++ 典型的協程庫 NtyCo,https://github.com/wangbojing/NtyCo;
【2】NtyCo 簡介
【2.1】NtyCo 相關 API 簡介
NtyCo 封裝了若幹接口,一類是協程本身的,一類是 posix 異步封裝協程 API;
協程相關 API 函數接口
協程建立
int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg);
協程排程器運作
void nty_schedule_run(void);
POSIX 異步封裝 API 函數接口
建立 socke 執行個體
int nty_socket(int domain, int type, int protocol);
接收請求
int nty_accept(int fd, struct sockaddr *addr, socklen_t *len);
接收資料
ssize_t nty_recv(int fd, void *buf, size_t len, int flags);
發送資料
ssize_t nty_send(int fd, const void *buf, size_t len, int flags);
關閉 socket 執行個體
int nty_close(int fd);
【2.2】協程的實作
1. 協程的實作之建立協程
int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg);
- 參數 1 : nty_coroutine **new_co,需要傳入空的協程的對象,這個對象是由函數内部建立并傳回的;
- 參數 2 : proc_coroutine func 協程的子過程;當協程被排程的時候,就會執行該函數;
- 參數 3 : void *arg 需要傳入到新協程中的參數;
協程不存在親屬關系,都是一緻的排程關系,接受排程器的排程;調用 create API 就會建立一個新協程,新協程就會加入到排程器的就緒隊列中;
2. 協程的實作之實作 IO 異步操作
排程器與協程的上下文切換圖示
在協程的上下文 IO 異步操作函數,步驟如下
- 1. 将 sockfd 添加到 epoll 管理中
- 2. 進行上下文環境切換,由協程上下文 yield 到排程器的上下文
- 3. 排程器擷取下一個協程上下文,resume 新的協程
IO 異步操作的上下文切換的時序圖
3. 協程的實作之回調協程的子過程
關鍵示例代碼
static void nty_coroutine_init(nty_coroutine *co) {
void **stack = (void **)(co->stack + co->stack_size);
stack[-3] = NULL;
stack[-2] = (void *)co;
// ctx 協程的上下文
// 可以将回調函數位址存入 EIP 中,将相應參數存儲到相應的參數寄存器中
co->ctx.esp = (void*)stack - (4 * sizeof(void*));
co->ctx.ebp = (void*)stack - (3 * sizeof(void*));
// CPU 寄存器 EIP 用于存儲 CPU 運作下一條指令的位址
co->ctx.eip = (void*)_exec; // 設定回調函數入口
co->status = BIT(NTY_COROUTINE_STATUS_READY);
}
static void _exec(void *lt) {
#if defined(__lvm__) && defined(__x86_64__)
__asm__("movq 16(%%rbp), %[lt]" : [lt] "=r" (lt));
#endif
nty_coroutine *co = (nty_coroutine*)lt;
co->func(co->arg); //
co->status |= (BIT(NTY_COROUTINE_STATUS_EXITED) | BIT(NTY_COROUTINE_STATUS_FDEOF)
| BIT(NTY_COROUTINE_STATUS_DETACH));
#if 1
nty_coroutine_yield(co);
#else
co->ops = 0;
_switch(&co->sched->ctx, &co->ctx);
#endif
}
4. 協程實作之原語操作
協程的核心原語操作 : create, resume, yield;
協程的原語操作有 create 但卻沒有 exit 的分析
- 以 NtyCo 為例,協程一旦建立就不能由使用者自己銷毀,必須在子過程執行結束時便自動銷毀協程的上下文資料;
- 以 _exec 執行入口函數傳回而銷毀協程的上下文與相關資訊;
- co->func(co->arg) 是子過程,若使用者需要長久運作協程,就必須要在 func 函數裡面寫入循環等操作;
- 是以 NtyCo 裡面沒有實作 exit 的原語操作
原語 create : 建立一個協程
// 1. 排程器是否存在,不存在也建立;排程器作為全局的單例;将排程器的執行個體存儲線上程的私有空間 pthread_setspecific
// 2. 配置設定一個 coroutine 的記憶體空間,分别設定
// coroutine 的資料項,棧空間,棧大小,初始狀态,建立時間,子過程回調函數,子過程的調用參數
// 3. 将新配置設定協程添加到就緒隊列 ready_queue 中
int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg)
原語 yield : 讓出 CPU
// 參數:需要恢複運作的協程執行個體
// 調用後該函數也不會立即傳回,而是切換到運作協程執行個體的 yield 的位置;
// 傳回是在等協程相應事務處理完成後,主動 yield 會傳回到 resume 的地方;
void nty_coroutine_yield(nty_coroutine *co)
原語 resume : 恢複協程的運作權
// 參數:需要恢複運作的協程執行個體
// 調用後該函數也不會立即傳回,而是切換到運作協程執行個體的 yield 的位置;
// 傳回是在等協程相應事務處理完成後,主動 yield 會傳回到 resume 的地方;
int nty_coroutine_resume(nty_coroutine *co)
5. 協程實作之切換
上下文切換,就是将 CPU 的寄存器暫時儲存,再将即将運作的協程的上下文寄存器,分别 mov 到相對應的寄存器上,進而完成上下文的切換;
nty_cpu_ctx 結構體
// 關聯相關寄存器的結構體
typedef struct _nty_cpu_ctx {
void *esp;
void *ebp;
void *eip;
void *edi;
void *esi;
void *ebx;
void *r1;
void *r2;
void *r3;
void *r4;
void *r5;
} nty_cpu_ctx;
_switch 函數 API
// 參數 1 : 即将運作協程的上下文,寄存器清單
// 參數 2 : 正在運作協程的上下文,寄存器清單
int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx);
// _switch 傳回後,執行即将運作協程的上下文,進而實作上下文的切換
_switch 函數實作代碼分析
// %rdi 函數第一個參數, %rsi 函數第二個參數
__asm__ (
" .text \n"
" .p2align 4,,15 \n"
".globl _switch \n"
".globl __switch \n"
"_switch: \n"
"__switch: \n"
" movq %rsp, 0(%rsi) # save stack_pointer \n" // 儲存棧指針到 cur_ctx 執行個體的 rsp 項
" movq %rbp, 8(%rsi) # save frame_pointer \n" // 儲存幀指針到 cur_ctx 執行個體的 rbp 項
" movq (%rsp), %rax # save insn_pointer \n" // 将棧頂位址裡面的值存儲到 rax 寄存器中
" movq %rax, 16(%rsi) \n" // 儲存 rax,rbx,r12 - r15
" movq %rbx, 24(%rsi) # save rbx,r12-r15 \n"
" movq %r12, 32(%rsi) \n"
" movq %r13, 40(%rsi) \n"
" movq %r14, 48(%rsi) \n"
" movq %r15, 56(%rsi) \n"
" movq 56(%rdi), %r15 \n" // 将 rdi 對應偏移量中的值儲存入相應的寄存器中
" movq 48(%rdi), %r14 \n" // 即将 rdi 對應偏移量中的值儲存入 new_ctx 的每一個項中
" movq 40(%rdi), %r13 # restore rbx,r12-r15 \n"
" movq 32(%rdi), %r12 \n"
" movq 24(%rdi), %rbx \n"
" movq 8(%rdi), %rbp # restore frame_pointer \n"
" movq 0(%rdi), %rsp # restore stack_pointer \n"
" movq 16(%rdi), %rax # restore insn_pointer \n" // 将指令指針 rip 的值存儲到 rax 中
" movq %rax, (%rsp) \n" // 将存儲 rip 值的 rax 寄存器指派給棧指針指向的位址處
" ret \n" // 出棧,回到棧指針,執行 rip 指向的指令
);
6. 協程實作之定義
協程的運作體 R 與運作體排程器 S
- 1. 運作體 R:包含運作狀态 { 就緒,睡眠,等待 },運作體回調函數,回調參數,棧指針,棧大小,目前運作體;
- 2. 排程器 S:包含執行集合 { 就緒,睡眠,等待 };
6.1 運作體在多種狀态集合的切換
協程的狀态
- 建立新的協程,建立完成後,便加入到就緒集合,等待排程器的排程;
- 協程在運作完成後,進行 IO 操作,此時 IO 并未準備好,便進入等待狀态集合;
- IO 準備就緒,協程開始運作,後續進行 sleep 操作,便進入到睡眠狀态集合;
就緒 (ready),睡眠 (sleep),等待 (wait) 集合對應的資料結構
- 就緒 (ready) 集合并沒有設定優先級的選型,所有的協程優先級一緻,可以使用隊列來存儲就緒的協程,簡稱為就緒隊列 (ready_queue)
- 睡眠 (sleep) 集合需要按照睡眠時長進行排序,是以采用紅黑樹來存儲,簡稱睡眠樹 (sleep_tree);紅黑樹在工程中為 <key, value>, key 為睡眠時長,value 為對應的協程結點
- 等待 (wait) 集合,其功能是在等待 IO 準備就緒,等待 IO 是有時長的,是以等待 (wait) 集合采用紅黑樹存儲,簡稱等待樹 (wait_tree)
圖示中,Coroutine 是協程的相應屬性,status 表示協程的運作狀态,并包含 sleep 與 wait 兩棵紅黑樹以及 ready 隊列;前提條件是不管何種運作狀态的協程,都在就緒隊列中,隻是同時包含有其他的運作狀态;
6.2 排程器與協程的功能界限
協程屬性 : 每一協程都需要使用的而且可能會不同的屬性;排程器屬性 : 每一協程都需要的而且一緻的資料;
協程的核心結構體
typedef struct _nty_coroutine {
//private
// 協程自身的上下文,需要儲存 CPU 的寄存器 ctx
nty_cpu_ctx ctx;
// 子過程的回調函數 func
proc_coroutine func;
// 子過程回調函數的參數 arg 以及資料 data
void *arg;
void *data;
// 協程自身的棧空間的大小 stack_size
size_t stack_size;
size_t last_stack_size;
// 協程目前的運作狀态
nty_coroutine_status status;
// 排程器的全局對象
nty_schedule *sched;
// 協程的建立時間
uint64_t birth;
// 協程 id
uint64_t id;
#if CANCEL_FD_WAIT_UINT64
int fd;
unsigned short events; //POLL_EVENT
#else
int64_t fd_wait;
#endif
char funcname[64];
struct _nty_coroutine *co_join;
void **co_exit_ptr;
// 協程自身的棧空間
void *stack;
void *ebp;
uint32_t ops;
uint64_t sleep_usecs;
// 目前運作狀态的結點
RB_ENTRY(_nty_coroutine) sleep_node;
RB_ENTRY(_nty_coroutine) wait_node;
LIST_ENTRY(_nty_coroutine) busy_next; //
TAILQ_ENTRY(_nty_coroutine) ready_next;
TAILQ_ENTRY(_nty_coroutine) defer_next;
TAILQ_ENTRY(_nty_coroutine) cond_next;
TAILQ_ENTRY(_nty_coroutine) io_next;
TAILQ_ENTRY(_nty_coroutine) compute_next;
struct {
void *buf;
size_t nbytes;
int fd;
int ret;
int err;
} io;
struct _nty_coroutine_compute_sched *compute_sched;
int ready_fds;
struct pollfd *pfds;
nfds_t nfds;
} nty_coroutine;
排程器的屬性,需要有儲存 CPU 的寄存器上下文 ctx,可以從協程運作狀态 yield 到排程器運作;從協程到排程器用 yield,從排程器到協程用 resume;
排程器結構體
typedef struct _nty_coroutine_link nty_coroutine_link;
typedef struct _nty_coroutine_queue nty_coroutine_queue;
typedef struct _nty_coroutine_rbtree_sleep nty_coroutine_rbtree_sleep;
typedef struct _nty_coroutine_rbtree_wait nty_coroutine_rbtree_wait;
typedef struct _nty_schedule {
uint64_t birth;
nty_cpu_ctx ctx;
void *stack;
size_t stack_size;
int spawned_coroutines;
uint64_t default_timeout;
struct _nty_coroutine *curr_thread;
int page_size;
int poller_fd;
int eventfd;
struct epoll_event eventlist[NTY_CO_MAX_EVENTS];
int nevents;
int num_new_events;
pthread_mutex_t defer_mutex;
nty_coroutine_queue ready;
nty_coroutine_queue defer;
nty_coroutine_link busy;
nty_coroutine_rbtree_sleep sleeping;
nty_coroutine_rbtree_wait waiting;
//private
} nty_schedule;
7. 協程實作之排程器
排程器的實作,有兩種方案,一種是生産者消費者模式,另一種是多狀态運作;
7.1 生産者消費者模式
邏輯代碼示例
while (1) {
// 周遊睡眠集合,将滿足條件的協程加入到 ready 隊列
nty_coroutine *expired = NULL;
while ((expired = sleep_tree_expired(sched)) != NULL) {
TAILQ_ADD(&sched->ready, expired);
}
// 周遊等待集合,将滿足條件的協程加入到 ready 隊列
nty_coroutine *wait = NULL;
int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
for (i = 0; i < nready; i ++) {
wait = wait_tree_search(events[i].data.fd);
TAILQ_ADD(&sched->ready, wait);
}
// 使用 resume 恢複 ready 的協程運作權
while (!TAILQ_EMPTY(&sched->ready)) {
nty_coroutine *ready = TAILQ_POP(sched->ready);
resume(ready);
}
}
7.2 多狀态運作
邏輯代碼示例
while (1) {
// 周遊睡眠集合,使用 resume 恢複 expired 的協程運作權
nty_coroutine *expired = NULL;
while ((expired = sleep_tree_expired(sched)) != NULL) {
resume(expired);
}
// 周遊等待集合,使用 resume 恢複 wait 的協程運作權
nty_coroutine *wait = NULL;
int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
for (i = 0; i < nready; i ++) {
wait = wait_tree_search(events[i].data.fd);
resume(wait);
}
// 使用 resume 恢複 ready 的協程運作權
while (!TAILQ_EMPTY(sched->ready)) {
nty_coroutine *ready = TAILQ_POP(sched->ready);
resume(ready);
}
}
補充知識點
1. X86-64寄存器和棧幀
1.1 寄存器
通用寄存器
X86_64 有 16 個 64 位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%rsi,%rdi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15;
- %rax 作為函數傳回值使用
- %rsp 棧指針寄存器,指向棧頂
- %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函數參數,依次對應第 1 參數,第 2 參數...
- %rbx,%rbp,%r12,%r13,%r14,%r15 用作資料存儲,遵循被調用者使用規則,調用子函數之前要備份上述寄存器,以防被修改
- %r10,%r11 用作資料存儲,遵循調用者使用規則,即使用之前要先儲存原值
段寄存器
- cs 代碼段寄存器
- ds, es, fs, gs 資料段寄存器
- ss 堆棧段寄存器
狀态和控制寄存器 eflags
該寄存器表示的意義非常豐富,程式中并不直接操作此寄存器,并由此衍生出很多操作指令,除去一些保留位,其他每位都代表一個具體的含義,
其中 bits 0, 2, 4, 6, 7, 11 是狀态位,辨別了此操作後的狀态
- CF (bit 0) —— 進位辨別,算術操作進行了進位和借位,則此位被設定
- PF (bit 2) —— 奇偶辨別,結果包含奇數個1,則設定此位
- AF (bit 4) —— 輔助進位辨別,結果的第3位向第4位借位,則此位被設定
- ZF (bit 6) —— 零辨別,結果為零,此位設定
- SF (bit 7) —— 符号辨別,若為負數則設定此位
- OF (bit 11) —— 溢出辨別,結果向最高位符号位進行借位或者進位,此标志被設定
8, 9, 10 位為控制辨別
- TF (bit 8) —— 陷阱辨別,設定程序可以被單步調試
- IF (bit 9) —— 中斷辨別,設定能夠響應中斷請求
- DF (bit 10) —— 方向辨別,用于标示字元處理過程中指針移動方向
指令寄存器 EIP
EIP —— 标志目前程序将要執行指令位置,在64位模式下擴充為 RIP 64位指令寄存器
1.2 棧幀
1.2.1 棧幀結構
C 語言屬于面向過程語言,其最大特點就是把一個程式分解成若幹過程 (函數),比如:入口函數是 main,然後調用各個子函數;在對應機器語言中,GCC 把過程轉化成棧幀 (frame),每個棧幀對應一個過程;X86-32 典型棧幀結構中,由 %ebp 指向棧幀開始,%esp 指向棧頂;
1.2.2 C 函數與彙編分析
int foo ( int x )
{
int array[] = {1,3,5};
return array[x];
} /* ----- end of function foo ----- */
int main ( int argc, char *argv[] )
{
int i = 1;
int j = foo(i);
fprintf(stdout, "i=%d,j=%d\n", i, j);
return EXIT_SUCCESS;
} /* ----- end of function main ----- */
未經過優化的結果
優化後的結果
從優化可見,此處直接引用了棧頂之外的空間;
通路棧頂之外
通過 readelf 檢視可執行程式的 header 資訊
根據紅色區域可見 x86-64 遵循 ABI 規則的版本,其定義了一些規範,遵循 ABI 的具體實作應該滿足這些規範,其中,便規定了程式可以使用棧頂之外 128 位元組的位址;
彙編相關分析
一、movl value, %eax
- movl value, %eax 這條指令是把 value 的值 (32位),送入 %eax 寄存器;
- value 表達式格式 : base_address (offset_address, index, size);計算公式 : base_address + offset_address + index * size;
二、call foo
- Pushl %rip // 儲存下一條指令的位址,用于函數傳回繼續執行
- Jmp foo // 跳轉到函數 foo
三、ret
- popl %rip // 恢複指令指針寄存器
四、cltq
- cltq 指令,特指 %eax->%rax 的符号拓展轉換,等價于 movslq %eax, %rax
五、.LC0
.LC0 指一個字元串,此處代表 fprintf 的格式化字元串;
六、leave
leave 指令等價于
Movq %rbp %rsp // 撤銷棧空間,復原 %rsp
Popq %rbp // 恢複上一個棧幀的 %rbp
1.3 寄存器儲存慣例
C 語言示例代碼
#include <stdio.h>
#include <stdlib.h>
void sfact_helper ( long int x, long int * resultp)
{
if (x <= 1)
*resultp = 1;
else {
long int nresult;
sfact_helper(x-1, &nresult);
*resultp = x * nresult;
}
} /* ----- end of function foo ----- */
long int sfact ( long int x )
{
long int result;
sfact_helper(x, &result);
return result;
} /* ----- end of function sfact ----- */
int
main ( int argc, char *argv[] )
{
int sum = sfact(10);
fprintf(stdout, "sum=%d\n", sum);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
sfact_helper 函數生成的彙編代碼
在函數 sfact_helper 中,用到了寄存器 %rbx 和 %rbp,在覆寫之前,GCC 選擇了先儲存他們的值,如代碼 6~9 所示;在函數傳回之前,GCC 依次恢複了他們,如代碼 27-28 所示;
%rbx 在函數進入的時候指向的是 -16(%rsp),而在退出的時候,指向的是32(%rsp);此處使用了通路棧幀之外的空間的特性,即 GCC 不用預先配置設定空間再使用,而是先使用棧空間,然後在适當的時機配置設定,如第 11 行代碼配置設定了空間,之後棧指針發生變化,是以同一個位址的引用偏移也相應做出調整;
注意事項
- 盡量使用 6 個以下的參數清單
- 傳遞大對象,盡量使用指針或者引用,鑒于寄存器隻有 64 位,而且隻能存儲整形數值,寄存器存不下大對象
彙編相關分析
一、lea 指令
- 指令格式 : LEA 目的 源
- 指令功能 : 取源操作數位址的偏移量,并把它傳送到目的操作數所在的單元
二、imul 指令
- (1) 雙操作數的有符号乘指令
- 語句格式 : IMUL OPD, OPS
- 功能 : (OPD) * (OPS) ----> OPD
- 其中 OPD 可為 16/32 的寄存器,OPS 可為同類型的寄存器、存儲器操作數或立即數
- (2) 3 個操作數的有符号乘指令
- 語句格式 : IMUL OPD, OPS, N
- 功能 : (OPS * N) -----> OPD
- 其中 OPD 可為 16/32 的寄存器,OPS 可為同類型的寄存器、存儲器操作數,n 為立即數
- (3) 單操作數的有符号乘指令
- 語句格式 : IMUL OPS
- 功能 :
- 位元組乘法 : (AL) * (OPS) ----> AX
- 字乘法 : (AX) * (OPS) ----> DX, AX
- 雙字乘法 : (EAX) * (OPS) ----> EDX, EAX
三、mul 指令
- 語句格式 : MUL OPS
- 功能 :
- 位元組乘法 : (AL) * (OPS) ----> AX
- 字乘法 : (AX) * (OPS) ----> DX, AX
- 雙字乘法 : (EAX) * (OPS) ----> EDX, EAX
- 功能 :
1.4 參數傳遞範例
C 示例代碼
#include <stdio.h>
#include <stdlib.h>
int foo ( int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7 )
{
int array[] = {100, 200, 300, 400, 500, 600, 700};
int sum = array[ arg1 ] + array[ arg7 ];
return sum;
} /* ----- end of function foo ----- */
int
main ( int argc, char *argv[] )
{
int i = 1;
int j = foo(0, 1, 2, 3, 4, 5, 6);
fprintf(stdout, "i = %d, j = %d\n", i, j);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
生成的彙編代碼
Main 函數中,代碼 31~37 準備函數 foo 的參數
- 參數 7 存儲在棧上即 %rsp 指向的位置;
- 參數 6 存儲在寄存器 %r9d;
- 參數 5 存儲在寄存器 %r8d;
- 參數 4 對應于 %ecx;
- 參數 3 對應于 %edx;
- 參數 2 對應于 %esi;
- 參數 1 對應于 %edi;
Foo 函數中,代碼 14-15,分别取出參數 7 和參數 1,參與運算;
此處數組引用,使用了最經典的尋址方式,-40(%rsp,%rdi,4) = %rsp + %rdi * 4 + (-40); 其中 %rsp 用作數組基位址,%rdi 用作了數組的下标,數字 4 表示 sizeof(int) = 4
1.5 結構體傳參
C 示例代碼
#include <stdio.h>
#include <stdlib.h>
struct demo_s {
char var8;
int var32;
long var64;
};
struct demo_s foo (struct demo_s d)
{
d.var8 = 8;
d.var32 = 32;
d.var64 = 64;
return d;
} /* ----- end of function foo ----- */
int
main ( int argc, char *argv[] )
{
struct demo_s d, result;
result = foo (d);
fprintf(stdout, "demo: %d, %d, %ld\n", result.var8, result.var32, result.var64);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
生成彙編代碼
- 問題1 : 結構體如何傳遞;被分成了兩個部分,var8 和 var32 合并成 8 個位元組的大小,放在寄存器 %rdi 中,var64 放在寄存器的 %rsi 中,即結構體分解
- 問題2 : 結構體如何存儲;如 foo 函數的第 15~17 行所示,結構體的引用變成了一個偏移量通路;類似于數組,隻不過其元素大小可變;
- 問題3 : 結構體如何傳回,原本 %rax 充當傳回值的角色,現在添加第二個傳回值角色 %rdx;同樣,GCC 用兩個寄存器來表示結構體;
- 即使在預設情況下,GCC 依然想盡辦法使用寄存器;随着結構變得越來越大,寄存器不夠用了,則隻能使用棧了;
參考與緻謝
本部落格為部落客的學習實踐總結,并參考了衆多部落客的博文,在此表示感謝,部落客若有不足之處,請批評指正。
【1】協程的實作與原理 [ M ]
【2】X86-64寄存器和棧幀
【3】x64 ASM 常用彙編指令
【4】movl 16(%ebx,%esi,4),%eax的含義是?
【5】寄存器介紹
【6】Push, Pop, call, leave 和 Ret 指令圖解
【7】鎮宅之寶扛鼎之作
【8】協程的實作之切換