Coroutine
教材
setjmp/longjmp
cross function goto
compile and run
callee-saved暫存器狀態存到jmp_buf,這是暫存器狀態應該跨呼叫應該保持的
rip存到jmp_buf
- void longjmp(jmp_buf env, int val);
回復setjmp時候的狀態,
並且從執行setjmp的下一行指令執行
non-rpeemptive schedulers
stack 位移問題:導致任何區域變數會造成原本的堆疊被改寫
理由:全部co-routine分享一個stack,longjmp會改變stack ptr
導致原本的堆疊變數被覆寫
解決辦法: 增加stacks
Explanation of asm call
Put the address of the work (tasks[i]) into a special storage called rax.
Think of rax like a temporary container the CPU uses to remember something important.
-
Replace the current workspace (rsp) with your prepared workspace (stack).
The rsp is like the computer's "desk" space where it keeps temporary stuff.
Now, you're telling the CPU to switch and use your special workspace (stack) instead of the current one.
-
Start executing (call) the function stored at the address you just put into rax.
This is like telling the CPU: "Go do the work I gave you earlier, using the new workspace we set up."
coro
source: https://github.com/sysprog21/concurrent-programs/tree/master/coro
Preemptive scheduling
目標
- 學習使用ucontext.h
- Linux signal API
- 用SIGALRM完成搶佔式排程
source
API 學習
ucontext.h
-
#include <ucontext.h>
-
struct ucontext_t
- getcontext(ucontext_t *ucp): initialize the structure pointed by ucp to current active context: https://man7.org/linux/man-pages/man3/getcontext.3.html
- setcontext(const ucontext_t *ucp): restore the user context pointed to by ucp
- makecontext(ucontext_t *ucp, typeof(void (int arg0, …)) *func,
int argc, …): modify the context pointed by ucp. (need to initialize the stack, and define successor ucontext_t). args can only be integers
- int swapcontext(ucontext_t *restrict oucp,
const ucontext_t *restrict ucp): save the current context to oucp and activate the context pointed by ucp (主要switch)
signal.h
- #include<signal.h> man
- struct sigaction
- sigaddset(sigset_t *set, int signo): add signo to the set
- int sigdelset(sigset_t *set, int signo): delete a signal from the set
- sigfillset(sigset_t *set): all signals defined in the volume are inclued
- int sigaction(int signum,
const struct sigaction *_Nullable restrict act,
struct sigaction *_Nullable restrict oldact): change an action of signal token by a process (SIGKILL, SIGSTOP not included)
- int sigprocmask(int how, const sigset_t *_Nullable restrict set,
sigset_t *_Nullable restrict oldset)
unistd
- #include <unistd.h>
- useconds_t ualarm(useconds_t usecs, useconds_t interval): send SIGALRM to the invoking process after usecs. If interval is not 0, will trigger SIGALRM signal every interval usec
Task resched
設定task context, stack, callback
執行時會把timer signal block
透過task_ptr union重新把task結構組裝回來, 因為makecontext只限制整數參數
這裡重新允許timer signal 處理
執行callback(sort) 並排程
scheduler主要邏輯
context 轉換
main
主要流程:
- 建立SIGALRM timer
- 加入task
- task 透過註冊的task_trampoline去執行真正的callback,這裡在前面拼湊task結構是SIGALRM blocked,自己認為是前面不該被搶佔
- schedule函式是主要從全域變數task_current找尋串列是否有可以執行的task
- main函式透過每一次timer interrupt來檢查全域變數任務串列task_main和回收任務串列task_reap是否為空, 全為空代表排程器完成它應有工作
- 為什麼task_trampoline後面會unreachable? 理由:因為已經被標註成reap_self, 不再會被排程器執行
防搶佔和irq set/restore
這裡模仿實際kernel會使用到的方式
實例:防搶佔 printf
Tinync