跳到主要內容

讓作業系統走進保護模式

一個作業系統從開機到開始執行,大致經歷「開機→載入核心至記憶體→跳入保護模式→開始執行核心」的過程。

是故,交由開機磁區處理「載入核心與準備保護模式」的工作,顯然會有超過512位元組之問題。因此,可將這個過程交給 Loader 處理。


而開機磁區負責把Loader 加載至記憶體,並將控制權交給 Loader,其它工作放心地交給 Loader 處理,可有效避免 512 位元組之限制。



程式碼

boot.asm

;%define _BOOT_DEBUG_ ; 做 Boot Sector 時一定將此行註解掉!將此行打開後用 nasm Boot.asm -o Boot.com 做成一個.COM檔案易於測試

%ifdef _BOOT_DEBUG_
 org  0100h   ; 測試狀態, 做成 .COM 檔案, 可測試
%else
 org  07c00h   ; Boot 狀態, Bios 將把 Boot Sector 加載到 0:7C00 處並開始執行
%endif

;================================================================================================
%ifdef _BOOT_DEBUG_
BaseOfStack  equ 0100h ; 測試狀態下堆疊基位址(堆疊底, 從這個位置向低位址生長)
%else
BaseOfStack  equ 07c00h ; Boot狀態下堆疊基位址(堆疊底, 從這個位置向低位址生長)
%endif

BaseOfLoader  equ 09000h ; LOADER.BIN 被加載到的位置 ----  段位址
OffsetOfLoader  equ 0100h ; LOADER.BIN 被加載到的位置 ---- 偏移位址

RootDirSectors  equ 14 ; 根目錄占用空間
SectorNoOfRootDirectory equ 19 ; Root Directory 的第一個磁區號
SectorNoOfFAT1  equ 1 ; FAT1 的第一個磁區號 = BPB_RsvdSecCnt
DeltaSectorNo  equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
     ; 檔案的開始Sector號 = DirEntry中的開始Sector號 + 根目錄占用Sector數目 + DeltaSectorNo
;================================================================================================

 jmp short LABEL_START  ; Start to boot.
 nop    ; 這個 nop 不可少

 ; 下面是 FAT12 磁碟的頭
 BS_OEMName DB 'ForrestY' ; OEM String, 必須 8 個字元
 BPB_BytsPerSec DW 512  ; 每磁區字元數
 BPB_SecPerClus DB 1  ; 每叢集多少磁區
 BPB_RsvdSecCnt DW 1  ; Boot 記錄占用多少磁區
 BPB_NumFATs DB 2  ; 共有多少 FAT 表
 BPB_RootEntCnt DW 224  ; 根目錄檔案數最大值
 BPB_TotSec16 DW 2880  ; 邏輯磁區總數
 BPB_Media DB 0xF0  ; 媒體描述符
 BPB_FATSz16 DW 9  ; 每FAT磁區數
 BPB_SecPerTrk DW 18  ; 每磁道磁區數
 BPB_NumHeads DW 2  ; 磁頭數(面數)
 BPB_HiddSec DD 0  ; 隱藏磁區數
 BPB_TotSec32 DD 0  ; 如果 wTotalSectorCount 是 0 由這個值記錄磁區數
 BS_DrvNum DB 0  ; 中斷 13 的驅動器號
 BS_Reserved1 DB 0  ; 未使用
 BS_BootSig DB 29h  ; 擴展引導標記 (29h)
 BS_VolID DD 0  ; 卷序列號
 BS_VolLab DB 'OrangeS0.02'; 卷標, 必須 11 個字元
 BS_FileSysType DB 'FAT12   ' ; 檔案系統類型, 必須 8個字元

LABEL_START:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ss, ax
 mov sp, BaseOfStack

 ; 清屏
 mov ax, 0600h  ; AH = 6,  AL = 0h
 mov bx, 0700h  ; 黑底白字(BL = 07h)
 mov cx, 0   ; 左上角: (0, 0)
 mov dx, 0184fh  ; 右下角: (80, 50)
 int 10h   ; int 10h

 mov dh, 0   ; "Booting  "
 call DispStr   ; 顯示字元串

 xor ah, ah ; ┓
 xor dl, dl ; ┣ 軟體驅動復位
 int 13h ; ┛

; 下面在 A 碟的根目錄尋找 LOADER.BIN
 mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
 cmp word [wRootDirSizeForLoop], 0 ; ┓
 jz LABEL_NO_LOADERBIN  ; ┣ 判斷根目錄區是不是已經讀完
 dec word [wRootDirSizeForLoop] ; ┛ 如果讀完表示沒有找到 LOADER.BIN
 mov ax, BaseOfLoader
 mov es, ax   ; es <- BaseOfLoader
 mov bx, OffsetOfLoader ; bx <- OffsetOfLoader 於是, es:bx = BaseOfLoader:OffsetOfLoader
 mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 號
 mov cl, 1
 call ReadSector

 mov si, LoaderFileName ; ds:si -> "LOADER  BIN"
 mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
 cld
 mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
 cmp dx, 0          ; ┓循環次數控制,
 jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣如果已經讀完了一個 Sector,
 dec dx           ; ┛就跳到下一個 Sector
 mov cx, 11
LABEL_CMP_FILENAME:
 cmp cx, 0
 jz LABEL_FILENAME_FOUND ; 如果比較了 11 個字元都相等, 表示找到
dec cx
 lodsb    ; ds:si -> al
 cmp al, byte [es:di]
 jz LABEL_GO_ON
 jmp LABEL_DIFFERENT  ; 只要發現不一樣的字元就表明本 DirectoryEntry 不是
; 我們要找的 LOADER.BIN
LABEL_GO_ON:
 inc di
 jmp LABEL_CMP_FILENAME ; 繼續循環

LABEL_DIFFERENT:
 and di, 0FFE0h      ; else ┓ di &= E0 為了讓它指向本項目開頭
 add di, 20h       ;     ┃
 mov si, LoaderFileName     ;     ┣ di += 20h  下一個目錄項目
 jmp LABEL_SEARCH_FOR_LOADERBIN;    ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
 add word [wSectorNo], 1
 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
 mov dh, 2   ; "No LOADER."
 call DispStr   ; 顯示字元串
%ifdef _BOOT_DEBUG_
 mov ax, 4c00h  ; ┓
 int 21h   ; ┛沒有找到 LOADER.BIN, 回到 DOS
%else
 jmp $   ; 沒有找到 LOADER.BIN, 死循環在這裡
%endif

LABEL_FILENAME_FOUND:   ; 找到 LOADER.BIN 後便來到這裡繼續
 mov ax, RootDirSectors
 and di, 0FFE0h  ; di -> 當前項目的開始
 add di, 01Ah  ; di -> 首 Sector
 mov cx, word [es:di]
 push cx   ; 保存此 Sector 在 FAT 中的序號
 add cx, ax
 add cx, DeltaSectorNo ; cl <- LOADER.BIN的起始磁區號(0-based)
 mov ax, BaseOfLoader
 mov es, ax   ; es <- BaseOfLoader
 mov bx, OffsetOfLoader ; bx <- OffsetOfLoader
 mov ax, cx   ; ax <- Sector 號

LABEL_GOON_LOADING_FILE:
 push ax   ; `.
 push bx   ;  |
 mov ah, 0Eh   ;  | 每讀一個磁區就在 "Booting  " 後面
 mov al, '.'   ;  | 打一個點, 形成這樣的效果:
 mov bl, 0Fh   ;  | Booting ......
 int 10h   ;  |
 pop bx   ;  |
 pop ax   ; /

 mov cl, 1
 call ReadSector
 pop ax   ; 取出此 Sector 在 FAT 中的序號
 call GetFATEntry
 cmp ax, 0FFFh
 jz LABEL_FILE_LOADED
 push ax   ; 保存 Sector 在 FAT 中的序號
 mov dx, RootDirSectors
 add ax, dx
 add ax, DeltaSectorNo
 add bx, [BPB_BytsPerSec]
 jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:

 mov dh, 1   ; "Ready."
 call DispStr   ; 顯示字元串

; *****************************************************************************************************
 jmp BaseOfLoader:OffsetOfLoader ; 這一句正式跳轉到已加載到內
      ; 存中的 LOADER.BIN 的開始處,
      ; 開始執行 LOADER.BIN 的程式碼。
      ; Boot Sector 的使命到此結束。
; *****************************************************************************************************



;============================================================================
;變量
;----------------------------------------------------------------------------
wRootDirSizeForLoop dw RootDirSectors ; Root Directory 占用的磁區數, 在循環中會遞減至零.
wSectorNo  dw 0  ; 要讀取的磁區號
bOdd   db 0  ; 奇數還是偶數

;============================================================================
;字元串
;----------------------------------------------------------------------------
LoaderFileName  db "LOADER  BIN", 0 ; LOADER.BIN 之檔案名
; 為簡化程式碼, 下面每個字元串的長度均為 MessageLength
MessageLength  equ 9
BootMessage:  db "Booting  "; 9字元, 不夠則用空格補齊. 序號 0
Message1  db "Ready.   "; 9字元, 不夠則用空格補齊. 序號 1
Message2  db "No LOADER"; 9字元, 不夠則用空格補齊. 序號 2
;============================================================================


;----------------------------------------------------------------------------
; 函數名: DispStr
;----------------------------------------------------------------------------
; 作用:
; 顯示一個字元串, 函數開始時 dh 中應該是字元串序號(0-based)
DispStr:
 mov ax, MessageLength
 mul dh
 add ax, BootMessage
 mov bp, ax   ; ┓
 mov ax, ds   ; ┣ ES:BP = 串位址
 mov es, ax   ; ┛
 mov cx, MessageLength ; CX = 串長度
 mov ax, 01301h  ; AH = 13,  AL = 01h
 mov bx, 0007h  ; 頁號為0(BH = 0) 黑底白字(BL = 07h)
 mov dl, 0
 int 10h   ; int 10h
 ret


;----------------------------------------------------------------------------
; 函數名: ReadSector
;----------------------------------------------------------------------------
; 作用:
; 從第 ax 個 Sector 開始, 將 cl 個 Sector 讀入 es:bx 中
ReadSector:
 ; -----------------------------------------------------------------------
 ; 怎樣由磁區號求磁區在磁碟中的位置 (磁區號 -> 柱面號, 起始磁區, 磁頭號)
 ; -----------------------------------------------------------------------
 ; 設磁區號為 x
 ;                           ┌ 柱面號 = y >> 1
 ;       x           ┌ 商 y ┤
 ; -------------- => ┤      └ 磁頭號 = y & 1
 ;  每磁道磁區數     │
 ;                   └ 余 z => 起始磁區號 = z + 1
 push bp
 mov bp, sp
 sub esp, 2   ; 辟出兩個字元的堆疊區域保存要讀的磁區數: byte [bp-2]

 mov byte [bp-2], cl
 push bx   ; 保存 bx
 mov bl, [BPB_SecPerTrk] ; bl: 除數
 div bl   ; y 在 al 中, z 在 ah 中
 inc ah   ; z ++
 mov cl, ah   ; cl <- 起始磁區號
 mov dh, al   ; dh <- y
 shr al, 1   ; y >> 1 (其實是 y/BPB_NumHeads, 這裡BPB_NumHeads=2)
 mov ch, al   ; ch <- 柱面號
 and dh, 1   ; dh & 1 = 磁頭號
 pop bx   ; 恢復 bx
 ; 至此, "柱面號, 起始磁區, 磁頭號" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
 mov dl, [BS_DrvNum]  ; 驅動器號 (0 表示 A 碟)
.GoOnReading:
 mov ah, 2   ; 讀
 mov al, byte [bp-2]  ; 讀 al 個磁區
 int 13h
 jc .GoOnReading  ; 如果讀取錯誤 CF 會被置為 1, 這時就不停地讀, 直到正確為止

 add esp, 2
 pop bp

 ret

;----------------------------------------------------------------------------
; 函數名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
; 找到序號為 ax 的 Sector 在 FAT 中的項目, 結果放在 ax 中
; 需要注意的是, 中間需要讀 FAT 的磁區到 es:bx 處, 所以函數一開始保存了 es 和 bx
GetFATEntry:
 push es
 push bx
 push ax
 mov ax, BaseOfLoader; `.
 sub ax, 0100h ;  | 在 BaseOfLoader 後面留出 4K 空間用於存放 FAT
 mov es, ax  ; /
 pop ax
 mov byte [bOdd], 0
 mov bx, 3
 mul bx   ; dx:ax = ax * 3
 mov bx, 2
 div bx   ; dx:ax / 2  ==>  ax <- 商, dx <- 餘數
 cmp dx, 0
 jz LABEL_EVEN
 mov byte [bOdd], 1
LABEL_EVEN:;偶數
 ; 現在 ax 中是 FATEntry 在 FAT 中的偏移量,下面來
 ; 計算 FATEntry 在哪個磁區中(FAT占用不止一個磁區)
 xor dx, dx
 mov bx, [BPB_BytsPerSec]
 div bx ; dx:ax / BPB_BytsPerSec
     ;  ax <- 商 (FATEntry 所在的磁區相對於 FAT 的磁區號)
     ;  dx <- 餘數 (FATEntry 在磁區內的偏移)。
 push dx
 mov bx, 0 ; bx <- 0 於是, es:bx = (BaseOfLoader - 100):00
 add ax, SectorNoOfFAT1 ; 此句之後的 ax 就是 FATEntry 所在的磁區號
 mov cl, 2
 call ReadSector ; 讀取 FATEntry 所在的磁區, 一次讀兩個, 避免在邊界
      ; 發生錯誤, 因為一個 FATEntry 可能跨越兩個磁區
 pop dx
 add bx, dx
 mov ax, [es:bx]
 cmp byte [bOdd], 1
 jnz LABEL_EVEN_2
 shr ax, 4
LABEL_EVEN_2:
 and ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

 pop bx
 pop es
 ret
;----------------------------------------------------------------------------

times  510-($-$$) db 0 ; 填充剩下的空間,使生成的二進制程式碼恰好為512字元
dw  0xaa55    ; 結束標誌

loader.asm

org 0100h

 mov ax, 0B800h
 mov gs, ax
 mov ah, 0Fh
 mov al, 'L'
 mov [gs:((80 * 0 + 39) * 2)], ax

 jmp $

步驟

  1. 編譯 boot.asm (nasm boot.asm -o boot.bin)
  2. 編譯 loader.asm (nasm loader.asm -o loader.bin)
  3. 用 bximage 製作軟碟印象檔(floppy image),改名為 a.img (bximage -fd a.img)
  4. 將 boot.bin 寫入 a.img (dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc)
  5. 將 loader.bin 複製到 a.img
    mkdir tmp 
    sudo mount -o loop a.img tmp 
    sudo cp loader.bin tmp 
    sudo umount tmp 
    rmdir tmp
執行結果

留言

這個網誌中的熱門文章

用 C# 批次控制 Word 合併列印

前由 我有全區的電話資料,問題在於我要依不同里別來製作出電話簿。結果如下圖: 單純採用合併列印無法達成我的需求。解決方法係用「功能變數」儲存上一個里別,與現在里別進行比較:若不同,則換頁。不過,這樣功能變數還蠻長的。最後,我還是採用 C# 來解決。 解決方案 用 C# 控制 WORD 中合併列印的「資料來源 Data Source」,給予不同里別的「sqlstatement」。迴圈處理不同的里別即可。但可預見其處理過程會很慢,不過還好,我可以不用在意它,有跑出結果即可。 程式碼 IList<string> areas = new List<string>() { "後壁", "侯伯", "嘉苳", "土溝", "嘉田", "嘉民", "菁豊", "崁頂", "後廍", "墨林", "菁寮", "新嘉", "頂長", "平安", "仕安", "竹新", "新東", "長安", "頂安", "福安", "烏樹" }; string root = @"D:\"; // 根目錄 string data = root + @"\data.docm"; // 資料檔(即資料來源) string template = root + @"\template.docx"; // 已設定好格式與合併欄位的 Word 檔 string output = @"d:\Final"; // 輸出之資料夾 object oMissing = System.Reflection.Missing.Va...

VLC c# 順利編譯

原文網址: http://www.cnblogs.com/haibindev/archive/2011/12/21/2296173.html 原文作者: haibindev 原文標題:c#万能视频播放器 本文的重點在於修正 class VlcPlayer,使其能順利在 VC# Express 2010 .Net Framework 4 下順利編譯。 修正重點在於 CallingConvention = CallingConvention. StdCall 改成 CallingConvention = CallingConvention. Cdecl using System; using System.Runtime.InteropServices; using System.Security; using System.Text; namespace VlcDotNet { class VlcPlayer { private IntPtr libvlc_instance_; private IntPtr libvlc_media_player_; private double duration_; public VlcPlayer(string pluginPath) { string plugin_arg = "--plugin-path=" + pluginPath; string[] arguments = { "-I", "dummy", "--ignore-config", "--no-video-title", plugin_arg }; libvlc_instance_ = LibVlcAPI.libvlc_new(arguments); libvlc_media_player_ = LibVlcAPI.libvlc_media_player_new(libvlc_instance_); } public ...

[Symfony+Doctrine] 透過非 Id 來使用 Pessimistic Lock

根據 文件 ,Doctrine 有 Pessimistic Lock,又分為兩種: LockMode::PESSIMISTIC_WRITE:對應至 MySQL 的 Select FOR UPDATE LockMode::PESSIMISTIC_READ:對應至 MySQL 的 Select LOCK IN SHARE MODE 差別在於 LOCK IN SHARE MODE 會將在 row 的資料鎖定(row-level lock),在非同一交易(Transaction)下,不給寫入,其他交易可以讀取, 且可以繼續 Select LOCK IN SHARE MODE 。而 FOR UPDATE 不僅鎖定該資料,在非同一交易下,不給寫入,其它交易可以讀取, 但不能 Select LOCK IN SHARE MODE 。MySQL 文件有更詳細的比較與情境使用的說明,參考 網址 。 現在問題是,我們要完全採用 ORM 來處理資料。Doctrine 的文件提到 EntityManager::find 可以採用 Pessimistic Lock, 但 find 是透過 id 來處理 。而其他 find 系列函數(包括:findAll, findBy, findOneBy)皆不支援 LockMode。 因此,勢必要有方法來「透過非 id 來使用 Pessimistic Lock」。透過查看原始碼,簡單的方法是有的,解法之範例如下: 19 public function depositAction() 20 { 21 22 $em = $this->getDoctrine()->getManager(); 23 24 $em->transactional(function ($em) { 25 $entityName = 'AcmeTrainingBundle:Account'; 26 $lockMode = LockMode::PESSIMISTIC_READ; 27 $orderBy = null; 28 $...