《30天自制操作系統(tǒng)》是一本兼具趣味性、實(shí)用性與學(xué)習(xí)性的操作系統(tǒng)圖書。作者從計(jì)算機(jī)的構(gòu)造、匯編語(yǔ)言、C語(yǔ)言開(kāi)始解說(shuō),讓讀者在實(shí)踐中掌握算法。在這本書的指導(dǎo)下,從零編寫所有代碼,30天后就可以制作出一個(gè)具有窗口系統(tǒng)的32位多任務(wù)操作系。
《30天自制操作系統(tǒng)》適合操作系統(tǒng)愛(ài)好者和程序設(shè)計(jì)人員閱讀。
只需30天 從零開(kāi)始編寫一個(gè)五臟俱全的圖形操作系統(tǒng) 39.1KB迷你系統(tǒng) 實(shí)現(xiàn)多任務(wù)、漢字顯示、文件壓縮,還能聽(tīng)歌看圖玩游戲 日本編程天才 揭開(kāi)CPU、內(nèi)存、磁盤以及操作系統(tǒng)底層工作模式的神秘面紗
“好想編寫一個(gè)操作系統(tǒng)呀!”筆者的朋友曾說(shuō)這是所有程序員都曾經(jīng)懷揣的一個(gè)夢(mèng)想。說(shuō)“所有的程序員”可能有點(diǎn)夸張了,不過(guò)作為程序員的夢(mèng)想,它至少也應(yīng)該能排進(jìn)前十名吧。
也許很多人覺(jué)得編寫操作系統(tǒng)是個(gè)天方夜譚,這一定是操作系統(tǒng)業(yè)界的一個(gè)陰謀(笑)。他們故意讓大家相信編寫操作系統(tǒng)是一件非常困難的事情,這樣就可以高價(jià)兜售自己開(kāi)發(fā)的操作系統(tǒng),而且操作系統(tǒng)的作者還會(huì)被頂禮膜拜。那么實(shí)際情況又怎么樣呢?和別的程序相比,其實(shí)編寫操作系統(tǒng)并沒(méi)有那么難,至少筆者的感覺(jué)是這樣。
在各位讀者之中,也許有人曾經(jīng)挑戰(zhàn)過(guò)操作系統(tǒng)的編寫,但因?yàn)樘y而放棄了。擁有這樣經(jīng)歷的人也許不會(huì)認(rèn)同筆者的觀點(diǎn)。其實(shí)你錯(cuò)了,你的失敗并不是因?yàn)榫帉懖僮飨到y(tǒng)太難,而是因?yàn)闆](méi)有人告訴你那其實(shí)是一件很簡(jiǎn)單的事而已。
不僅是編寫操作系統(tǒng),任何事都是一樣的。如果講解的人認(rèn)為它很難,那就不可能把它講述得通俗易懂,即便是同樣的內(nèi)容,也會(huì)講得無(wú)比復(fù)雜。這樣的講解,肯定是很難懂的。
那么,你想不想和筆者一起再挑戰(zhàn)一次呢?如果你曾經(jīng)夢(mèng)想過(guò)編寫自己的操作系統(tǒng),一定會(huì)覺(jué)得樂(lè)在其中的。
可能有人會(huì)說(shuō),這本書足足有700多頁(yè),怎么會(huì)“有趣”和“簡(jiǎn)單”呢?唔,這么一說(shuō)筆者也覺(jué)得挺心虛的,不過(guò)其實(shí)也只是長(zhǎng)了那么一點(diǎn)點(diǎn)啦。平均下來(lái)的話,每天只有大約23頁(yè)的內(nèi)容,你看,也沒(méi)有那么長(zhǎng)吧?
這本書的文風(fēng)非常輕松,也許你不知不覺(jué)中就會(huì)讀得很快。但是這樣的話可能印象不會(huì)很深,最好還是能靜下心來(lái)慢慢地讀。書中所展示的程序代碼和文字的說(shuō)明同樣重要,因此也希望大家仔細(xì)閱讀。只要注意這些,理解本書的內(nèi)容就應(yīng)該沒(méi)有問(wèn)題了。
在本書中,我們使用C語(yǔ)言和匯編語(yǔ)言來(lái)編寫操作系統(tǒng),不過(guò)不必?fù)?dān)心,你可以在閱讀本書的同時(shí)來(lái)逐步學(xué)習(xí)關(guān)于這些編程語(yǔ)言的知識(shí)。本書在這方面寫得非常仔細(xì),如果能有人通過(guò)本書終于把C語(yǔ)言中的指針給搞懂了,那筆者的目的也就達(dá)到了。即便是從這樣的水平開(kāi)始,30天后你也能夠編寫出一個(gè)很棒的操作系統(tǒng),請(qǐng)大家拭目以待吧!
川合秀實(shí)(Hidemi Kawai),生于1975年,是一位以“輕量化”編程思想見(jiàn)長(zhǎng)的“非主流”開(kāi)發(fā)者。2000年因自行開(kāi)發(fā)的OSASK項(xiàng)目而名聲大噪。OSASK是一個(gè)開(kāi)源的32位微型操作系統(tǒng),它并非以Linux等內(nèi)核為基礎(chǔ),而是完全從零開(kāi)始開(kāi)發(fā),在一張軟盤的容量下實(shí)現(xiàn)了GUI、多任務(wù)、多語(yǔ)言等高級(jí)特性,啟動(dòng)時(shí)間只需1秒。本書的內(nèi)容可以看成是作者以O(shè)SASK為藍(lán)本,教會(huì)讀者從零開(kāi)始開(kāi)發(fā)一個(gè)操作系統(tǒng),同時(shí)可以讓初學(xué)者在編寫操作系統(tǒng)的過(guò)程中,了解操作系統(tǒng)背后更多的知識(shí)。
第0天 著手開(kāi)發(fā)之前
1 前言
2 何謂操作系統(tǒng)
3 開(kāi)發(fā)操作系統(tǒng)的各種方法
4 無(wú)知?jiǎng)t無(wú)畏
5 如何開(kāi)發(fā)操作系統(tǒng)
6 操作系統(tǒng)開(kāi)發(fā)中的困難
7 學(xué)習(xí)本書時(shí)的注意事項(xiàng)(重要。
8 各章內(nèi)容摘要
第1天 從計(jì)算機(jī)結(jié)構(gòu)到匯編程序入門
1 先動(dòng)手操作
2 究竟做了些什么
3 初次體驗(yàn)匯編程序
4 加工潤(rùn)色
第2天 匯編語(yǔ)言學(xué)習(xí)與Makefile入門
1 介紹文本編輯器
2 繼續(xù)開(kāi)發(fā)
3 先制作啟動(dòng)區(qū)
4 Makefile入門
第3天 進(jìn)入32位模式并導(dǎo)入C語(yǔ)言
1 制作真正的IPL
2 試錯(cuò)
3 讀到18扇區(qū)
4 讀入10個(gè)柱面
5 著手開(kāi)發(fā)操作系統(tǒng)
6 從啟動(dòng)區(qū)執(zhí)行操作系統(tǒng)
7 確認(rèn)操作系統(tǒng)的執(zhí)行情況
8 32位模式前期準(zhǔn)備
9 開(kāi)始導(dǎo)入C語(yǔ)言
10 實(shí)現(xiàn)HLT(harib00j)
第4天 C語(yǔ)言與畫面顯示的練習(xí)
1 用C語(yǔ)言實(shí)現(xiàn)內(nèi)存寫入(harib01a)
2 條紋圖案(harib01b)
3 挑戰(zhàn)指針(harib01c)
4 指針的應(yīng)用(1)(harib01d)
5 指針的應(yīng)用(2)(harib01e)
6 色號(hào)設(shè)定(harib01f)
7 繪制矩形(harib01g)
8 今天的成果(harib01h)
第5天 結(jié)構(gòu)體、文字顯示與GDT/IDT初始化
1 接收啟動(dòng)信息(harib02a)
2 試用結(jié)構(gòu)體(harib02b)
3 試用箭頭記號(hào)(harib02c)
4 顯示字符(harib02d)
5 增加字體(harib02e)
6 顯示字符串(harib02f)
7 顯示變量值(harib02g)
8 顯示鼠標(biāo)指針(harib02h)
9 GDT與IDT的初始化(harib02i)
第6天 分割編譯與中斷處理
1 分割源文件(harib03a)
2 整理Makefile(harib03b)
3 整理頭文件(harib03c)
4 意猶未盡
5 初始化PIC(harib03d)
6 中斷處理程序的制作(harib03e)
第7天 FIFO與鼠標(biāo)控制
1 獲取按鍵編碼(hiarib04a)
2 加快中斷處理(hiarib04b)
3 制作FIFO緩沖區(qū)(hiarib04c)
4 改善FIFO緩沖區(qū)(hiarib04d)
5 整理FIFO緩沖區(qū)(hiarib04e)
6 總算講到鼠標(biāo)了(harib04f)
7 從鼠標(biāo)接受數(shù)據(jù)(harib04g)
第8天 鼠標(biāo)控制與32位模式切換
1 鼠標(biāo)解讀(1)(harib05a)
2 稍事整理(harib05b)
3 鼠標(biāo)解讀(2)(harib05c)
4 移動(dòng)鼠標(biāo)指針(harib05d)
5 通往32位模式之路
第9天 內(nèi)存管理
1 整理源文件(harib06a)
2 內(nèi)存容量檢查(1)(harib06b)
3 內(nèi)存容量檢查(2)(harib06c)
4 挑戰(zhàn)內(nèi)存管理(harib06d)
第10天 疊加處理
1 內(nèi)存管理(續(xù))(harib07a)
2 疊加處理(harib07b)
3 提高疊加處理速度(1)(harib07c)
4 提高疊加處理速度(2)(harib07d)
第11天 制作窗口
1 鼠標(biāo)顯示問(wèn)題(harib08a)
2 實(shí)現(xiàn)畫面外的支持(harib08b)
3 shtctl的指定省略(harib08c)
4 顯示窗口(harib08d)
5 小實(shí)驗(yàn)(harib08e)
6 高速計(jì)數(shù)器(harib08f)
7 消除閃爍(1)(harib08g)
8 消除閃爍(2)(harib08h)
第12天 定時(shí)器(1)
1 使用定時(shí)器(harib09a)
2 計(jì)量時(shí)間(harib09b)
3 超時(shí)功能(harib09c)
4 設(shè)定多個(gè)定時(shí)器(harib09d)
5 加快中斷處理(1)(harib09e)
6 加快中斷處理(2)(harib09f)
7 加快中斷處理(3)(harib09g)
第13天 定時(shí)器(2)
1 簡(jiǎn)化字符串顯示(harib10a)
2 重新調(diào)整FIFO緩沖區(qū)(1)(harib10b)
3 測(cè)試性能(harib10c~harib10f)
4 重新調(diào)整FIFO緩沖區(qū)(2)(harib10g)
5 加快中斷處理(4)(harib10h)
6 使用“哨兵”簡(jiǎn)化程序(harib10i)
第14天 高分辨率及鍵盤輸入
1 繼續(xù)測(cè)試性能(harib11a~harib11c)
2 提高分辨率(1)(harib11d)
3 提高分辨率(2)(harib11e)
4 鍵盤輸入(1)(harib11f)
5 鍵盤輸入(2)(harib11g)
6 追記內(nèi)容(1)(harib11h)
7 追記內(nèi)容(2)(harib11i)
第15天 多任務(wù)(1)
1 挑戰(zhàn)任務(wù)切換(harib12a)
2 任務(wù)切換進(jìn)階(harib12b)
3 做個(gè)簡(jiǎn)單的多任務(wù)(1)(harib12c)
4 做個(gè)簡(jiǎn)單的多任務(wù)(2)(harib12d)
5 提高運(yùn)行速度(harib12e)
6 測(cè)試運(yùn)行速度(harib12f)
7 多任務(wù)進(jìn)階(harib12g)
第16天 多任務(wù)(2)
1 任務(wù)管理自動(dòng)化(harib13a)
2 讓任務(wù)休眠(harib13b)
3 增加窗口數(shù)量(harib13c)
4 設(shè)定任務(wù)優(yōu)先級(jí)(1)(harib13d)
5 設(shè)定任務(wù)優(yōu)先級(jí)(2)(harib13e)
第17天 命令行窗口
1 閑置任務(wù)(harib14a)
2 創(chuàng)建命令行窗口(harib14b)
3 切換輸入窗口(harib14c)
4 實(shí)現(xiàn)字符輸入(harib14d)
5 符號(hào)的輸入(harib14e)
6 大寫字母與小寫字母(harib14f)
7 對(duì)各種鎖定鍵的支持(harib14g)
第18天 dir命令
1 控制光標(biāo)閃爍(1)(harib15a)
2 控制光標(biāo)閃爍(2)(harib15b)
3 對(duì)回車鍵的支持(harib15c)
4 對(duì)窗口滾動(dòng)的支持(harib15d)
5 mem命令(harib15e)
6 cls命令(harib15f)
7 dir命令(harib15g)
第19天 應(yīng)用程序
1 type命令(harib16a)
2 type命令改良(harib16b)
3 對(duì)FAT的支持(harib16c)
4 代碼整理(harib16d)
5 第一個(gè)應(yīng)用程序(harib16e)
第20天 API
1 程序整理(harib17a)
2 顯示單個(gè)字符的API(1)(harib17b)
3 顯示單個(gè)字符的API(2)(harib17c)
4 結(jié)束應(yīng)用程序(harib17d)
5 不隨操作系統(tǒng)版本而改變的API(harib17e)
6 為應(yīng)用程序自由命名(harib17f)
7 當(dāng)心寄存器(harib17g)
8 用API顯示字符串(harib17h)
第21天 保護(hù)操作系統(tǒng)
1 攻克難題——字符串顯示API(harib18a)
2 用C語(yǔ)言編寫應(yīng)用程序(harib18b)
3 保護(hù)操作系統(tǒng)(1)(harib18c)
4 保護(hù)操作系統(tǒng)(2)(harib18d)
5 對(duì)異常的支持(harib18e)
6 保護(hù)操作系統(tǒng)(3)(harib18f)
7 保護(hù)操作系統(tǒng)(4)(harib18g)
第22天 用C語(yǔ)言編寫應(yīng)用程序
1 保護(hù)操作系統(tǒng)(5)(harib19a)
2 幫助發(fā)現(xiàn)bug(harib19b)
3 強(qiáng)制結(jié)束應(yīng)用程序(harib19c)
4 用C語(yǔ)言顯示字符串(1)(harib19d)
5 用C語(yǔ)言顯示字符串(2)(harib19e)
6 顯示窗口(harib19f)
7 在窗口中描繪字符和方塊(harib19g)
第23天 圖形處理相關(guān)
1 編寫malloc(harib20a)
2 畫點(diǎn)(harib20b)
3 刷新窗口(harib20c)
4 畫直線(harib20d)
5 關(guān)閉窗口(harib20e)
6 鍵盤輸入API(harib20f)
7 用鍵盤輸入來(lái)消遣一下(harib20g)
8 強(qiáng)制結(jié)束并關(guān)閉窗口(harib20h)
第24天 窗口操作
1 窗口切換(1)(harib21a)
2 窗口切換(2)(harib21b)
3 移動(dòng)窗口(harib21c)
4 用鼠標(biāo)關(guān)閉窗口(harib21d)
5 將輸入切換到應(yīng)用程序窗口(harib21e)
6 用鼠標(biāo)切換輸入窗口(harib21f)
7 定時(shí)器API(harib21g)
8 取消定時(shí)器(harib21h)
第25天 增加命令行窗口
1 蜂鳴器發(fā)聲(harib22a)
2 增加更多的顏色(1)(harib22b)
3 增加更多的顏色(2)(harib22c)
4 窗口初始位置(harib22d)
5 增加命令行窗口(1)(harib22e)
6 增加命令行窗口(2)(harib22f)
7 增加命令行窗口(3)(harib22g)
8 增加命令行窗口(4)(harib22h)
9 變得更像真正的操作系統(tǒng)(1)(harib22i)
10 變得更像真正的操作系統(tǒng)(2)(harib22j)
第26天 為窗口移動(dòng)提速
1 提高窗口移動(dòng)速度(1)(harib23a)
2 提高窗口移動(dòng)速度(2)(harib23b)
3 提高窗口移動(dòng)速度(3)(harib23c)
4 提高窗口移動(dòng)速度(4)(harib23d)
5 啟動(dòng)時(shí)只打開(kāi)一個(gè)命令行窗口(harib23e)
6 增加更多的命令行窗口(harib23f)
7 關(guān)閉命令行窗口(1)(harib23g)
8 關(guān)閉命令行窗口(2)(harib23h)
9 start命令(harib23i)
10 ncst命令(harib23j)
第27天 LDT與庫(kù)
1 先來(lái)修復(fù)bug(harib24a)
2 應(yīng)用程序運(yùn)行時(shí)關(guān)閉命令行窗口(harib24b)
3 保護(hù)應(yīng)用程序(1)(harib24c)
4 保護(hù)應(yīng)用程序(2)(harib24d)
5 優(yōu)化應(yīng)用程序的大。╤arib24e)
6 庫(kù)(harib24f)
7 整理make環(huán)境(harib24g)
第28天 文件操作與文字顯示
1 alloca(1)(harib25a)
2 alloca(2)(harib25b)
3 文件操作API(harib25c)
4 命令行API(harib25d)
5 日文文字顯示(1)(harib25e)
6 日文文字顯示(2)(harib25f)
7 日文文字顯示(3)(harib25g)
第29天 壓縮與簡(jiǎn)單的應(yīng)用程序
1 修復(fù)bug(harib26a)
2 文件壓縮(harib26b)
3 標(biāo)準(zhǔn)函數(shù)
4 非矩形窗口(harib26c)
5 bball(harib26d)
6 外星人游戲(harib26e)
第30天 高級(jí)的應(yīng)用程序
1 命令行計(jì)算器(harib27a)
2 文本閱覽器(harib27b)
3 MML播放器(harib27c)
4 圖片閱覽器(harib27d)
5 IPL的改良(harib27e)
6 光盤啟動(dòng)(harib27f)
第31天 寫在開(kāi)發(fā)完成之后
1 繼續(xù)開(kāi)發(fā)要靠大家的努力
2 關(guān)于操作系統(tǒng)的大小
3 操作系統(tǒng)開(kāi)發(fā)的訣竅
4 分享給他人使用
5 關(guān)于光盤中的軟件
6 關(guān)于開(kāi)源的建議
7 后記
8 畢業(yè)典禮
9 附錄
第15天
多任務(wù)(1)
挑戰(zhàn)任務(wù)切換(harib12a)
任務(wù)切換進(jìn)階(harib12b)
做個(gè)簡(jiǎn)單的多任務(wù)(1)(harib12c)
做個(gè)簡(jiǎn)單的多任務(wù)(2)(harib12d)
提高運(yùn)行速度(harib12e)
測(cè)試運(yùn)行速度(harib12f)
多任務(wù)進(jìn)階(harib12g)
1 挑戰(zhàn)任務(wù)切換(harib12a)
“話說(shuō),多任務(wù)到底是啥呢?”我們今天的內(nèi)容,就從這個(gè)問(wèn)題開(kāi)始吧。
多任務(wù),在英語(yǔ)中叫做“multitask”,顧名思義就是“多個(gè)任務(wù)”的意思。簡(jiǎn)單地說(shuō),在Windows等操作系統(tǒng)中,多個(gè)應(yīng)用程序同時(shí)運(yùn)行的狀態(tài)(也就是同時(shí)打開(kāi)好幾個(gè)窗口的狀態(tài))就叫做多任務(wù)。
對(duì)于生活在現(xiàn)代社會(huì)的各位來(lái)說(shuō),這種多任務(wù)簡(jiǎn)直是理所當(dāng)然的事情。比如你會(huì)一邊用音樂(lè)播放軟件聽(tīng)音樂(lè)一邊寫郵件,郵件寫到一半忽然有點(diǎn)東西要查,便打開(kāi)Web瀏覽器上網(wǎng)搜索。這對(duì)于大家來(lái)說(shuō)這些都是家常便飯了吧?扇绻麤](méi)有多任務(wù)的話會(huì)怎么樣呢?想寫郵件的時(shí)候就必須關(guān)掉正在播放的音樂(lè),要查東西的時(shí)候就必須先保存寫到一半的郵件,然后才能打開(kāi)Web瀏覽器……光想象一下就會(huì)覺(jué)得太不方便了。
然而在從前,沒(méi)有多任務(wù)反倒是普遍的情形(那個(gè)時(shí)候大家不用電腦聽(tīng)音樂(lè),也沒(méi)有互聯(lián)網(wǎng))。在那個(gè)年代,電腦一次只能運(yùn)行一個(gè)程序,如果要同時(shí)運(yùn)行多個(gè)程序的話,就得買好幾臺(tái)電腦才行。
就在那個(gè)時(shí)候,誕生了最初的多任務(wù)操作系統(tǒng),大家都覺(jué)得太了不起了。從現(xiàn)在開(kāi)始,我們也要準(zhǔn)備給“紙娃娃系統(tǒng)”添加執(zhí)行多任務(wù)的能力了。連這樣一個(gè)小不點(diǎn)兒操作系統(tǒng)都能夠?qū)崿F(xiàn)多任務(wù),真是讓人不由地感嘆它生逢其時(shí)呀。
稍稍思考一下我們就會(huì)發(fā)現(xiàn),多任務(wù)這個(gè)東西還真是奇妙,它究竟是怎樣做到讓多個(gè)程序同時(shí)運(yùn)行的呢?如果我們的電腦里面裝了好多個(gè)CPU的話,同時(shí)運(yùn)行多個(gè)程序倒也順理成章,但實(shí)際上就算我們只有一個(gè)CPU,照樣可以實(shí)現(xiàn)多任務(wù)。
其實(shí)說(shuō)穿了,這些程序根本沒(méi)有在同時(shí)運(yùn)行,只不過(guò)看上去好像是在同時(shí)運(yùn)行一樣:程序A運(yùn)行一會(huì)兒,接下來(lái)程序B運(yùn)行一會(huì)兒,再接下來(lái)輪到程序C,然后再回到程序A……如此反復(fù),有點(diǎn)像日本忍者的“分身術(shù)”呢(笑)。
為了讓這種分身術(shù)看上去更完美,需要讓操作系統(tǒng)盡可能快地切換任務(wù)。如果10秒才切換一次,那就連人眼都能察覺(jué)出來(lái)了,同時(shí)運(yùn)行多個(gè)程序的戲碼也就穿幫了。再有,如果我們給程序C發(fā)出一個(gè)按鍵指令,正巧這個(gè)瞬間系統(tǒng)切換到了程序A的話,我們就不得不等上20秒,才能重新輪到程序C對(duì)按鍵指令作出反應(yīng)。這實(shí)在是讓人抓狂啊(哭)。
在一般的操作系統(tǒng)中,這個(gè)切換的動(dòng)作每0.01~0.03秒就會(huì)進(jìn)行一次。當(dāng)然,切換的速度越快,讓人覺(jué)得程序是在同時(shí)運(yùn)行的效果也就越好。不過(guò),CPU進(jìn)行程序切換(我們稱為“任務(wù)切換”)這個(gè)動(dòng)作本身就需要消耗一定的時(shí)間,這個(gè)時(shí)間大約為0.0001秒左右,不同的CPU及操作系統(tǒng)所需的時(shí)間也有所不同。如果CPU每0.0002秒切換一次任務(wù)的話,該CPU處理能力的50%都要被任務(wù)切換本身所消耗掉。這意味著,如果同時(shí)運(yùn)行2個(gè)程序,每個(gè)程序的速度就只有單獨(dú)運(yùn)行時(shí)的1/4,這樣你會(huì)覺(jué)得開(kāi)心嗎?如果變成這種結(jié)果,那還不如干脆別搞多任務(wù)呢。
相比之下,即便是每0.001秒切換一次任務(wù),單單在任務(wù)切換上面也要消耗CPU處理能力的10%。大概有人會(huì)想,10%也沒(méi)什么大不了的吧?可如果你看看速度快10%的CPU賣多少錢,說(shuō)不定就會(huì)恍然大悟,“對(duì)啊,只要優(yōu)化一下任務(wù)切換間隔,就相當(dāng)于一分錢也不花,便換上了比現(xiàn)在更快的CPU嘛……”(笑),你也就明白了浪費(fèi)10%也是很不值得的。正是因?yàn)檫@個(gè)原因,任務(wù)切換的間隔最短也得0.01秒左右,這樣一來(lái)只有1%的處理能力消耗在任務(wù)切換上,基本上就可以忽略不計(jì)了。
關(guān)于多任務(wù)是什么的問(wèn)題,已經(jīng)大致講得差不多了,接下來(lái)我們來(lái)看看如何讓CPU來(lái)處理多任務(wù)。
當(dāng)你向CPU發(fā)出任務(wù)切換的指令時(shí),CPU會(huì)先把寄存器中的值全部寫入內(nèi)存中,這樣做是為了當(dāng)以后切換回這個(gè)程序的時(shí)候,可以從中斷的地方繼續(xù)運(yùn)行。接下來(lái),為了運(yùn)行下一個(gè)程序,CPU會(huì)把所有寄存器中的值從內(nèi)存中讀取出來(lái)(當(dāng)然,這個(gè)讀取的地址和剛剛寫入的地址一定是不同的,不然就相當(dāng)于什么都沒(méi)變嘛),這樣就完成了一次切換。我們前面所說(shuō)的任務(wù)切換所需要的時(shí)間,正是對(duì)內(nèi)存進(jìn)行寫入和讀取操作所消耗的時(shí)間。
接下來(lái)我們來(lái)看看寄存器中的內(nèi)容是怎樣寫入內(nèi)存里去的。下面這個(gè)結(jié)構(gòu)叫做“任務(wù)狀態(tài)段”(task status segment),簡(jiǎn)稱TSS。TSS有16位和32位兩個(gè)版本,這里我們使用32位版。顧名思義,TSS也是內(nèi)存段的一種,需要在GDT中進(jìn)行定義后使用。
參考上面的結(jié)構(gòu)定義,TSS共包含26個(gè)int成員,總計(jì)104字節(jié)(摘自CPU的技術(shù)資料),我特意把它們分成4行來(lái)寫。從開(kāi)頭的backlink起,到cr3為止的幾個(gè)成員,保存的不是寄存器的數(shù)據(jù),而是與任務(wù)設(shè)置相關(guān)的信息,在執(zhí)行任務(wù)切換的時(shí)候這些成員不會(huì)被寫入(backlink除外,某些情況下是會(huì)被寫入的)。后面的部分中我們會(huì)用到這里的設(shè)定,不過(guò)現(xiàn)在你完全可以先忽略它。
第2行的成員是32位寄存器,第3行是16位寄存器,應(yīng)該沒(méi)必要解釋了吧……不對(duì),eip好像到現(xiàn)在還沒(méi)講過(guò)呢。EIP的全稱是"extended instruction pointer",也就是"擴(kuò)展指令指針寄存器"的意思。這里的"擴(kuò)展"代表它是一個(gè)32位寄存器,也就是說(shuō)其對(duì)應(yīng)的16位版本叫做IP,類比一下的話,跟EAX與AX之間的關(guān)系是一樣的。
EIP是CPU用來(lái)記錄下一條需要執(zhí)行的指令位于內(nèi)存中哪個(gè)地址的寄存器,因此它才被稱為"指令指針"。如果沒(méi)有這個(gè)寄存器,記性不好的CPU就會(huì)忘記自己正在運(yùn)行哪里的程序,于是程序就沒(méi)辦法正常運(yùn)行了。每執(zhí)行一條指令,EIP寄存器中的值就會(huì)自動(dòng)累加,從而保證一直指向下一條指令所在的內(nèi)存地址。
說(shuō)點(diǎn)題外話,JMP指令實(shí)際上是一個(gè)向EIP寄存器賦值的指令。JMP 0x1234這種寫法,CPU會(huì)解釋為MOV EIP,0x1234,并向EIP賦值。也就是說(shuō),這條指令其實(shí)是篡改了CPU記憶中下一條該執(zhí)行的指令的地址,蒙了CPU一把。這樣一來(lái),CPU在讀取下一條指令時(shí),就會(huì)去讀取0x1234這個(gè)地址中的指令。你看,這不就相當(dāng)于是做了一個(gè)跳轉(zhuǎn)嗎?
對(duì)了,如果你在匯編語(yǔ)言里用MOV EIP,0x1234這種寫法是會(huì)出錯(cuò)的,還是不要嘗試的好。在匯編語(yǔ)言中,應(yīng)該使用JMP 0x1234來(lái)代替MOV EIP,0x1234。
如果在TSS中將EIP寄存器的值記錄下來(lái),那么當(dāng)下次再返回這個(gè)任務(wù)的時(shí)候,CPU就可以明白應(yīng)該從哪里讀取程序來(lái)運(yùn)行了。
按照常識(shí),段寄存器應(yīng)該是16位的才對(duì),可是在TSS數(shù)據(jù)結(jié)構(gòu)中卻定義成了int(也就是DWORD)類型。我們可以大膽想象一下,說(shuō)不定英特爾公司的人將來(lái)會(huì)把段寄存器變成32位的,這樣想想也挺有意思的呢(笑)。
第4行的ldtr和iomap也和第1行的成員一樣,是有關(guān)任務(wù)設(shè)置的部分,因此在任務(wù)切換時(shí)不會(huì)被CPU寫入。也許你會(huì)想,那就和第1行一樣,暫時(shí)先忽略好了--但那可是絕對(duì)不行的!如果胡亂賦值的話,任務(wù)就無(wú)法正常切換了,在這里我們先將ldtr置為0,將iomap置為0x40000000就好了。
關(guān)于TSS的話題暫且先告一段落,我們回來(lái)繼續(xù)講任務(wù)切換的方法。要進(jìn)行任務(wù)切換,其實(shí)還得用JMP指令。JMP指令分為兩種,只改寫EIP的稱為near模式,同時(shí)改寫EIP和CS的稱為far模式,在此之前我們使用的JMP指令基本上都是near模式的。不記得CS是什么了?CS就是代碼段(code segment)寄存器啦。
說(shuō)起來(lái)我們其實(shí)用過(guò)一次far模式的JMP指令,就在asmhead.nas的"bootpack啟動(dòng)"的最后一句(見(jiàn)8.5節(jié))。
JMP DWORD 2*8:0x0000001b
這條指令在向EIP存入0x1b的同時(shí),將CS置為2*8(=16)。像這樣在JMP目標(biāo)地址中帶冒號(hào)(:)的,就是far模式的JMP指令。
如果一條JMP指令所指定的目標(biāo)地址段不是可執(zhí)行的代碼,而是TSS的話,CPU就不會(huì)執(zhí)行通常的改寫EIP和CS的操作,而是將這條指令理解為任務(wù)切換。也就是說(shuō),CPU會(huì)切換到目標(biāo)TSS所指定的任務(wù),說(shuō)白了,就是JMP到一個(gè)任務(wù)那里去了。
CPU每次執(zhí)行帶有段地址的指令時(shí),都會(huì)去確認(rèn)一下GDT中的設(shè)置,以便判斷接下來(lái)要執(zhí)行的JMP指令到底是普通的far-JMP,還是任務(wù)切換。也就是說(shuō),從匯編程序翻譯出來(lái)的機(jī)器語(yǔ)言來(lái)看,普通的far-JMP和任務(wù)切換的far-JMP,指令本身是沒(méi)有任何區(qū)別的。
好了,枯燥的講解就到這里,讓我們實(shí)際做一次任務(wù)切換吧。我們準(zhǔn)備兩個(gè)任務(wù):任務(wù)A和任務(wù)B,嘗試從A切換到B。
首先,我們需要?jiǎng)?chuàng)建兩個(gè)TSS:任務(wù)A的TSS和任務(wù)B的TSS。
本次的HariMain節(jié)選
struct TSS32 tss_a, tss_b;
向它們的ldtr和iomap分別存入合適的值。
本次的HariMain節(jié)選
tss_a.ldtr = 0;
tss_a.iomap = 0x40000000;
tss_b.ldtr = 0;
tss_b.iomap = 0x40000000;
接著將它們兩個(gè)在GDT中進(jìn)行定義。
本次的HariMain節(jié)選
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);
set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);
將tss_a定義在gdt的3號(hào),段長(zhǎng)限制為103字節(jié),tss_b也采用類似的定義。
現(xiàn)在兩個(gè)TSS都創(chuàng)建好了,該進(jìn)行實(shí)際的切換了。
我們向TR寄存器存入3 * 8這個(gè)值,這是因?yàn)槲覀儎偛虐旬?dāng)前運(yùn)行的任務(wù)定義為GDT的3號(hào)。TR寄存器以前沒(méi)有提到過(guò),它的作用是讓CPU記住當(dāng)前正在運(yùn)行哪一個(gè)任務(wù)。當(dāng)進(jìn)行任務(wù)切換的時(shí)候,TR寄存器的值也會(huì)自動(dòng)變化,它的名字也就是"task register"(任務(wù)寄存器)的縮寫。我們每次給TR寄存器賦值的時(shí)候,必須把GDT的編號(hào)乘以8,因?yàn)橛⑻貭柟揪褪沁@樣規(guī)定的。如果你有意見(jiàn)的話,可以打電話找英特爾的大叔投訴哦(笑)。
給TR寄存器賦值需要使用LTR指令,不過(guò)用C語(yǔ)言做不到。唉,各位是不是都已經(jīng)見(jiàn)怪不怪了?啥?你早就料到了?(笑)所以說(shuō),正如你所料,我們只能把它寫進(jìn)naskfunc.nas里面。
本次的HariMain節(jié)選
load_tr(3 * 8);
本次的naskfunc.nas節(jié)選
_load_tr: ; void load_tr(int tr);
LTR [ESP+4] ; tr
RET
對(duì)了,LTR指令的作用只是改變TR寄存器的值,因此執(zhí)行了LTR指令并不會(huì)發(fā)生任務(wù)切換。
要進(jìn)行任務(wù)切換,我們必須執(zhí)行far模式的跳轉(zhuǎn)指令,可惜far跳轉(zhuǎn)這事C語(yǔ)言還是無(wú)能為力,這種語(yǔ)言還真是不方便啊。沒(méi)辦法,這個(gè)函數(shù)我們也得在naskfunc.nas里創(chuàng)建。
本次的naskfunc.nas節(jié)選
_taskswitch4: ; void taskswitch4(void);
JMP 4*8:0
RET
也許有人會(huì)問(wèn),在JMP指令后面寫個(gè)RET有意義嗎?也對(duì),通常情況下確實(shí)沒(méi)意義,因?yàn)橐呀?jīng)跳轉(zhuǎn)到別的地方了嘛,后面再寫什么指令也不會(huì)被執(zhí)行了。不過(guò),用作任務(wù)切換的JMP指令卻不太一樣,在切換任務(wù)之后,再返回這個(gè)任務(wù)的時(shí)候,程序會(huì)從這條JMP指令之后恢復(fù)運(yùn)行,也就是執(zhí)行JMP后面的RET,從匯編語(yǔ)言函數(shù)返回,繼續(xù)運(yùn)行C語(yǔ)言主程序。
另外,如果far-JMP指令是用作任務(wù)切換的話,地址段(冒號(hào)前面的4*8的部分)要指向TSS這一點(diǎn)比較重要,而偏移量(冒號(hào)后面的0的部分)并沒(méi)有什么實(shí)際作用,會(huì)被忽略掉,一般來(lái)說(shuō)像這樣寫0就可以了。
現(xiàn)在我們需要在HariMain的某個(gè)地方來(lái)調(diào)用taskswitch(),可到底該寫在哪里呢?唔,有了,就放在顯示"10[sec]"的語(yǔ)句后面好了。也就是說(shuō),程序啟動(dòng)10秒以后進(jìn)行任務(wù)切換。
本次的HariMain節(jié)選
} else if (i == 10) { /* 10秒計(jì)時(shí)器*/
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
taskswitch4(); /*這里! */
} else if (i == 3) { /* 3秒計(jì)時(shí)器 */
大功告成了?不對(duì),我們還沒(méi)準(zhǔn)備好tss_b呢。在任務(wù)切換的時(shí)候需要讀取tss_b的內(nèi)容,因此我們得在TSS中定義好寄存器的初始值才行。
本次的HariMain節(jié)選
tss_b.eip = (int) &task_b_main;
tss_b.eflags = 0x00000202; /* IF = 1; */
tss_b.eax = 0;
tss_b.ecx = 0;
tss_b.edx = 0;
tss_b.ebx = 0;
tss_b.esp = task_b_esp;
tss_b.ebp = 0;
tss_b.esi = 0;
tss_b.edi = 0;
tss_b.es = 1 * 8;
tss_b.cs = 2 * 8;
tss_b.ss = 1 * 8;
tss_b.ds = 1 * 8;
tss_b.fs = 1 * 8;
tss_b.gs = 1 * 8;
乍看之下,貌似會(huì)有很多看不懂的地方吧,我們從后半段對(duì)寄存器賦值的地方開(kāi)始看。這里我們給cs置為GDT的2號(hào),其他寄存器都置為GDT的1號(hào),asmhead.nas的時(shí)候也是一樣的。也就是說(shuō),我們這次使用了和bootpack.c相同的地址段。當(dāng)然,如果你用別的地址段也沒(méi)問(wèn)題,不過(guò)這次我們只是想隨便做個(gè)任務(wù)切換的實(shí)驗(yàn)而已,這種麻煩的事情還是以后再說(shuō)吧。
繼續(xù)看剩下的部分,關(guān)于eflags的賦值,如果把STI后的EFLAGS的值通過(guò)io_load_eflags賦給變量的話,該變量的值就顯示為0x00000202,因此在這里就直接使用了這個(gè)值,僅此而已。如果還有看不懂的地方,大概就是eip和esp的部分了吧。
在eip中,我們需要定義在切換到這個(gè)任務(wù)的時(shí)候,要從哪里開(kāi)始運(yùn)行。在這里我們先把task_b_main這個(gè)函數(shù)的內(nèi)存地址賦值給它。
本次的bootpack.c節(jié)選
void task_b_main(void)
{
for (;;) { io_hlt(); }
}
這個(gè)函數(shù)只執(zhí)行了一個(gè)HLT,沒(méi)有任何實(shí)際作用,后面我們會(huì)對(duì)它進(jìn)行各種改造,現(xiàn)在就先這樣吧。
task_b_esp是專門為任務(wù)B所定義的棧。有人可能會(huì)說(shuō),直接用任務(wù)A的棧不就好了嗎?那可不行,如果真這么做的話,棧就會(huì)混成一團(tuán),程序也無(wú)法正常運(yùn)行。
本次的HariMain節(jié)選
int task_b_esp;
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
總之先寫成這個(gè)樣子了。我們?yōu)槿蝿?wù)B的棧分配了64KB的內(nèi)存,并計(jì)算出棧底的內(nèi)存地址。請(qǐng)各位回憶一下向棧PUSH數(shù)據(jù)(入棧)的動(dòng)作,ESP中存入的應(yīng)該棧末尾的地址,而不是棧開(kāi)頭的地址。
好了,我們已經(jīng)講解得夠多了,現(xiàn)在總算是萬(wàn)事俱備啦,馬上"make run"一下吧。這個(gè)程序如果運(yùn)行正常的話應(yīng)該是什么樣子呢?嗯,啟動(dòng)之后的10秒內(nèi),還是跟以前一樣的,10秒一到便執(zhí)行任務(wù)切換,task_b_main開(kāi)始運(yùn)行。因?yàn)閠ask_b_main只有一句HLT,所以接下來(lái)程序就全部停止了,鼠標(biāo)和鍵盤也應(yīng)該都沒(méi)有反應(yīng)了。
唔……這樣看起來(lái)好像很無(wú)聊啊,算了,總之我們先來(lái)"make run"吧。10秒鐘的等待還真是漫長(zhǎng)……哇!停了停了!
看來(lái)我們的首次任務(wù)切換獲得了圓滿成功。
……