發表文章

目前顯示的是有「作業系統實作」標籤的文章

從 Linux Kernel 出發看!探討 Process 組成結構!

對所有電腦使用者而言,執行一支應用程式就像吃蛋糕一樣簡單。對於程式開發者而言,寫支 Hello World Program 並跑起來,也是三十秒內可以完成的事。但是,這樣容易的動作,其實底層有著複雜的機制,否則短短十行程式碼的 Hello World 能夠動起來,真的是奇跡了。想要知曉程式是怎麼被作業系統執行,要追溯到 Process 的機制,在研究過 Linux Kernel 的 Process 相關機制後,一切就將明朗。 註:在本篇文章內,都將會假設 binary program 已經被作業系統解析並放到記憶體執行,說明只著重於 Process 的部份 。雖然 Process 與 binary program 和其載體息息相關,但本文不會討論 ELF 的細節,有興趣的讀者可以自己去尋找相關文件閱讀。 若你是程式開發者,對『Process(程序)』一詞應該不陌生。嚴格來說, Process 就是處於執行狀態的程式,如果參考一些原文書或英文文獻,它們也許會這樣定義:『Process is a program in execution』。所以我們可以認定,Process 的組成,是擁有著『程式執行檔的 binary code + 記錄資料的記憶體』。 本文將 Process 分成內外兩個角度來探討: 從 Process 內部,程式執行的觀點  從 Process 外部,也就是作業系統的觀點  程式執行的觀點 單純以 Process 內部的角度來看,Process 就是一般的程式碼被放到記憶體執行。曾於舊文『 Linux 下程序的記憶體映射 』略為提及,一支程式擁有著數個 segments(區段),並仰賴著這些 segment 來持續運作。大致上來說,每個 segment 有不同的用途: Code Segment - 存放主要程式 Data Segment - 存放已被初始化並賦予值的全域變數 BSS Segment - 紀錄尚未被賦予值的全域變數 Stack Segment(Stack/Heap) - 紀錄 Process 在執行時動態註冊的變數包括 function 中的 local variable 對程式本身來說,會動態並隨機使用的是 Stack Segment,程式向作業系統(OS) 要記憶體空間...

Linux Kernel Sendfile() 的提升 Server 效能之路

Apache 和 Samba 這類伺服器,主要以傳送檔案資料的工作為主,他們最常做的工作不外乎是開啟檔案(Open)、讀取(Read)、寫入網路連線(Write to Socket)。但是以 Kernel 的角度來說,這樣一個讀取檔案資料和傳送出去的流程相當繁複,並擁有最少兩次的 Kernel/User Space 資料搬移, 導致同一筆資料需要經過兩次多餘的複製。舉例來說,若一個檔案有 1MB,則 Linux Kernel 需要多做 2MB 的記憶體複製,使得效能經常消耗在這種地方,尤以 CPU 不夠快的平台上狀況特別明顯。 而過去曾有 khttpd 這樣的實作,讓 Linux Kernel 自成一個小型的 Web Server,提供一個極有效率方式的讀取靜態網站頁面和 Server 服務,其加速的方法,便是於 Kernel Space 讀取檔案並直接從網路連線送出資料,目的也在於減少 Kernel/User space 之間不必要的 context switch。 想瞭解 User Space 和 Kernel 的資料搬移狀況,我們可以來研究應用程式在讀取和傳送網路資料的流程,經簡化後大致上是(以下簡稱 User Space 為 US,Kernel Space 為 KS): [US] open() [KS] do_sys_open() [KS] do_filp_open() - 找到檔案,並從所在的檔案系統取得 struct file [KS] Return File Object [US] malloc() - 準備一塊記憶體當 buffer [US] read(file, buffer) [KS] vfs_read(file) - 標準 VFS 的檔案讀取 API [KS] file->f_op->read() - 使用資料所在的檔案系統(filesystem),其提供的低階 read 操作 [KS] copy_to_user(buffer) - 將檔案資料從硬碟讀出來後,複製一份到 user space 的 buffer 接著是將讀到的資料透過網路傳送出去: [US] write(Socket, buffer) - 將 buffer 內資料傳送出去 [KS] copy_from_user(buffer) -...

Linux 的 Real-time 排程支援

POSIX.1b 定義了一系列的系統呼叫,去提供即時(Real-time)需求的支援,實作細節和實際效能則由各作業系統自行負責。當然,Linux 也依循了這標準,讓各個 Process 有能力的在有限度的範圍內,調整自己的在系統上排程。這裡所講,並不只是一般常見的系統優先權機制,而是建構在其之上的進階排程實作。 首先,可以從 sched_getscheduler(pid_t pid) 的回傳值中,取得目前程式的排程方法,有助於了解當前的排程情形,其可能回傳值如下: SCHED_OTHER SCHED_FIFO SCHED_RR SCHED_BATCH 範例原始碼(sched_policy.c): #include <stdio.h> #include <sched.h> const char *sched_policy[] = { "SCHED_OTHER", "SCHED_FIFO", "SCHED_RR", "SCHED_BATCH" }; int main(int argc, char *argv[]) { printf("Scheduler Policy is %s.\n", sched_policy[sched_getscheduler(0)]); return 0; } Compiling it: gcc sched_policy.c -o sched_policy Results: Scheduler Policy is SCHED_OTHER 標準預設的排程方法是 SCHED_OTHER,意味 Kernel 並不會為這些一般性 Process 做特別的即時排程處理。因為,我們所寫的程式,都沒有被特殊設定,所得到的將都會是使用 SCHED_OTHER。 值得探討的是 SCHED_FIFO 和 SCHED_RR,這是為即時(Real-time)需求所設計的兩種排程類型,在運作規則上,其實兩者是同樣的東西,只是 SCHED_RR 擁有時段分配的制約機制。 SCHED_FIFO (First In-First Out) SCHED_FIFO 顧名思義就是『先進先出(First In First Out)』,一但 Procees 是 FI...

OS Kernel 相關閱讀與實作心得﹝一﹞

極慚愧的是,這次寒假的計劃並未完全如期完成,實作 OS Kernel 的計劃只進行到 IRQ 的部份。不過,自從最近拜讀《 即時多工核心程式設計 》之後,深深被其所影響,讓我想好好重新調整實作的步調並整理針對 OS Kernel 設計的心得。 這本大作將核心架構在 DOS 環境上,用 DOS 省去了讀者摸索低階的時間,並以 C++ 的邏輯表達和 Template 的優勢加上必要的 ASM coding 設計一個 pure 的即時多工核心。尤其以直接切入 Kernel 運作為主題的方式,讓讀者更能立即擁有享受 Hacking Kernel 的成就感。 一個多工 OS Kernel 應該具備的機制,大致如下: Task Context Switching/Scheduler IPC (Inter Process Communication) Semaphore 事實上, OS Kernel 基礎的結構本來就並不龐大、複雜,這與『 microkernel 』的定義指標極為相似。而且,只存在該有的 Task Handler 和各工作 Connection 的機制,也容易理解與設計。至於其它的 Hardware Manager 等功能,其實並不需要在第一時間就去規劃,依據需求再決定於 Kernel Mode 或 User Mode 之下實作就可以了。 【Task Context Switching/Scheduler】 除了 Context Switching 會無可避免的消耗 CPU 資源之外,在 Task 控制的規劃上,有著兩種模式,可視 OS 用途而定: Preemptive Non-preemptive Preemptive 即為強佔式〔強取式〕核心,在 Scheduler 被觸發時,優先權高的 Task 將會搶去優先權低的排隊位置,取得先執行的權力。此方式的系統回應快,對於 Realtime OS 是非常必要的技術。Linux 在 2.6 版後就加入了這項技術,以提升需要 Realtime 的多媒體效能。 Non-preemptive 則是無論如何,每個 Task 用完自己的時間後交出使用權利,照順序一個個 Task 執行,對於優先權高的 Task 來說也不能不遵守此規則,要等到自己的時間到來才可以執行,故可能造成 Task 無法即時回應使用者的需求。此種核心適合...

經典著作《即時多工核心程式設計》

日前 Jserv 康慨借我一本經典著作《即時多工核心程式設計》,於是在寒假最後的空閒時間先快速看過了一遍。真不愧是 Jserv 大力推薦的好書,作者將多工核心的藍圖具體而詳盡的勾勒出來,短短數個章節已將重要細節交代非常清楚,由於該書的範例說明用 C++ 來撰寫表達,結構與觀念也都非常易懂,重複閱讀了幾遍也能品嘗出不同細節的奧妙之處。這本經典著作的模樣可以在 Jserv's blog 找到: http://blog.linux.org.tw/~jserv/archives/001258.html 該書於 1995 年出版,所附 Floppy 早就發霉爛掉了,不過有 Jserv 大神的筆記和筆跡,想必比那片張 Floppy 更值不少錢吧!〔笑〕 Jserv 說這本書在某些書店架上還可以看得到,有興趣的人可以去找找看。

OrzMicrokernel main.asm 核心啟動主結構

作業系統核心的運作分兩個階段,第一個階段是初始化,第二個階段是規律的活下去。從第一階段到第二階段的流程和主結構都清楚地寫在 main.asm,而工作排程、記憶體規劃等設計都是由此再延伸出去。 main.asm 原始碼: %include "defines.asm" %include "orzmicro.inc" %include "16bit.asm" %include "32bit.asm" ;----------------------------------------------------------------------------------- ; Now, we are under 32 bit protected mode. Starting... mov al,1 ; 設定為 320x200 的圖形模式 call SetVideoMode ; 呼叫 video.asm 的子程式設定顯示模式 call InitTasks ; 呼叫 tasks.asm 的子程式初始化工作狀態記錄表 mov eax, Ts1 + 0x10 ; 設 EAX 為工作記憶體開始位址 mov ebx, Ts2 - Ts1 - 0x10 ; 設 EBX 為工作所佔記憶大小 mov ecx, [Ts1 + 8] ; 設 ECX 為工作二進位檔的第 8 個 bytes 所設定的堆疊大小 mov edx, [Ts1 + 12] ; 設 ECX 為工作二進位檔的第 12 個 bytes 所設定的資料大小 mov esi, ts1Name ; 設 ESI 為工作名稱 call CreateTask ; 呼叫 tasks.asm 的 CreateTask 子程式建立工作 mov eax, Ts2 + 0x10 ; 設 EAX 為工作記憶體開始位址 mov ebx, Ts3 - Ts2 - 0x10 ; 設 EBX 為工作所...

OrzMicrokernel tasks.asm 尋找工作函式註解

到目前為止,從 Orz Microkernel 運作流程來看,我們已經看過主要的程式碼了,剩下的部份就只有些零零星星的函式和各種服務程式的實作。其中最重要的就是 Orz Microkernel 自訂的 IPC 訊息中斷服務(Message Service),該服務會運用到工作處理方面的函式,所以現在先將工作相關的函式註解補齊。 程式碼和詳細註解: ;------------------------------------------------------------------------------ ; 尋找工作 FindTask ;;;; 輸入值: EAX = 工作編號 ID ;;;; 返回值:若進位旗標 CF=1 則表示找不到工作,否則 EAX = 工作狀態編號,EBP = 工作狀態指標 ;;;; 會影響到的暫存器:EAX, ECX, ESI, EBP FindTask: mov ecx, [tasksCount] ; 設 ECX 為工作總數量 jecxz FindTask1 ; 若 ECX 為零,則跳到 FindTask1 mov esi, tasksList ; 設 ESI 為工作清單的記憶體位址 FindTask0: movzx ebp, BYTE [esi] ; 從 ESI 取 2 Bytes(工作編號)到 EBP imul ebp, s_task.msize ; EBP = EBP x s_task.msize add ebp, tasks ; EBP = EBP + 工作狀態記錄所在記憶體位址 cmp eax, [ebp + s_task.taskID] ; 若 EAX = s_task.taskID 則零值旗標 ZF = 0 jz FindTask2 ; 若零值旗標 ZF = 0 則跳到 FindTask2 inc esi ; ESI 加一 loop FindTask0 ; 迴圈 ECX 次,繼續尋...

OrzMicrokernel irq_handlers.asm IRQ處理和操作系統計時器 IRQ0 的函式註解

irq_handlers.asm 是 Orz Microkernel 用來處理 IRQ 的函式檔案,裡面有著處理所有 IRQ 的程式碼。之前工作排程器的部份有提到,定時切換工作是靠 IRQ0 的系統計時器達成的,其原理是當工作被賦予執行權時,同時也被授權了能佔用 CPU 的時間長度,接著,再靠著系統計時器的功能倒數計時。最後,該工作能佔用 CPU 的時間被用完,系統計時器就會如同鬧鐘響起一般,去呼叫 tasks.asm 的 Schedule 準備切換到下一個工作行程。 程式碼和詳細註解: ;------------------------------------------------------------------------ ; IRQ 是處理硬體中斷請求的通道,硬體可透過 IRQ 要求 CPU 產生中斷,以處理硬體的 ; 相關操作。 ; ; IRQ 在中斷(INT)的編號定義: ; 08 Hardware IRQ0 System Timer ; 09 Hardware IRQ1 Keyboard ; 0A Hardware IRQ2 Redirected ; 0B Hardware IRQ3 Serial Comms. COM2/COM4 ; 0C Hardware IRQ4 Serial Comms. COM1/COM3 ; 0D Hardware IRQ5 Reserved/Sound Card ; 0E Hardware IRQ6 Floppy Disk Controller ; 0F Hardware IRQ7 Parallel Comms. ; 70 Hardware IRQ8 Real Time Clock ; 71 Hardware IRQ9 Redirected IRQ2 ; 72 Hardware IRQ10 Reserved ; 73 Hardware IRQ11 Reserved ; 74 Hardware IRQ12 PS/2 Mouse ; 75 Hardware IRQ13 Math...

OrzMicrokernel tasks.asm 工作排程器的切換執行權函式註解

工作新增、載入後,一切都準備就緒了,main.asm 最後一個任務,就是讓作業系統的心臟:『工作排程器』開始跳動,使所有工作能夠生生不息的運轉。Orz Microkernel 利用了 Schedule 這個 Function 切換到工作清單中的下一個工作,並利用 IRQ_0 的計時器,每一定單位時間就切換一次,使所有工作如同同時執行一般。 原始程式碼和詳細註解: ;-------------------------------------------------------------------------- ; 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 ...

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

因為每天花在 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] ...

OrzMicrokernel tasks.asm 初始化多工機制和工作狀態的資料結構

main.asm 在設定完顯示模式後,緊接著就是開始初始化多工機制。我們將配置一塊記憶體來存放所有工作的名稱、執行狀態﹝包括各種暫存器的內容﹞,在 Orz Microkernel 中,工作數量被限制為 64 個,它被定義在 define.asm 裡頭。 程式碼和詳細註解: InitTasks: mov ecx, MAX_TASKS ; 設 ECX 為最大工作數量﹝定義在 define.asm﹞ mov ebp, tasks ; 設定工作狀態記錄所在位址﹝定義在tables.asm﹞ xor eax, eax ; 將 EAX 歸零 InitTasks0: mov [ebp + s_task.taskID], eax ; 初始化工作狀態記錄編號 add ebp, s_task.msize ; 移到下一個工作狀態記錄的開頭 loop InitTasks0 ; 重覆工作狀態記錄初始化 ecx 次 ret 從上面程式碼看到的 s_task,是工作狀態記錄的資料結構,它被定義在 task.asm 的開頭: ; 使用虛擬指令 RESB、RESD 宣告不含初值的資料結構 s_task struc s_task .taskID: resd 1 ; 工作編號 Dword(2 Bytes) .state: resb 1 ; 工作狀態 Dword(1 Byte) .newTick: resd 1 ; need for state=STATE_SLEEP .queue: resd (MAX_TASKS + 1) .qFirst: resd 1 .qLast: resd 1 .s_taskID: resd 1 .s_message: resd 1 .s_dataPtr: resd 1 .s_...

OrzMicrokernel video.asm 切換顯示模式的註解

依照 Orz Microkernel 在 main.asm 的核心啟動流程來看,在轉換到保護模式並做了最基本的初始化之後,就會 call SetVideoMode 來切換顯示模式到 320x200。關於這部份,比較複雜的是牽涉到 BIOS 中斷,由於在保護模式之下無法使用 BIOS INT,所以特別額外在 realmode.asm 寫了在保護模式下處理此類中斷的呼叫函式﹝關於 realmode.asm 的註解日後補上﹞。 程式碼和詳細註解: ;------------------------------------------------------------------------------ ; 文字模式﹝80x25,16色﹞:AL=0 ; 圖形模式﹝320x200,256色﹞:AL=1 SetVideoMode: and al, al jnz SetVideoMode0 ; 若 AL 不為 0 則呼叫 SetVideoMode0 mov [rmode_ax], WORD 0x03 ; 文字模式 mov al, 0x10 ; 中斷 10H ; 在保護模式下舊有的中斷向量表已被 IDT 代替 ; 導致 BIOS 中斷在保護模式下無法使用 ; 故 BIOS 中斷得在真實模式執行 ; 因此特別在 realmode.asm 中設計一個處理真實模式中斷的函式 jmp RmodeInt ; 呼叫在 realmode.asm 的 RmodeInt 處理中斷 SetVideoMode0: dec al ; AL=AL-1 jnz SetVideoMode1 ; 若 AL 不為 0 則呼叫 SetVideoMode1 返回 mov [rmode_ax], WORD 0x13 ; 圖形模式 mov al, 0x10 ; 中斷 10H jmp RmodeInt ; 呼...

OrzMicrokernel bit32.asm 進入保護模式後的核心初始化

OrzMicrokernel 在 bit16.asm 完成了轉換成保護模式的工作,緊接著會跳到 bit32.asm 初始化核心和設定硬體中斷周期。最重要的是,從跳轉到這檔案後,就可以開始使用 32 位元的各種延伸暫存器以及更大的記憶體定址,但前題是:我們得先將新的暫存器區段位址設定好,因為暫存器設定還是處於在真實模式時的狀態。 程式碼和詳細註解: [BITS 32] ; Intel 處理器的設計是以 dword 為單位來存取記憶體,所以記憶體位址定為 4 的倍數。 align 4 ; 設定所有資料的起始位址都是 4 的倍數 Begin32c: ; 初始化暫存器區段 mov ax, kernel_data - _GDT ; 從 tables.asm 的 kernel_data 開始 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0x2FFFF ; 堆疊暫存器設在 2MB 記憶體後 call TestMemory ; 呼叫 mm.asm 的子程式測試記憶體 mov edi, memMap ; 初始化索引暫存器 ; memMap 記憶體大小有0x20000=128KB mov ecx, 0x100 / 32 ; 計數器設為 256/32=8 xor eax, eax ; 將 eax 歸零 dec eax rep stosd ; 每次從 EAX 中複製 4 Bytes 到 EDI ; 複製 8 次 mov ecx, 0x2000...

OrzMicrokernel bit16.asm 核心前置動作並轉換到保護模式的註解

bit16.asm 是 OrzMicrokernel 從真實模式轉換到保護模式的部份。保護模式是作業系統必要的運作環境,因為保護模式支援了 4GB 的實體記憶體定址、可擴充的記憶體分頁機制、多重任務機制、特權級等多工作業系統會用到的各種機制。 詳細程式碼和註解: [BITS 16] [ORG KERNEL_START] Start: ; 初始化暫存器區段 mov ax, cs mov ds, ax mov es, ax mov ss, ax ; 初始化堆疊區段 mov sp, 0x0000 ; 堆疊指標暫存器歸零 ; 準備開始切換到保護模式 cli ; 禁用中斷以免干擾初始化 cld ; 設定字串處理時由低住址往高位址 call PrepareIRQs ; 呼叫 irqs.asm 的副程式以初始化 IRQ ; A20 位址線控制器: ; 早期 8086 的 CPU 只有 20 bits 的位址匯流排,只能定址到 1MB 的記憶體 ; 因此,如果試圖存取超過 1MB 的記憶體,將會回到位址零重新定位循環下去 ; 所以,要打開 A20 位址線,讓電腦可以定址大於 1MB 的記憶體 call A20En ; 呼叫 irqs.asm 的副程式 ; 以打開 A20 位址線 lgdt [_GDT - KERNEL_START] ; 載入 GDT:_GDT 在 tables.asm lidt [_IDTR - KERNEL_START] ; 載入 IDT:_IDTR 在 tables.asm ; 切換到保護模式 ; 切換成保護模式的方法:將 CR0 的 PE ...

OrzMicrokernel boot.asm 啟動磁區定義和程式初始化註解

OrzMicrokernel 的 boot.asm 是針對 1.44 MB 磁碟片所設計的,採用的是 FAT12 ﹝注意不是 FAT16 或 FAT32﹞的檔案系統,所以,在啟動磁區的部份的定義上比較簡單且單純。另外,這次註解的內容主要是程式一開始的部份,包括初始化、在螢幕上顯示一段文字等。 程式碼與詳細註解: Base: jmp short Begin ; 從 Begin 執行 nop ; 空執行 ; 啟動磁區格式 oem db 'OrzMicro' ; 廠商軟體名 bytesPerSector dw 0x0200 ; 設每磁區的大小為 512 Bytes sectPerCluster db 0x02 ; 每磁叢的磁區數 resSectors dw 0x01 ; FAT 前的磁區數 ; ﹝開機記錄所佔用的磁區數﹞ fatCopies db 0x02 ; FAT 表總數 rootEntCnt dw 0x0070 ; 根目錄的最大檔案數 totalSectors dw 0x05A0 ; 邏輯磁區總數 media db 0xF9 ; 磁碟種類 sectPerFat dw 0x0003 ; 每個 FAT 表的磁區數 sectPerTrack dw 0x0009 ; 每磁軌的磁區數 heads dw 0x0002 ; 磁頭數﹝面數﹞ hiddenSectors dd 0 ; 隱藏磁區數 sectorHuge dd 0 ; 保留 4 Bytes ...

OrzMicrokernel boot.asm 磁碟內容參數的詳細註解

這是 OrzMicrokernel 裡 boot.asm 磁碟內容參數的部分,這部份比較直觀且不涉及硬體裝置的操作和定義,只要了解磁碟的資料配置結構,很容易就能了解其中的運作。 程式碼包括詳細註解: ;----------------------------------------------- ; 計算各項磁碟內容參數 ; ; 計算磁區位置: ; 1. FAT表磁區 ; 2. 根目錄磁區 ; 3. 資料磁區 ; ; 計算 FAT 檔案系統資訊: ; 1. FBD 所佔磁區數量 ;----------------------------------------------- ; ; 磁碟資料結構:[開機記錄磁區][隱藏磁區][FAT表磁區][根目錄磁區][資料磁區] ; [FAT表磁區] 是由許多 FAT 表所構成 ; [根目錄磁區] 是由許多 FBD 所構成 ; FBD(檔案描述區塊):大小為 32Bytes ; 每個檔案都有自己的 FBD,記錄檔案名稱、建檔日期、檔案大小等資料 ; 計算 FAT 的開始位置 ; 方法:跳過隱藏磁區和開機磁區 ; hiddenSectors = [高位元 2Bytes][低位元 2Bytes] mov si,[hiddenSectors] ; 低位元給 SI mov di,[hiddenSectors+2] ; 高位元給 DI add si,[resSectors] ; 跳過隱藏磁區加開機記錄所佔用的磁區數 adc di,0 ; DI:SI = 第一個 FAT 表的開始位置 mov [fatStart],si ; 用 fatStart 保存 FAT 的開始位置 mov [fatStart+2],di ; 計算根目錄磁區的開始位置 ; 方法:跳過 FAT 表磁區 ; FAT 表的總數 x 每個表的磁區數 = 所有 FAT 表的磁區總數 mov al,[fatCopies] ; AL = FAT 表總數 xo...

OrzMicrokernel 陪我度過颱風天的阿宅生活

最近幾次少數能以『強烈的颱風』身份影響並登陸台灣的,大概就屬這次的颱風了。還沒登陸,風就已經重擊了家裡的窗戶,呼呼砰砰聲搞得我醒著做事覺得煩、睡也睡不好。前兩天晚上去逛敦南誠品,在去的路上,風狂野的在短短一個街口,就把我的雨傘給吹成奇形怪狀﹝比翻過來還可怕﹞,後來回家時只好淋著雨一身濕答答的走回停車處。在被風玩弄之後,我就決定這兩天不出門了,弄得自己閒得發荒,一直在家當阿宅 Orz。 我突然想起前陣子 Jserv 寫的 OrzMicrokernel,一直沒能好好的找個時間去讀它,所以,藉著這次颱風過境,就好好品嘗一下了﹝笑﹞。一切果然如同大家所說, OrzMicrokernel 真的是簡潔有力,是一個作業系統設計的優良範例,它的設計非常直觀易懂;可是我也有聽到一群朋友,看得要死還是看不大懂 OrzMicrokernel 。搞半天,原來他們對組合語言並不是說非常熟悉,還有對磁碟 LBA、CHS、FAT12 等等這種格式定義很陌生!不過也難怪,大多人平時寫程式都太高階了,早就忘了電腦是怎麼動起來的。 尋思如果很多想了解的人,都卡在對組合語言的一點不熟悉和資料結構定義這種問題上,導致無法踏入 OrzMicrokernel 的話,那真的是太可惜了。所以,無聊到極點的我,就決定開始幫 OrzMicrokernel 逐行中文註解,希望從組合語言指令用法開始加註說明,還有說明重要的資料結構﹝註解寫起來比程式碼還多行,驚!﹞。為了說明的完全清晰正確,我還重新查資料驗證自己的觀念,生怕有一點遺漏。 以下是 boot.asm 讀取磁區的註解片段: ; DX:AX = LBA 磁區位置, ES:BX = 緩衝區位址 ReadSector: ; 磁區 CHS 表示法 ; 磁碟和磁片的記錄方式『不是』一整面的磁區用完再換面儲存,而是以空心圓柱狀的方式。 ; 因為磁片只有簡單兩面,以磁片為例: ; 正確的磁片資料記錄順序為:第零面第零軌、第一面第零軌、第零面第一軌、第一面第一軌... ; 每面的同一軌都用滿後才換下一個磁軌紀錄資料,形狀如同空心圓柱,故稱磁柱(cylinder)。 ; 因此以磁片來說,第 0 面第 0 軌和第 1 面第 0 軌就是第 0 磁柱 ; 磁區 LBA 表示法 ; LBA 編號法是不考慮磁區的實...

PC 作業系統實作馬上入門

最近在整理過去的許多筆記,看到了很多懷念的東西,尋思當初許多的瓶頸,都缺少一個人幫我打通任督二脈,讓我一路走起來倍感艱辛,雖然不後悔這樣沒章法的強幹式學習,但許多冤枉路我心裡明白是白走了,尤其在作業系統這部份,對大多數人來說,更是不易突破的關卡。因為,看程式方面的書籍它不會教你,看硬體架構的書又不告訴你程式實作,如果想自己融會貫通,又因為沒有良好的入門範例,不知道該注意哪些事項而挫折連連。 開始前,你要有個體認,我們要寫的是作業系統,整台電腦都將由我們的程式完全控制,所以,如果你的程式不正常,將會落得停格當機下場,沒有『程式關閉,請聯絡...』或是『Core...』這套。另外,不要想說有任何 API 可以用,你都要一一自己打造這一切,因為系統開發本來就是一件辛苦的事,你正在開拓一個沙漠的綠洲。 而大多數人在學習作業系統撰寫時,常都會卡在第一步:我要如何讓自己的作業系統啟動?然後如同第一次學寫程式時,出現一行 Hello World?當初的我也在這問題上打轉好一陣子,縱使我會組合語言、有過不少寫程式經驗、看過許許多多的相關書籍,但我還是不知道該從哪開始。 我們先來了解電腦啟動的流程: 電腦開啟電源,會啟動 ROM 裡面的 BIOS 執行硬體檢查和初始化。 BIOS 會依序搜尋 BIOS 設定中的啟動裝置(Boot Device),如硬碟。 一旦在某個啟動裝置上發現了啟動磁區(Boot Sector 大小為 512 Bytes),就會將此磁區 512 Bytes 的程式載入到記憶體位址 0000:7c00 BIOS 會跳到記憶體位址 0000:7c00 執行被載入的程式,並將執行權全權交給它。 以下是一個 512 Bytes 的簡單開機程式實作範例﹝組合語言﹞: org 07c00h ;此程式將被載入到 0000:7c00 執行 mov ax, cs mov ds, ax mov es, ax call Disp ;呼叫 Disp 顯示字串 jmp $ ;無限回圈 Disp: mov ax, BootMsg mov bp, ax ;指定 BootMsg ...