OrzMicrokernel tasks.asm 工作排程器的切換執行權函式註解
工作新增、載入後,一切都準備就緒了,main.asm 最後一個任務,就是讓作業系統的心臟:『工作排程器』開始跳動,使所有工作能夠生生不息的運轉。Orz Microkernel 利用了 Schedule 這個 Function 切換到工作清單中的下一個工作,並利用 IRQ_0 的計時器,每一定單位時間就切換一次,使所有工作如同同時執行一般。
原始程式碼和詳細註解:
後記
如同開頭所說的,完整的工作排程架構還涵蓋了 IRQ0 的計時器部份,而此部份程式碼於 irq_handlers.asm 之中,日後有空將再行註解。
另外,在這次 Schedule 系列函式中,我個人有一點小疑問在這行:
movzx eax, BYTE [eax]
在這行命令中只留下 EAX 最後 8bits(1 Byte) 資料,問題是為何要刻意取其中 8bits 出來呢?
就算沒有這行命令,我們還是可以正常的來計算記憶體偏移量,那此行命令不就多此一舉?希望知道的人能回應我的愚笨問題。
原始程式碼和詳細註解:
;--------------------------------------------------------------------------
; Scheduler 工作排程器:
; 多工的實作原理是讓所有的工作行程,依序輪流切換佔用 CPU 的運算能力。
; 由於每個工作佔用 CPU 的時間極小,因此近乎同時所有工作同時執行一般。
;
; Schedule: 是用來切換到下一個工作,並讓其佔用 CPU 的函式。在 main.asm 中呼叫後,
; 就會設定 taskRunningTime 工作執行的時間長度,而在 irq_handlers.asm 中
; 的 IRQ_0 計時器,就會開始依照 taskRunningTime 定時轉換到下一個工作。
tx_NoMoreTasks db "No more tasks",0 ; 設定沒有工作的訊息字串
Schedule:
mov eax, [tasksCount] ; 設 EAX 為目前工作數量
and eax, eax ; 若工作數量為零則設定零值旗標 ZF = 1
jnz Schedule0 ; 若旗標 ZF = 0 則跳到 Schedule0
mov esi, tx_NoMoreTasks ; 在 ESI 寫入沒有工作的訊息字串
jmp GpFault ; 輸出訊息
Schedule0:
mov eax, [currentTaskN] ; 設 EAX = currentTaskN
inc eax ; EAX 加一
cmp eax, [tasksCount] ; 若EAX<tasksCount則設定進位旗標CF = 1
jc Schedule1 ; 若進位旗標 CF = 1 則跳到 Schedule1
xor eax, eax ; 清除 EAX
Schedule1:
mov [currentTaskN], eax ; 從 EAX 回存到 currentTaskN
; tasksList 所占記憶體大小為 MAX_TASKS(參考 tables.asm)
add eax, tasksList ; 從工作清單的記憶體起始位址
movzx eax, BYTE [eax] ; 只留下 EAX 最後 8bits(1 Byte)
imul eax, s_task.msize ; 計算此工作的狀態記錄記憶體偏移量
; = 目前執行的工作編號 x s_task 的大小
add eax, tasks ; EAX = 偏移量 + 工作的狀態記錄的記憶體開始位址
mov [currentTaskP], eax ; 設 currentTaskP 為此工作狀態記錄的記憶體位址
mov ebp, eax ; 設 EBP 指向工作狀態記錄的記憶體位址
mov eax, [ebp + s_task.state] ; 取得工作狀態放入 EAX
cmp eax, STATE_SENDW ; 若 EAX = STATE_SENDW 則零值旗標 ZF = 1
jz Schedule0 ; 若零值旗標 ZF = 1 則跳到 Schedule0
cmp eax, STATE_STOP ; 若 EAX = STATE_STOP 則零值旗標 ZF = 1
jz Schedule0 ; 若零值旗標 ZF = 1 則跳到 Schedule0
ScheduleCurr:
; 轉換到將要執行的工作
cli ; 禁用中斷以免干擾工作執行權轉換
mov eax, 0x20 ; 設定 EAX = 0x20 (此工作執行的時間長度)
mov [taskRunningTime], eax ; 設 taskRunningTime 為 EAX
; taskcode 和 task_data(參考 tables.asm)
mov eax, [ebp + s_task.taskPageAddr] ; 設 EAX 為此工作的記憶體分頁位址
mov [task_code + 2], ax ; 設定 taskcode 中第 3~4 byte 為分頁偏移位址
mov [task_data + 2], ax ; 設定 taskdata 中第 3~4 byte 為分頁偏移位址
shr eax, 0x10 ; 將 EAX 向右平移 16 個 bits
; 移去 AX([AH][AL]) 的內容
mov [task_code + 4], al ; 設定 taskcode 中第 5 byte 為分頁節段位址(低位元)
mov [task_code + 7], ah ; 設定 taskcode 中第 8 byte 為分頁節段位址(高位元)
mov [task_data + 4], al ; 設定 taskdata 中第 5 byte 為分頁節段位址(低位元)
mov [task_data + 7], ah ; 設定 taskdata 中第 8 byte 為分頁節段位址(高位元)
; 註:分頁數最多不超過 65535 (使用 2 bytes 的空間記錄分頁數)
mov eax, [ebp + s_task.taskPages] ; 設 EAX 為分頁數
mov [task_code], ax ; 設定 taskcode 中第 1~2 byte 為分頁數
mov [task_data], ax ; 設定 taskdata 中第 1~2 byte 為分頁數
mov [kernel_esp], esp ; 儲存核心目前的堆疊暫存器狀態到
; kernel_esp
; 將工作上一次執行時的暫存器狀態和旗標等設定,還原到目前的執行環境。
mov esp, [ebp + s_task.r_esp] ; 還原此工作的堆疊暫存器 ESP
mov ax, task_data - _GDT ; 計算 task_data 的偏移區段位址
; (參考 tables.asm 的結構)
mov ss, ax ; 設定 SS 堆疊區段為 AX
push DWORD [ds:ebp + s_task.r_eflags] ; 將此工作的旗標暫存器 EFLAG 存入堆疊
push DWORD (task_code - _GDT) ; 將 task_code 的偏移區段位址存入堆疊
; (參考 tables.asm 的結構)
push DWORD [ds:ebp + s_task.r_eip] ; 將此工作的指標暫存器 EIP 存入堆疊
; 目前堆疊內容:[r_eflags][task_code 的偏移區段位][r_eip]
mov eax, [ds:ebp + s_task.r_ebp] ; 設 EAX 為此工作的基底暫存器 EBP
push eax ; 將此工作的基底暫存器 EBP 存入堆疊
mov eax, [ds:ebp + s_task.r_eax] ; 還原此工作的累積暫存器 EAX
push eax ; 將累積暫存器 EAX 存入堆疊
; 目前堆疊內容:[r_eflags][task_code 的偏移區段位][r_eip][r_ebp][r_eax]
mov ebx, [ds:ebp + s_task.r_ebx] ; 還原此工作的基底暫存器 EBX
mov ecx, [ds:ebp + s_task.r_ecx] ; 還原此工作的計數暫存器 ECX
mov edx, [ds:ebp + s_task.r_edx] ; 還原此工作的資料暫存器 EDX
mov esi, [ds:ebp + s_task.r_esi] ; 還原此工作的來源索引暫存器 ESI
mov edi, [ds:ebp + s_task.r_edi] ; 還原此工作的目的索引暫存器 EDI
mov ax,task_data - _GDT ; 計算 task_data 的偏移區段位址
; (參考 tables.asm 的結構)
mov ds, ax ; 設資料區段 DS 為 AX
mov es, ax ; 設額外區段 ES 為 AX
pop eax ; 還原此工作的累積暫存器 EAX
pop ebp ; 還原此工作的基底暫存器 EBP
iretd ; 自堆疊取回 EIP,再取回 EFLAGS
; 轉移到該位址執行工作行程
後記
如同開頭所說的,完整的工作排程架構還涵蓋了 IRQ0 的計時器部份,而此部份程式碼於 irq_handlers.asm 之中,日後有空將再行註解。
另外,在這次 Schedule 系列函式中,我個人有一點小疑問在這行:
movzx eax, BYTE [eax]
在這行命令中只留下 EAX 最後 8bits(1 Byte) 資料,問題是為何要刻意取其中 8bits 出來呢?
就算沒有這行命令,我們還是可以正常的來計算記憶體偏移量,那此行命令不就多此一舉?希望知道的人能回應我的愚笨問題。
答案是:"Yes or No"
回覆刪除Orz Microkernel 的設計初衷是希望以最小的實做去體驗完整作業系統的功能,可在 scheduler 的程式碼窺其一二。然而,相當明顯的,就現有的設計來說,task count 被限制在極少的數量,取出其中 8bit 的操作其實就是一種 pre-optimization 途徑,畢竟所需處理的資料就只有那些。