2007年10月22日 星期一

OrzMicrokernel tasks.asm 建立新工作函式註解

Standard
因為每天花在 Orz Microkernel 上的時間不多,而且這次的程式片斷比較長,所以讓我多花了幾天的時間做整理,直到現在才大約有個較完整的註解。繼續依順續閱讀 main.asm 中可以看到,在工作初始化多工機制和工作狀態的資料結構之後,就開始連續載入並新增 6 個工作到我們的工作清單中,main.asm 呼叫 tasks.asm 裡的 CreateTask 函式來新增工作。

以下是程式碼和詳細註解:
;--------------------------------------------------------------------------------------
; 建立新的工作 CreateTask
;;;; 輸入值: EAX = 工作的開始位址, EBX = 工作所佔的記憶體大小, ECX = 工作的堆疊大小
;;;; EDX = 工作的資料大小, ESI = 工作名稱指標
;;;; 返回值:若進位旗標 CF=1 則表示建立新工作失敗,否則 EAX = 工作編號
;;;; 會影響到的暫存器:EAX, EBX, ECX, EDX, ESI, EDI, EBP

CreateTask:
mov [taskNamePtr], esi ; 設 taskNamePtr 為工作的名稱
mov [taskBegin], eax ; 設 taskBegin 為工作的開始位址
mov [copySize], ebx ; 設 copySize 為工作所佔的記憶體大小
add ebx, ecx ; EBX 加工作的堆疊大小
add ebx, edx ; EBX 加工作的資料大小
mov [taskSize], ebx ; taskSize = 所佔記憶體 + 堆疊大小 + 資料大小

mov ebx, [tasksCount] ; 設定 EBX 為目前工作數量
cmp ebx, MAX_TASKS ; 比較工作數量是否已達最大值
jc CreateTask0

stc ; 設進位旗標 CF = 1
ret

CreateTask0:
mov esi, tasksList ; 工作清單的記憶體位址
add esi, ebx ; 位移 EBX

mov ebp, tasks ; 設定 EBP 為工作狀態記錄所在記憶體位址
xor cl, cl ; CL 歸零
xor edx, edx ; EDX 歸零

CreateTask1:
cmp [ebp + s_task.taskID],edx ; 比對工作編號
jz CreateTask2 ; 工作編號已經存在

add ebp, s_task.msize ; 移到下一個工作狀態記錄的記憶體開始處
inc cl ; CL 加一
jmp short CreateTask1 ; tasksCount < MAX_TASKS

CreateTask2:
; 初始化工作的各種暫存器、堆疊等狀態
mov [ebp + s_task.r_eip], edx ; 初始化指標暫存器﹝工作目前執行到的位置﹞
mov [ebp + s_task.r_eax], edx ; 初始化 EAX 累積暫存器
mov [ebp + s_task.r_ebx], edx ; 初始化 EBX 基底暫存器
mov [ebp + s_task.r_ecx], edx ; 初始化 ECX 計數暫存器
mov [ebp + s_task.r_edx], edx ; 初始化 EDX 資料暫存器
mov [ebp + s_task.r_esi], edx ; 初始化 ESI 來源索引暫存器
mov [ebp + s_task.r_edi], edx ; 初始化 EDI 目的索引暫存器
mov [ebp + s_task.r_ebp], edx ; 初始化 EBP 基底指標暫存器
mov [ebp + s_task.state], BYTE STATE_RUNNING ; 設定工作執行狀態

push esi ; 將 ESI 存入堆疊
push ecx ; 將 ECX 存入堆疊
mov eax, [taskSize] ; 設 EAX 為工作記憶體大小
call SizeToPages ; 計算工作所佔記憶體分頁數
mov [ebp + s_task.taskPages], ecx ; 將分頁數儲存至工作狀態記錄的資料結構中
push ebp ; 將 EBP 存入堆疊
call AllocPages ; 呼叫 mm.asm 的子程式初始化記憶體分頁
pop ebp ; 從堆疊取回工作狀態記錄所在記憶體位址
pop ecx ; 從堆疊取回工作的堆疊大小
pop esi ; 從堆疊取回工作清單的記憶體位址
jnc CreateTask3

stc ; 設進位旗標 CF=1
ret

CreateTask3:
mov [esi], cl ; 設定 ESI 為工作的堆疊大小
inc DWORD [tasksCount] ; 工作總數量加一

lea esi,[ebp + s_task.queue] ; 將 ESI 指向 s_task.queue
mov [ebp + s_task.qFirst], esi ; 將 s_task.qFirst 指向 ESI
mov [ebp + s_task.qLast], esi ; 將 s_task.qLast 指向 ESI

mov [ebp + s_task.taskPageAddr], eax ; 設 s_task.taskPageAddr 為工作記憶體分頁位址

mov eax, [ebp + s_task.taskPages] ; 設 EAX 為分頁數
imul eax, 0x1000 ; EAX = 分頁數 x 4096
dec eax ; EAX = EAX - 1
mov [ebp + s_task.r_esp], eax ; 將分頁數設定到工作的堆疊暫存器(從零開始算)

xor eax,eax ; 清除 ZF 零值旗標
clc ; 清除 CF 進位旗標
sti ; 設 IF 中斷旗標等於一
pushfd ; 將所有暫存器內容存入堆疊
cli ; 設 IF 中斷旗標等於零
pop eax ; 從堆疊中取回 EAX 值
; (*)
mov [ebp + s_task.r_eflags], eax

mov esi, [taskBegin] ; 設 ESI 為工作的開始位址
mov edi, [ebp + s_task.taskPageAddr] ; 設 EDI 為工作記憶體分頁位址
mov ecx, [copySize] ; 設 ECX 為工作所佔的記憶體大小
rep movsb ; 將工作的資料從 ESI 複製到 EDI 工作記憶體分頁位址

lea edi, [ebp + s_task.taskName] ; 將 EDI 指向 s_task.taskName
mov esi, [taskNamePtr] ; 設 ESI 為工作名稱(位於 taskNamePtr 的內容)
mov ecx, MAX_TASK_NAME ; 設 ECX 為工作名稱的最大字數

; 將工作名稱從 ESI(taskNamePtr) 複製到 EDI(s_task.taskName)
CreateTask4:
lodsb ; 從 ESI 複製 1 Byte 到 EAX(AL)
and al, al ; 判斷此 Byte 是否為空值
jz CreateTask5 ; 如果 AL 為零則跳到 CreateTask5
stosb ; 如果 AL 有值則從 EAX 複製 1 Byte 到 EDI(s_task.taskName)
loop CreateTask4 ; 重覆執行複製直到 AL 為空值
xor al, al ; 若工作名稱超過最大字數,設零值旗標 ZF=1 、同位旗標 PF=1
CreateTask5:
stosb ; 從 EAX 複製 1 Byte 空字元『\0』到 EDI(s_task.taskName)

mov eax, [currentID] ; 設 EAX 為目前工作編號
mov [ebp + s_task.taskID], eax ; 設 s_task.taskID 為 EAX
inc eax ; 目前工作編號加一
; EAX!=0 PID
mov [currentID],eax ; 設定下一個工作編號

clc ; 清除 CF 進位旗標
ret

taskBegin dd 0x12345678
taskSize dd 0x12345678
copySize dd 0x12345678
taskNamePtr dd 0x12345678


後記

tasks.asm 的程式比較長,牽涉到的資料結構也比較雜,但這是多工作業系統的命脈。另外一提,在 CreateTask3 裡從 lea esi,[ebp + s_task.queue] 開始的三行程式碼,應該要有更詳細的說明,但這次主題是在建立工作,所以其中牽涉到更多完整工作佇列運作的內容,就暫且不多加解釋,待日後有機會再回頭重新詳解。