2007年11月27日 星期二

實作 Linux 下的硬體自動偵測:PCI 裝置

Standard
x86 的硬體分為很多種類別,從早期的 ISA 一直到後來的 PCI、PCMCIA、USB,都有各自的硬體接角定義,要辨識這些裝置,得靠這些硬體通電與主機板連接後,所提供的自身資訊。

就最常用到的 PCI 裝置來說,目前有 99.999% 的硬體都是歸屬於 PCI 裝置﹝包括AGP、主機板上的各種控制晶片﹞。所以,只要能夠正確辨識 PCI 裝置並載入相對應的 Modules,就能夠對大部份硬體做自動識別。

識別 PCI 的方法,就是讀取 /proc/bus/pci/devices 或 /sys/bus/devices/* 取得裝置所提供的 vendor(製造商)、device(裝置或晶片型號)、subvendor(再製造商)、subdevice(再製造之裝置型號),然後將這些資訊與 Linux kernel 所提供的 modules.pcimap 做比對,以得到該裝置所對應的 module name。最後,只要使用 modprobe 去載入模組就大功告成了。

比較要注意的是 modules.pcimap,如果你詳細研究過這 map 檔,應該會發現同一個 vendor:device,卻偶爾有兩種可使用的 modules。例如使用 10ec:8139 去尋找,會同時找到 8139cp 和 8139too。諸如此類的裝置非常多,所以比對 modules.pcimap 時有其規則以免發生這樣的狀況:

比對的優先順序應該是:
  1. 尋找 vendor:device 和 subvendor:subdevice 相同的專用模組。
  2. 尋找 device 和 subvendor:subdevice 相同的通用模組。
  3. 尋找 device 建議模組。
  4. 尋找 vendor:device 標準模組。

使用 bash 完成的程式碼:
#!/bin/bash
MODULEMAP=/usr/share/frexhwd/modules.fhm

# findmodule [vendor] [device] [subvendor] [subdevice]
findmodule() {
while read VENDEV SUBVENDEV MODULE; do
if [ x"${3}${4}" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done <<-EOF
$(grep "^${1}${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"${3}${4}" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done lgt;lgt;-EOF
$(grep "^ffff${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"$MODULE" != x ]; then
echo $MODULE
return
fi
done <<-EOF
$(grep "^ffff${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"${1}${2}" = x"$VENDEV" ] && [ x"ffffffff" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done lgt;lgt;-EOF
$(grep "ffffffff" $MODULEMAP)
EOF

}

for DEVINFO in /sys/bus/pci/devices/*;do
VENDOR=`cat $DEVINFO/vendor`
VENDOR=${VENDOR:2}
DEVICE=`cat $DEVINFO/device`
DEVICE=${DEVICE:2}
SUBVENDOR=`cat $DEVINFO/vendor`
SUBVENDOR=${SUBVENDOR:2}
SUBDEVICE=`cat $DEVINFO/subsystem_device`
SUBDEVICE=${SUBDEVICE:2}
MODULE=`findmodule $VENDOR $DEVICE $SUBVENDOR $SUBDEVICE`
if [ x"$MODULE" != x ]; then
modprobe -q $MODULE &< /dev/null
fi
done


關於 /usr/share/frexhwd/modules.fhm,是以 modules.pcimap 為基礎,自行轉的檔案格式。我寫了一小段 Script 做轉換:
#!/bin/bash
while read MODULE VENDOR DEVICE SUBVENDOR SUBDEVICE OINFO; do
if [ x"$MODULE" = x"#" ] || [ x"$MODULE" = x ]; then
continue
fi
echo -e "${VENDOR:6}${DEVICE:6}\t${SUBVENDOR:6}${SUBDEVICE:6}\t$MODULE" >> $2
done < $1

2007年11月26日 星期一

Frexhwd 嬌小的硬體偵側程式

Standard
今年初的時候,我在 SourceForge 申請了一個 Project 叫做『Frexhwd』,這是當時我寫給這專案的一段描述:

Frexhwd is a simple and small Hardware Detection for Linux. It is written by Unix
shell(Bash) and C Language. it easy to run on small, embedded or simple system.

由這專案的名稱就可以知道,這是一個硬體偵側程式(Hardware Detection),而當初建立這專案的目標就是希望以最簡單的方式,實作硬體偵測並自動載入相對應的 Drivers。其實說到類似的功能,Debian 的 discover 與 Redhat/Fedora 的 Kudzu 都已經做得相當完整,而且在實際運作上都已經沒有什麼問題了。但如果真要說令人不滿意的地方,就是太過 fat!

discover 除了在非 Debian 系的環境中 compile 會碰到不少問題外,他的妸娜的身材倒還可以接受;而 Kudzu 從一開始就選擇 Python 當撰寫語言,根本是個錯誤。正因為 Python 的相關 Librarys 和檔案,本身就已經是個肥大的腫瘤,令使用它的 Kudzu 無法輕巧的移植到其他簡單的系統中。

最近又再次接觸到許多小型系統的研發,發現許多程式應該回歸其應用本質和單純,包山包海的結果必然會和複雜度同時產生,除了維護、移植的問題外,更有許多環境限制和浪費資源在非必要的功能,當初會建立 Frexhwd Project 也是因為有這樣的想法。

我希望 Frexhwd 是以 Shell 和 C Language 撰寫,一方面使用 Shell 可移植度高,另一方面,可以使用 C 補強 Shell 在檔案資料處理上的效能不足。不過照目前的進度來看,還未使用到 C 來撰寫任何一部份的程式,僅用到 Busybox 或 GNU 的 grep 來達成硬體模組的資料比對。

雖然遲遲沒有將 Frexhwd 的 source code 放上去,但已經有一些成果,等最近忙一段落,會將相關檔案整理好放上 SourceForge。

2007年11月16日 星期五

使用 Bash Shell 撰寫 Web CGI 程式

Standard
使用系統的 Bash Shell 來寫 CGI,這是以前做嵌入式系統時的一個 Web 應用程式解決方案,構想很酷、很有趣。當時考慮到,如果用 C 語言寫 CGI,程式會既複雜又不易維護﹝研發嵌入式系統時 Cross Compile 和繁瑣的 Debug 可是很討厭的!!﹞;若是讓 Web Server 其支援 PHP、Perl,其程式架構會太過龐大。種種因素讓我決定用 Bash 完成需要的 CGI 網站應用程式,畢竟 Bash 從面市到現在,功能越來越齊全強大了,我想應該能勝任 Web Scripts 的工作!:P

當時我先上網搜尋是否有人試過使用 Bash Shell 開發過 CGI 程式,果然有個老兄寫了一些 Bash Functions 來支援些 Web 上的操作,讓我們可以輕易使用 Bash 來寫 CGI。有興趣的人可以到 bashlib(http://bashlib.sourceforge.net/) 抓回來玩玩看。

如果你看過了這個 Bashlib ,一定發現其實它功能很陽春,只有取得 GET 傳值、表單傳值、轉向、Cookies等幾種功能。嘿嘿,這時候當然要開始惡搞這個 bashlib,我改寫並加入了一些功能,像是 Session 的支援。

自己額外亂搞新增支援 Session 的 Functions:
function init_session {
if [ ! -d /tmp/.xcdmgrsess ]; then
mkdir /tmp/.xcdmgrsess &> /dev/null
fi

SESS_ID=`cookie SESS_ID`
if [ A$SESS_ID = A"" ]; then
SESS_ID=`mkcode 30`
set_cookie SESS_ID $SESS_ID
touch /tmp/.xcdmgrsess/$SESS_ID
else
if [ -f /tmp/.xcdmgrsess/$SESS_ID ]; then
while read SESSLINE; do
eval "export 'SESSION_${SESSLINE}'"
done < /tmp/.xcdmgrsess/$SESS_ID
else
touch /tmp/.xcdmgrsess/$SESS_ID
fi
fi
}

function uninit_session {
SESS_ID=`cookie SESS_ID`
if [ ! A$SESS_ID = A"" ]; then
set_cookie SESS_ID ""
if [ -f /tmp/.xcdmgrsess/$SESS_ID ]; then
rm -f /tmp/.xcdmgrsess/$SESS_ID
fi
fi
}

# session function. Same explanation as param
function session {
local name
local value

SESS_ID=`cookie SESS_ID`
while read SESSLINE; do
eval "export 'SESSION_${SESSLINE}'"
done < /tmp/.xcdmgrsess/$SESS_ID

if [ $# -eq 1 ]; then
name=$1
name=$(echo ${name} | /bin/sed -e 's/SESSION_//')
value=$(/usr/bin/env | /bin/grep "^SESSION_${name}" | /bin/sed -e 's/SESSION_//' | /usr/bin/cut -d= -f2-)
echo ${value}
elif [ $# -gt 1 ]; then
name=$1
shift
eval "export 'SESSION_${name}=$*'"
/usr/bin/env | /bin/grep '^SESSION_' | /bin/sed -e 's/SESSION_//' > /tmp/.xcdmgrsess/$SESS_ID
else
value=$(/usr/bin/env | /bin/grep '^SESSION_' | /bin/sed -e 's/SESSION_//' | /usr/bin/cut -d= -f1)
echo ${value}
fi
unset name
unset value
}


function set_session {
local name=$1
shift
local value=$*

session $name $value &> /dev/null
}


使用方法:
  1. 使用前先呼叫:
    • init_session
  2. 設自訂的 Session 變數:
    • set_session NAME "Fred"
  3. 取得 Session 變數值:
    • SESSION_NAME=`session NAME`


後記:

其實,用 Bash 這種系統 shell 來寫 CGI 本來就不是什麼新構想,因為常在開發 Web Server 的 CGI 支援時,就會用這種 Shell 寫簡單的 CGI 做測試。只是這次瘋狂了點,利用 Bash 達成更多 CGI 的功能。

另外,使用 Bash 當 CGI 的 Scripts Language 要注意的是 Injection 的攻擊,因為每一行都被當成系統命令在執行,所以當用戶透過 Web 執行 Bash CGI 時,就相當於用戶在你的 Console 下執行命令。想要避免這些問題,需要詳細了解 Bash 雙引號和單引號的使用和差別。

2007年11月15日 星期四

有趣的邏輯問題:是誰在說謊

Standard
開心的是期中考接近尾聲,而且今天也是我的生日,更開心的是老天爺待我很好,放我一天假沒給我排考。今天,在網路上逛啊逛,看到了一些判斷說謊的邏輯問題,雖然之前就曾看過,但不管再看多少遍都還是覺得很有趣。也想到好幾天沒寫東西了,就貼上來讓大伙一同分享吧!準備好接受考驗了嗎? Ready? Go!

第一題:

A 說B 在說謊,B 說 C 在說謊,C 說 A、B 都在說謊。請問到底誰在說謊?



第二題:

現在審問四名竊賊嫌疑犯。已知當中有一名是竊賊,還知道這四個人不是誠實就是說謊,請根據他們回答問題的結果中,判斷誰是竊賊。

甲說:『乙沒有偷,是丁偷的。』
乙說:『我沒有偷,是丙偷的。』
丙說:『甲沒有偷,是乙偷的。』
丁說:『我沒有偷。』



解答:一:只有 B 說真話。二:乙是竊賊。

2007年11月11日 星期日

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

Standard
到目前為止,從 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 次,繼續尋找工作

FindTask1:
stc ; 設進位旗標 CF = 1
ret

FindTask2:
mov eax, esi ; 設 EAX 為 ESI
sub eax, tasksList ; EAX = EAX - tasksList
clc ; 設進位旗標 CF = 0



後記

下星期要期中考,忙裡偷閒來看一下 Orz Microkernel 的 Source Code :)

另外補充,下星期四﹝11/15﹞是我的生日。唉,又要老一歲了。

2007年11月6日 星期二

Orz 天殺、惡搞的 Microkernel

Standard
昨天在 IRC 上與 Jserv 有談到一點 Orz Microkernel 的問題,因為在註解的過程中,我碰到不少挫折。內容談到 Orz Microkernel 本來就是一個玩具,當初被寫出來就只是 Just Fun!,所以裡面常會出現 0x12345678 這種奇怪沒道理的定義,更有許多 Magic Code 和惡搞的陷阱,像是對目前核心沒意義的偏移之類的,這部份讓我真的被玩弄了,有強烈 Orz 的感覺﹝Jserv 成功了!囧﹞。

雖然這些惡搞的 Code 令我傻眼,但一一去發現它也不失為一個好的休閒活動﹝笑﹞! Jserv這次有說到,因為聽說真的有大專院校的學生在研究 Orz Microkernel,所以打算在下一版的 Orz Microkernel ,做精簡化、優化等等的動作,並且藉此拿掉惡搞東西,可能還會加入 GUI、Function call 等東西進去,想必許多研究 Orz Microkernel的人應該也跟我一樣看到很多傻眼的 Code 吧!!

好像要遲到了,該準備去上課囉!我是個很混的大學生,好像讀書都沒讀 Orz Microkernel 的麼的認真說。:)

2007年11月5日 星期一

被誤解的可悲稱號:電腦高手

Standard
在你心中,所謂的『電腦高手』是怎麼樣的呢?會 DIY 組裝電腦?了解硬體行情?灌 Windows 作業系統?懂得如何處理各種商業軟體的問題?還是會寫程式?救中毒或壞掉硬碟內的資料?更或是會入侵、從網路偷別人隱私?我自認不是什麼電腦高手,充其量只是朋友抬舉,電腦方面的接觸只是兼職的工讀,但常聽到很多人的話令我不知如何是好。

『某某某電腦很強,要買什麼硬體比較划算,問他就好啦!』

就是這簡短的一句話,讓我有勤練十年功而手腳筋全斷的感覺,如果說知道硬體的價格是要當電腦高手的必要條件,那我想電腦賣場中的採購、會計、業務員是天底下最厲害的武林至尊了。

『怎麼會?你電腦不是很厲害,一定知道吧!?』

大家都知道,微軟的作業系統最令人無奈的問題,就是從早期的 Always 藍色死當,到後來的『程式關閉,請洽...』,再到現在的『程式無回應』。說無奈是因為我們根本無從得知,為什麼程式或系統會突然死掉,或是某些商業軟體在設計上有什麼暇疵;還有最重要的是,微軟系的病毒嚴重肆虐,已經讓人不想再去了解病毒的變化,能殺且殺,不能殺重灌便算。還有 Office 的操作,各種軟體的使用方法,都是常見的問題,多到數不清。

但總有人會以無辜的電腦用戶自居,向人問種種莫名的狀況,如果你回應他們說『不知道』,他們總是說:『怎麼會?你電腦不是很厲害,一定知道吧!?』。哪怕有人真有閒功夫,想盡辦法去查明過去所有的問題,也是惘然,因為在這時代,軟體每天都有更新改動,病毒每日都遵行達爾文理論。我們又哪裡有這麼多時間去一一了解呢?他們弄得好像我們如某漫畫裡的主角一般,什麼東西都會駕駛一樣。

『你會不會寫遊戲外掛?會不會寫某某遊戲?會不會自己寫一個某某軟體,然後就可以賺錢了。』

不難察覺講出這些詞的人,是完全不懂電腦方面技術的人,因此聽者多半是不太計較的。但是,他們其實有一定程度的汙辱人而不自知。為什麼這樣說?因為,會講出這些話的人,都把玩電腦很厲害和電腦技術很厲害劃為等號,絲毫無所尊重辛苦的求知者,在他們心理,只要是和電腦有關的人都是『玩電腦的』。

玩電腦很厲害只要多當孝子買東買西,多砸些時間看不明信件、安裝莫名軟體然後中毒、重灌系統就能達到一等一的水準;而真正電腦技術很厲害的人,不但要每天看書看文件,還要日夜鑽研實作各種技術、新想法,並想盡辦法吸收所有日新月異的資訊,廢寢忘食。這兩種人的能力和努力相差十萬八千里,辛苦更相差何止千萬倍,怎可放在一起比較?到這裡,如果你是想要朝電腦技術高手邁進的人,應該是覺得很洩氣,因為無論你怎麼努力,終究只是個玩電腦的。

『你可不可以幫我破解某某的密碼?幫我入侵某某網站?』

會講這些話的人受了『好萊塢』影響極深,好像所謂的電腦高手,只要敲敲幾個按鍵,任何密碼就會顯示在電腦螢幕上,入侵網站更是比吃飯還要簡單,完全不知道其中的技術複雜度。如果你拒絕,他們會覺得是你『故意』不幫他,如果同意幫忙,他們三五分鐘就問你一下破了沒。殊不知,『好萊塢』的劇情裡駭客多半都被神化,如果真的這麼厲害,第三次世界核子大戰早就開打囉。

後記

在這我要為很多投入電腦資訊技術的高手抱屈,不是說有點功夫就代表什麼都會,就像是懂得玩Wii的人不見得就會修Wii,也不見得會寫 Wii 的遊戲,更不見得會畫得出遊戲裡面的圖片。如果說要照那些被誤解的觀點來選出真正的電腦高手,我看光華商場的店家和賣盜版軟體的人﹝現在可能不如以前,過去大補帖時代的盜版商,技術可是很不簡單的!﹞可是最厲害的。

2007年11月4日 星期日

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

Standard
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's Co-Processor
; 76 Hardware IRQ14 Hard Disk Drive
; 77 Hardware IRQ15 Reserved
;
; 控制 IRQ 開啟或關閉的控制開關(Operation Control Word) 位址定義:
; IRQ0 ~ IRQ7 的控制字元位址是 0x21(bit0 ~ bit7)
; IRQ8 ~ IRQ15 的控制字元位址是 0xA1(bit0 ~ bit7)
;
; 中斷結束前要傳送『中斷結束』給『可程式化中斷控制器』:
; 中斷結束訊號 End Of Interrupt(EOI) :0x20
; 可程式化中斷控制器 Programmable Interrupt Controller(PIC) 定義:
; IRQ0 ~ IRQ7 的 MASTER_PIC 的 I/O 位址:0x20
; IRQ8 ~ IRQ15 的 SLAVE_PIC 的 I/O 位址:0xA0

IRQ_Dummy_M:
push ax ; 將原有 AX 暫存至堆疊
mov al, END_OF_INTERR ; 設 AL 為中斷結束訊號
out MASTER_PIC, al ; 設定主要可程式化中斷控制器
pop ax ; 從堆疊取回原有的 AX
sti ; 啟用中斷
iret

IRQ_Dummy_S:
push ax ; 將原有 AX 暫存至堆疊
mov al, END_OF_INTERR ; 設 AL 為中斷結束訊號
out MASTER_PIC, al ; 設定主要可程式化中斷控制器
out SLAVE_PIC, al ; 設定次要可程式化中斷控制器
pop ax ; 從堆疊取回原有的 AX
sti ; 啟用中斷
iret

;------------------------------------------------------------------------
;;;; 計時器

IRQ_0:
; 讓目前正在執行的工作先交還執行權給核心
cli ; 禁用中斷以免干擾工作執行權切換
push ax ; 將 AX 存入堆疊
push ds ; 將 DS 存入堆疊
mov ax, kernel_data - _GDT ; 計算 kernel_data 的資料區段位址
mov ds, ax ; 設資料區段暫存器 DS = AX

inc DWORD [globalTime]

; 倒數目前正在執行的工作擁有的時間
mov eax, [taskRunningTime] ; 設 EAX 目前工作可佔用 CPU 的時間長度
dec eax ; EAX 減一
mov [taskRunningTime], eax ; 回存至 taskRunningTime
jz IRQ_0_Switch ; 直到 EAX 為零,此時工作不可再佔用 CPU
; 呼叫 IRQ_0_Switch 準備切換到下一個工作

; 核心將執行權交還給目前正在執行的工作
pop ds ; 恢復目前工作的資料區段暫存器 DS
pop ax ; 恢復目前工作的計數暫存器 AX
jmp IRQ_Dummy_M ; 跳到 IRQ_Dummy_M 設定中斷並返回結束

IRQ_0_Switch:
mov al, END_OF_INTERR
out MASTER_PIC, al
pop ds ; 恢復目前工作的資料區段暫存器 DS
pop ax ; 恢復目前工作的計數暫存器 AX

GDT_Adjustment
; 儲存目前工作的所有暫存器狀態
mov [task_ebp], ebp ; 暫時將目前工作的基底暫存器 EBP 狀態移開至
; task_ebp

mov ebp, [currentTaskP] ; 將 EBP 指向目前工作的狀態記錄記憶體位址
mov [ds:ebp + s_task.r_eax], eax ; 儲存目前工作的累積暫存器 EAX
mov [ds:ebp + s_task.r_ebx], ebx ; 儲存目前工作的基底暫存器 EBX
mov [ds:ebp + s_task.r_ecx], ecx ; 儲存目前工作的計數暫存器 ECX
mov [ds:ebp + s_task.r_edx], edx ; 儲存目前工作的資料暫存器 EDX
mov [ds:ebp + s_task.r_esi], esi ; 儲存目前工作的來源索引暫存器 ESI
mov [ds:ebp + s_task.r_edi], edi ; 儲存目前工作的目的索引暫存器 EDI
mov eax, [task_ebp] ; 從 task_ebp 取回 EBP 狀態
mov [ds:ebp + s_task.r_ebp], eax ; 儲存目前工作的基底暫存器 EBP
; 目前堆疊內:[EFLAGS][CS][EIP]
pop eax ; 取得指標暫存器 EIP 狀態
mov [ds:ebp + s_task.r_eip], eax ; 儲存目前工作的指標暫存器 EIP
pop eax ; 將目前工作的暫存器 CS 移出堆疊
pop eax ; 取得目前工作的旗標暫存器 EFLAGS
mov [ds:ebp + s_task.r_eflags], eax ; 儲存目前工作的旗標暫存器 EFLAGS
mov [ds:ebp + s_task.r_esp], esp ; 儲存目前工作的堆疊暫存器 ESP

; 將核心堆疊暫存器狀態回復到 ESP
mov ax,kernel_data - _GDT ; 計算 kernel_data 的資料區段位址
mov ss,ax ; 設定 SS 堆疊區段為 AX
mov esp, [kernel_esp] ; 將之前核心的堆疊暫存器 ESP 狀態回復到 ESP

; 切換到下一個工作行程
jmp Schedule ; 呼叫 tasks.asm 的 Schedule 以切換到下一個
; 工作行程



後記

系統計時器這部份就是工作排程器的心臟,因為它不斷的定時中斷,我們可以不停的檢查工作是否該切換了,讓核心生生不息的跳動。

2007年11月3日 星期六

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

Standard
工作新增、載入後,一切都準備就緒了,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
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 出來呢?
就算沒有這行命令,我們還是可以正常的來計算記憶體偏移量,那此行命令不就多此一舉?希望知道的人能回應我的愚笨問題。