使用ucontext 來手動切換上下文
簡介
使用ucontext.h裡面的函式和資料結構,可以讓使用者實現手動來切換函式的執行流,**libtask-輕量級協程庫**就是使用ucontext.h來實現協程的。
主要的資料結構
主要的資料結構是:ucontext_t:
typedef struct ucontext {
unsigned long int uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
__sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
} ucontext_t;
其中有幾個成員是我們需要注意的:
成員 | 說明 |
---|---|
uc_link | 這個成員指向了當前函式返回之後繼續執行的上下文 |
uc_sigmask | 這個成員是跟訊號相關的 |
uc_stack | 這個成員是當前上下文所使用的棧 |
uc_mcontext | 這個成員包括了一些暫存器變數 |
Warning:如果uc_link指標為NULL,那麼當該上下文結束之後,執行該上下文的執行緒會退出
我們主要會使用下面4個函式來對上下文進行操作:
成員 | 說明 |
---|---|
getcontext(ucontext_t *ucp) | 獲取當前執行的上下文 |
setcontext(const ucontext_t *ucp) | 恢復ucp指向的上下文 |
makecontext(ucontext_t *ucp, void(*func)(void), int argc, …) | 設定該上下文到函式func,並且設定上下文開始執行時的引數和引數個數 |
swapcontext(ucontext_t *oucp, const ucontext_t *ucp) | 切換到ucp上下文執行,並且把當前上下文儲存在oucp裡面 |
所以一般我們的呼叫順序是:
1.getcontext:先得到當前的上下文。
2.makecontext:設定該上下文到函式func。
3.setcontext or swapcontext:切換到指定的上下文開始執行。
測試程式碼
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <string.h>
#include <errno.h>
static ucontext_t mainContext, sumContext, multiContext;
#define STACKSIZE 1 << 13
int add(int a, int b) {
printf("Calling the add function.\n");
int n;
if ((n = swapcontext(&sumContext, &multiContext)) < 0) {
perror("The Swap Context Error");
}
printf("The add result is : %d.\n", a + b);
return a + b;
}
int mul(int a, int b) {
printf("Calling the mul function.\n");
int n;
if ((n = swapcontext(&multiContext, &sumContext)) < 0) {
perror("The Swap Context Error");
}
printf("The multi result is : %d.\n", a * b);
return a * b;
}
int main(int argc, char **argv) {
int n;
char firstStack[STACKSIZE];
char secondStack[STACKSIZE];
memset(firstStack, 0, STACKSIZE);
memset(secondStack, 0, STACKSIZE);
if ((n = getcontext(&sumContext)) < 0) {
perror("Get Context Error");
return -1;
}
sumContext.uc_stack.ss_sp = firstStack;
sumContext.uc_stack.ss_size = STACKSIZE;
sumContext.uc_link = &mainContext;
if ((n = getcontext(&multiContext)) < 0) {
perror("Get Context Error");
return -1;
}
multiContext.uc_stack.ss_sp = secondStack;
multiContext.uc_stack.ss_size = STACKSIZE;
multiContext.uc_link = &sumContext;
makecontext(&sumContext, (void(*)())add, 2, 4, 5);
makecontext(&multiContext, (void(*)())mul, 2, 3, 4);
if ((n = swapcontext(&mainContext, &multiContext)) < 0) {
perror("The Swap Context Error");
}
return 0;
}
上面執行完之後的結果是:
上面的程式碼會如下執行:
1.主函式先執行,並且設定sumContext, multiContext兩個上下文的棧和上下文的開始執行位置。
2.暫停主函式的執行,把主函式的上下文儲存在mainContext裡面,然後切換到addContext去執行,因為addContext的開始執行點為mul函式,所以會從mul函式的第一條指令開始往下執行。
3.mul函式裡面又會進行切換,切換到sumContext上下文開始執行,並且儲存當前執行的上下文到multiContext裡面。
4.在add函式裡面再次進行切換,切換到multiContext上下文,因為在第三步的時候,已經把相關的上下文儲存在了multiContext裡面,所以切換的時候會繼續mul函式中斷之後的下一條指令開始執行。
5.multiContext返回,multiContext的uc_link欄位指向的是sumContext,所以sumContext繼續執行。
6.sumContext返回,sumContext的uc_link欄位指向的是主函式的Context,所以主函式繼續執行。
7.主函式return。
相關連結
在libtask庫裡面,就是使用了ucontext裡面的函式和結構體來對作業系統的任務進行再一次的抽象,抽象成為routine,並且自己實現了一個排程器來對routine進行排程。
具體的程式碼可以在:傳送門下載。
具體的實現檔案為:task.h task.c taskimpl.h fd.c