contributed by < Wallmountain >
不用抄寫題目,直接探討和改進 coro 實作。
以下嘗試用 lab0 提及的 setjmp 和 longjmp,用以實作〈你所不知道的 C 語言: goto 和流程控制篇〉闡述的 coroutine,參考的程式執行輸出如下:
原始程式碼如下: (檔名 jmp.c)
首先,先來看 setjmp
和 longjmp
的文件
The
setjmp()
function saves various information about the calling environment (typically, the stack pointer, the instruction pointer, possibly the values of other registers and the signal mask) in the buffer env for later use bylongjmp()
. In this case,setjmp()
returns 0.
setjmp
可跨越函式,在任意程式碼之間跳躍 (即 non-local goto),儲存目前位置資訊,之後可以使用 longjmp
跳躍到目前儲存的位置
The
longjmp()
function uses the information saved in env to transfer control back to the point where setjmp() was called and to restore ("rewind") the stack to its state at the time of the setjmp() call. In addition, and depending on the implementation (see NOTES), the values of some other registers and the process signal mask may be restored to their state at the time of thesetjmp()
call.
修改原程式碼中的 task
函式,印出變數的數值,去看執行結果是否符合預期
在下面的狀況下,執行結果並不符合預期,在 task 0 中的區域變數 i
在 task 1 完成後,發生了非預期的變化,變數 i
的值和原先尚未 longjmp
前不同。
而問題在文件中有提到
The compiler may optimize variables into registers, and longjmp()
may restore the values of other registers in addition to the
stack pointer and program counter. Consequently, the values of
automatic variables are unspecified after a call to longjmp() if
they meet all the following criteria:
• they are local to the function that made the corresponding
setjmp() call;
• their values are changed between the calls to setjmp() and
longjmp(); and
• they are not declared as volatile.
在編譯器優化後, longjmp
可能不會恢復 stack
中的變數,於是將變數 i
宣告成靜態變數,變數 i
便不會放在 stack
中了,以此解決這邊因為編譯器造成的問題。
在 preempt_sched 有實作 preemptive scheduling,將 task
以鏈結的形式儲存
若在將新工作加入鏈結或刪除鏈結中的工作時被搶占,則有機會發生工作遺失的問題,因此加入靜態變數
preempt_count
,當在需要更改工作的鏈結時,將其改成不能被搶占,便能解決 race condition
在 preempt.h 中,有用到以下的程式碼,也顯示了在 linux kernel 中各個執行緒擁有其各自的變數 preempt_count
,在需要搶佔時,必須去確定當前執行緒是否能夠被搶佔
0-7 bit 為 preempt-disable count 紀錄到目前為止,搶占被禁止的次數
接著三個 count 為分別記錄執行緒被中斷的次數
8-15 bit 為 Software interrupt count
16-19 bit 為 Hardware interrupt count
20-23 bit 為 Non-maskable interrupt count
接著,來研究 signal
使用方式,以下為 sigaction
的結構
sa_handler
為 function pointer,代表訊號產生時要做的行為
sa_sigaction
只有在當sa_flags
被賦值為SA_SIGINFO
時,取代sa_handler
成為訊號產生時要做的行為
當 sa_flags
被賦值為 SA_SIGINFO
時,handler
會轉變成指向帶有三個參數的函式的指標
sig
為導致觸發信號處理函式的信號編號
info
為指向帶有信號資訊結構的指標
ucontext
為指向結構ucontext_t
的指標,用來記錄存在user space stack
中的信號內容
uc_link
為指向下一個內容的指標,當前的context
可用 makecontext 建立
為避免在 printf
此類系統呼叫時被搶占,故將其包夾成一個 crititcal section
設定多久之後要觸發訊號 SIGALRM
,而之後會不會繼續每隔一段時間觸發訊號,則是要另外設定間格時間
問題 :
訊號觸發後,程式會跳到後續的處理函式,但由於跳轉前的函式執行位置來不及去用 setjmp
進行記錄,因此便會造成程式執行結果不符合預期
在原程式碼中,在每一次 longjmp
前,都會用 setjmp
儲存當前函式進行的進度
ucontext_t
需要儲存所有暫存器的資料,而jmp_buf
只需要儲存有用到的暫存器即可,因此ucontext_t
會比jmp_buf
大不少
對照 cserv 的 coroutine (也叫做 coro
) 實作,裡頭的 context 切換成本較低,且考慮到排程。
在 setjmp 中,有介紹
sigsetjmp
會另外紀錄signal mask
, 之後再用siglongjmp
回到記錄點
一開始,在程式當中加入每隔一段時間會觸發訊號,而結果發生問題