TOP
0
0
【簡體曬書區】 單本79折,5本7折,活動好評延長至5/31,趕緊把握這一波!
30天自製操作系統(附光碟)(簡體書)
滿額折

30天自製操作系統(附光碟)(簡體書)

人民幣定價:129.8 元
定  價:NT$ 779 元
優惠價:87678
領券後再享88折
海外經銷商無庫存,到貨日平均30天至45天
可得紅利積點:20 點
相關商品
商品簡介
作者簡介
目次
書摘/試閱

商品簡介

自製操作系統只需30天
從零開始編寫一個五臟俱全的圖形操作系統
39.1KB迷你係統30天自製完成
實現多任務、漢字顯示、文件壓縮,還能聽歌看圖玩遊戲
日本編程天才川合秀實作品
揭開CPU、內存、磁盤以及操作系統底層工作模式的神秘面紗

《30天自製操作系統》是一本兼具趣味性、實用性與學習性的操作系統圖書。作者從計算機的構造、匯編語言、C語言開始解說,讓讀者在實踐中掌握算法。在這本書的指導下,從零編寫所有代碼,30天后就可以製作出一個具有窗口系統的32位多任務操作系。
《30天自製操作系統》適合操作系統愛好者和程序設計人員閱讀。

作者簡介

川合秀實(Hidemi Kawai),生於1975年,是一位以"輕量化"編程思想見長的"非主流"開發者。2000年因自行開發的OSASK項目而名聲大噪。OSASK是一個開源的32位微型操作系統,它並非以Linux等內核為基礎,而是完全從零開始開發,在一張軟盤的容量下實現了GUI、多任務、多語言等高級特性,啟動時間只需1秒。本書的內容可以看成是作者以OSASK為藍本,教會讀者從零開始開發一個操作系統,同時可以讓初學者在編寫操作系統的過程中,了解操作系統背後更多的知識。

“好想編寫一個操作系統呀!”筆者的朋友曾說這是所有程序員都曾經懷揣的一個夢想。說“所有的程序員”可能有點誇張了,不過作為程序員的夢想,它至少也應該能排進前十名吧。
也許很多人覺得編寫操作系統是個天方夜譚,這一定是操作系統業界的一個陰謀(笑)。他們故意讓大家相信編寫操作系統是一件非常困難的事情,這樣就可以高價兜售自己開發的操作系統,而且操作系統的作者還會被頂禮膜拜。那麼實際情況又怎麼樣呢?和別的程序相比,其實編寫操作系統並沒有那麼難,至少筆者的感覺是這樣。
在各位讀者之中,也許有人曾經挑戰過操作系統的編寫,但因為太難而放棄了。擁有這樣經歷的人也許不會認同筆者的觀點。其實你錯了,你的失敗並不是因為編寫操作系統太難,而是因為沒有人告訴你那其實是一件很簡單的事而已。
不僅是編寫操作系統,任何事都是一樣的。如果講解的人認為它很難,那就不可能把它講述得通俗易懂,即便是同樣的內容,也會講得無比複雜。這樣的講解,肯定是很難懂的。
那麼,你想不想和筆者一起再挑戰一次呢?如果你曾經夢想過編寫自己的操作系統,一定會覺得樂在其中的。
可能有人會說,這本書足足有700多頁,怎麼會“有趣”和“簡單”呢?唔,這麼一說筆者也覺得挺心虛的,不過其實也只是長了那麼一點點啦。平均下來的話,每天只有大約23頁的內容,你看,也沒有那麼長吧?
這本書的文風非常輕鬆,也許你不知不覺中就會讀得很快。但是這樣的話可能印像不會很深,最好還是能靜下心來慢慢地讀。書中所展示的程序代碼和文字的說明同樣重要,因此也希望大家仔細閱讀。只要注意這些,理解本書的內容就應該沒有問題了。
在本書中,我們使用C語言和彙編語言來編寫操作系統,不過不必擔心,你可以在閱讀本書的同時來逐步學習關於這些編程語言的知識。本書在這方面寫得非常仔細,如果能有人通過本書終於把C語言中的指針給搞懂了,那筆者的目的也就達到了。即便是從這樣的水平開始,30天后你也能夠編寫出一個很棒的操作系統,請大家拭目以待吧!

目次

目錄

第0天著手開發之前1
1 前言1
2 何謂操作系統3
3 開發操作系統的各種方法4
4 無知則無畏4
5 如何開發操作系統6
6 操作系統開發中的困難7
7 學習本書時的注意事項(重要!) 9
8 各章內容摘要11

第1天從計算機結構到彙編程序入門13
1 先動手操作13
2 做了些什麼19
3 初次體驗彙編程序22
4 加工潤色24

第2天彙編語言學習與Makefile入門28
1 介紹文本編輯器28
2 繼續開發29
3 先製作啟動區40
4 Makefile入門41

第3天進入32位模式並導入C語言45
1 製作真正的IPL 45
2 試錯50
3 讀到18扇區51
4 讀入10個柱面52
5 著手開發操作系統54
6 從啟動區執行操作系統55
7 確認操作系統的執行情況56
8 32位模式前期準備57
9 開始導入C語言59
10 實現HLT(harib00j) 62

第4天C語言與畫面顯示的練習64
1 用C語言實現內存寫入(harib01a) 64
2 條紋圖案(harib01b) 67
3 挑戰指針(harib01c) 69
4 指針的應用(1)(harib01d) 74
5 指針的應用(2)(harib01e) 74
6 色號設定(harib01f) 75
7 繪製矩形(harib01g) 84
8 今天的成果(harib01h) 86

第5天結構體、文字顯示與GDT/IDT初始化88
1 接收啟動信息(harib02a) 88
2 試用結構體(harib02b) 89
3 試用箭頭記號(harib02c) 91
4 顯示字符(harib02d) 91
5 增加字體(harib02e) 94
6 顯示字符串(harib02f) 96
7 顯示變量值(harib02g) 97
8 顯示鼠標指針(harib02h) 99
9 GDT與IDT的初始化(harib02i) 101

第6天分割編譯與中斷處理108
1 分割源文件(harib03a) 108
2 整理Makefile(harib03b) 109
3 整理頭文件(harib03c) 110
4 意猶未盡112
5 初始化PIC(harib03d) 115
6 中斷處理程序的製作(harib03e) 119

第7天FIFO與鼠標控制125
1 獲取按鍵編碼(hiarib04a) 125
2 加快中斷處理(hiarib04b) 127
3 製作FIFO緩衝區(hiarib04c) 130
4 改善FIFO緩衝區(hiarib04d) 133
5 整理FIFO緩衝區(hiarib04e) 135
6 總算講到鼠標了(harib04f) 138
7 從鼠標接受數據(harib04g) 141

第8天鼠標控制與32位模式切換144
1 鼠標解讀(1)(harib05 a) 144
2 稍事整理(harib05b) 146
3 鼠標解讀(2)(harib05c) 148
4 移動鼠標指針(harib05d) 151
5 通往32位模式之路153

第9天內存管理162
1 整理源文件(harib06a) 162
2 內存容量檢查(1)(harib06b) 163
3 內存容量檢查(2)(harib06c) 168
4 挑戰內存管理(harib06d) 172

第10天疊加處理181
1 內存管理(續)(harib07a) 181
2 疊加處理(harib07b) 184
3 提高疊加處理速度(1)(harib07c) 194
4 提高疊加處理速度(2)(harib07d) 197

第11天製作窗口201
1 鼠標顯示問題(harib08a) 201
2 實現畫面外的支持(harib08b) 202
3 shtctl的指定省略(harib08c) 203
4 顯示窗口(harib08d) 206
5 小實驗(harib08e) 208
6 高速計數器(harib08f) 209
7 消除閃爍(1)(harib08g) 211
8 消除閃爍(2)(harib08h) 214

第12天定時器(1) 220
1 使用定時器(harib09a) 220
2 計量時間(harib09b) 224
3 超時功能(harib09c) 225
4 設定多個定時器(harib09d) 228
5 加快中斷處理(1)(harib09e) 232
6 加快中斷處理(2)(harib09f) 234
7 加快中斷處理(3)(harib09g) 236

第13天定時器(2) 240
1 簡化字符串顯示(harib10a) 240
2 重新調整FIFO緩衝區(1)(harib10b) 241
3 測試性能(harib10c~harib10f) 243
4 重新調整FIFO緩衝區(2)(harib10g) 246
5 加快中斷處理(4)(harib10h) 253
6 使用“哨兵”簡化程序(harib10i) 257

第14天高分辨率及鍵盤輸入262
1 繼續測試性能(harib11a~harib11c) 262
2 提高分辨率(1)(harib11d) 266
3 提高分辨率(2)(harib11e) 269
4 鍵盤輸入(1)(harib11f) 272
5 鍵盤輸入(2)(harib11g) 275
6 追記內容(1)(harib11h) 277
7 追記內容(2)(harib11i) 279

第15天多任務(1) 282
1 挑戰任務切換(harib12a) 282
2 任務切換進階(harib12b) 289
3 做個簡單的多任務(1)(harib12c) 291
4 做個簡單的多任務(2)(harib12d) 293
5 提高運行速度(harib12e) 294
6 測試運行速度(harib12f) 297
7 多任務進階(harib12g) 299

第16天多任務(2) 304
1 任務管理自動化(harib13a) 304
2 讓任務休眠(harib13b) 308
3 增加窗口數量(harib13c) 313
4 設定任務優先級(1)(harib13d) 317
5 設定任務優先級(2)(harib13e) 320

第17天命令行窗口329
1 閒置任務(harib14a) 329
2 創建命令行窗口(harib14b) 331
3 切換輸入窗口(harib14c) 334
4 實現字符輸入(harib14d) 337
5 符號的輸入(harib14e) 341
6 大寫字母與小寫字母(harib14f) 343
7 對各種鎖定鍵的支持(harib14g) 346

第18天dir命令350
1 控制光標閃爍(1)(harib15 a) 350
2 控制光標閃爍(2)(harib15b) 352
3 對回車鍵的支持(harib15c) 355
4 對窗口滾動的支持(harib15d) 357
5 mem命令(harib15e) 359
6 cls命令(harib15f) 363
7 dir命令(harib15g) 366

第19天應用程序371
1 type命令(harib16a) 371
2 type命令改良(harib16b) 378
3 對FAT的支持(harib16c) 382
4 代碼整理(harib16d) 387
5 第一個應用程序(harib16e) 387

第20天API 392
1 程序整理(harib17a) 392
2 顯示單個字符的API(1)(harib17b) 399
3 顯示單個字符的API(2)(harib17c) 402
4 結束應用程序(harib17d) 403
5 不隨操作系統版本而改變的API(harib17e) 405
6 為應用程序自由命名(harib17f) 408
7 當心寄存器(harib17g) 410
8 用API顯示字符串(harib17h) 412

第21天保護操作系統418
1 攻克難題——字符串顯示API(harib18a) 418
2 用C語言編寫應用程序(harib18b) 420
3 保護操作系統(1)(harib18c) 424
4 保護操作系統(2)(harib18d) 426
5 對異常的支持(harib18e) 431
6 保護操作系統(3)(harib18f) 434
7 保護操作系統(4)(harib18g) 435

第22天用C語言編寫應用程序443
1 保護操作系統(5)(harib19a) 443
2 幫助發現bug(harib19b) 448
3 強制結束應用程序(harib19c) 452
4 用C語言顯示字符串(1)(harib19d) 455
5 用C語言顯示字符串(2)(harib19e) 457
6 顯示窗口(harib19f) 462
7 在窗口中描繪字符和方塊(harib19g) 465

第23天圖形處理相關468
1 編寫malloc(harib20a) 468
2 畫點(harib20b) 472
3 刷新窗口(harib20c) 475
4 畫直線(harib20d) 478
5 關閉窗口(harib20e) 483
6 鍵盤輸入API(harib20f) 484
7 用鍵盤輸入來消遣一下(harib20g) 488
8 強制結束並關閉窗口(harib20h) 489

第24天窗口操作493
1 窗口切換(1)(harib21a) 493
2 窗口切換(2)(harib21b) 495
3 移動窗口(harib21c) 496
4 用鼠標關閉窗口(harib21d) 498
5 將輸入切換到應用程序窗口(harib21e) 500
6 用鼠標切換輸入窗口(harib21f) 506
7 定時器API(harib21g) 507
8 取消定時器(harib21h) 511

第25天增加命令行窗口515
1 蜂鳴器發聲(harib22a) 515
2 增加更多的顏色(1)(harib22b) 518
3 增加更多的顏色(2)(harib22c) 520
4 窗口初始位置(harib22d) 523
5 增加命令行窗口(1)(harib22e) 524
6 增加命令行窗口(2)(harib22f) 528
7 增加命令行窗口(3)(harib22g) 531
8 增加命令行窗口(4)(harib22h) 532
9 變得更像真正的操作系統(1)(harib22i) 534
10 變得更像真正的操作系統(2)(harib22j) 538

第26天為窗口移動提速541
1 提高窗口移動速度(1)(harib23a) 541
2 提高窗口移動速度(2)(harib23b) 543
3 提高窗口移動速度(3)(harib23c) 547
4 提高窗口移動速度(4)(harib23d) 549
5 啟動時只打開一個命令行窗口(harib23e) 551
6 增加更多的命令行窗口(harib23f) 554
7 關閉命令行窗口(1)(harib23g) 555
8 關閉命令行窗口(2)(harib23h) 561
9 start命令(harib23i) 563
10 ncst命令(harib23j) 564

第27天LDT與庫571
1 先來修復bug(harib24a) 571
2 應用程序運行時關閉命令行窗口(harib24b) 573
3 保護應用程序(1)(harib24c) 577
4 保護應用程序(2)(harib24d) 580
5 優化應用程序的大小(harib24e) 583
6 庫(harib24f) 587
7 整理make環境(harib24g) 590

第28天文件操作與文字顯示598
1 alloca(1)(harib25 a) 598
2 alloca(2)(harib25b) 601
3 文件操作API(harib25c) 605
4 命令行API(harib25d) 612
5 日文文字顯示(1)(harib25e) 615
6 日文文字顯示(2)(harib25f) 624
7 日文文字顯示(3)(harib25g) 629

第29天壓縮與簡單的應用程序635
1 修復bug(harib26a) 635
2 文件壓縮(harib26b) 636
3 標準函數644
4 非矩形窗口(harib26c) 647
5 bball(harib26d) 648
6 外星人遊戲(harib26e) 651

第30天高級的應用程序659
1 命令行計算器(harib27a) 659
2 文本閱覽器(harib27b) 664
3 MML播放器(harib27c) 671
4 圖片閱覽器(harib27d) 679
5 IPL的改良(harib27e) 683
6 光盤啟動(harib27f) 688

第31天寫在開發完成之後690
1 繼續開發要靠大家的努力690
2 關於操作系統的大小692
3 操作系統開發的訣竅693
4 分享給他人使用694
5 關於光盤中的軟件695
6 關於開源的建議696
7 後記698
8 畢業典禮703
9 附錄704

書摘/試閱

第15天
多任務(1)
挑戰任務切換(harib12a)
任務切換進階(harib12b)
做個簡單的多任務(1)(harib12c)
做個簡單的多任務(2)(harib12d)
提高運行速度(harib12e)
測試運行速度(harib12f)
多任務進階(harib12g)
1 挑戰任務切換(harib12a)
“話說,多任務到底是啥呢?”我們今天的內容,就從這個問題開始吧。
多任務,在英語中叫做“multitask”,顧名思義就是“多個任務”的意思。簡單地說,在Windows等操作系統中,多個應用程序同時運行的狀態(也就是同時打開好幾個窗口的狀態)就叫做多任務。
對於生活在現代社會的各位來說,這種多任務簡直是理所當然的事情。比如你會一邊用音樂播放軟件聽音樂一邊寫郵件,郵件寫到一半忽然有點東西要查,便打開Web瀏覽器上網搜索。這對於大家來說這些都是家常便飯了吧。可如果沒有多任務的話會怎麼樣呢?想寫郵件的時候就必須關掉正在播放的音樂,要查東西的時候就必須先保存寫到一半的郵件,然後才能打開Web瀏覽器……光想像一下就會覺得太不方便了。
然而在從前,沒有多任務反倒是普遍的情形(那個時候大家不用電腦聽音樂,也沒有互聯網)。在那個年代,電腦一次只能運行一個程序,如果要同時運行多個程序的話,就得買好幾台電腦才行。
就在那個時候,誕生了最初的多任務操作系統,大家都覺得太了不起了。從現在開始,我們也要準備給“紙娃娃系統”添加執行多任務的能力了。連這樣一個小不點兒操作系統都能夠實現多任務,真是讓人不由地感嘆它生逢其時呀。
稍稍思考一下我們就會發現,多任務這個東西還真是奇妙,它究竟是怎樣做到讓多個程序同時運行的呢?如果我們的電腦裡面裝了好多個CPU的話,同時運行多個程序倒也順理成章,但實際上就算我們只有一個CPU,照樣可以實現多任務。
其實說穿了,這些程序根本沒有在同時運行,只不過看上去好像是在同時運行一樣:程序A運行一會兒,接下來程序B運行一會兒,再接下來輪到程序C,然後再回到程序A……如此反复,有點像日本忍者的“分身術”呢(笑)。
為了讓這種分身術看上去更完美,需要讓操作系統盡可能快地切換任務。如果10秒才切換一次,那就連人眼都能察覺出來了,同時運行多個程序的戲碼也就穿幫了。再有,如果我們給程序C發出一個按鍵指令,正巧這個瞬間系統切換到了程序A的話,我們就不得不等上20秒,才能重新輪到程序C對按鍵指令作出反應。這實在是讓人抓狂啊(哭)。
在一般的操作系統中,這個切換的動作每0.01~0.03秒就會進行一次。當然,切換的速度越快,讓人覺得程序是在同時運行的效果也就越好。不過,CPU進行程序切換(我們稱為“任務切換”)這個動作本身就需要消耗一定的時間,這個時間大約為0.0001秒左右,不同的CPU及操作系統所需的時間也有所不同。如果CPU每0.0002秒切換一次任務的話,該CPU處理能力的50%都要被任務切換本身所消耗掉。這意味著,如果同時運行2個程序,每個程序的速度就只有單獨運行時的1/4,這樣你會覺得開心嗎?如果變成這種結果,那還不如乾脆別搞多任務呢。
相比之下,即便是每0.001秒切換一次任務,單單在任務切換上面也要消耗CPU處理能力的10%。大概有人會想,10%也沒什麼大不了的吧?可如果你看看速度快10%的CPU賣多少錢,說不定就會恍然大悟,“對啊,只要優化一下任務切換間隔,就相當於一分錢也不花,便換上了比現在更快的CPU嘛……”(笑),你也就明白了浪費10%也是很不值得的。正是因為這個原因,任務切換的間隔最短也得0.01秒左右,這樣一來只有1%的處理能力消耗在任務切換上,基本上就可以忽略不計了。
關於多任務是什麼的問題,已經大致講得差不多了,接下來我們來看看如何讓CPU來處理多任務。
當你向CPU發出任務切換的指令時,CPU會先把寄存器中的值全部寫入內存中,這樣做是為了當以後切換回這個程序的時候,可以從中斷的地方繼續運行。接下來,為了運行下一個程序,CPU會把所有寄存器中的值從內存中讀取出來(當然,這個讀取的地址和剛剛寫入的地址一定是不同的,不然就相當於什麼都沒變嘛),這樣就完成了一次切換。我們前面所說的任務切換所需要的時間,正是對內存進行寫入和讀取操作所消耗的時間。
接下來我們來看看寄存器中的內容是怎樣寫入內存裡去的。下面這個結構叫做“任務狀態段”(task status segment),簡稱TSS。TSS有16位和32位兩個版本,這裡我們使用32位版。顧名思義,TSS也是內存段的一種,需要在GDT中進行定義後使用。
參考上面的結構定義,TSS共包含26個int成員,總計104字節(摘自CPU的技術資料),我特意把它們分成4行來寫。從開頭的backlink起,到cr3為止的幾個成員,保存的不是寄存器的數據,而是與任務設置相關的信息,在執行任務切換的時候這些成員不會被寫入(backlink除外,某些情況下是會被寫入的)。後面的部分中我們會用到這裡的設定,不過現在你完全可以先忽略它。
第2行的成員是32位寄存器,第3行是16位寄存器,應該沒必要解釋了吧……不對,eip好像到現在還沒講過呢。EIP的全稱是"extended instruction pointer",也就是"擴展指令指針寄存器"的意思。這裡的"擴展"代表它是一個32位寄存器,也就是說其對應的16位版本叫做IP,類比一下的話,跟EAX與AX之間的關係是一樣的。
EIP是CPU用來記錄下一條需要執行的指令位於內存中哪個地址的寄存器,因此它才被稱為"指令指針"。如果沒有這個寄存器,記性不好的CPU就會忘記自己正在運行哪裡的程序,於是程序就沒辦法正常運行了。每執行一條指令,EIP寄存器中的值就會自動累加,從而保證一直指向下一條指令所在的內存地址。
說點題外話,JMP指令實際上是一個向EIP寄存器賦值的指令。JMP 0x1234這種寫法,CPU會解釋為MOV EIP,0x1234,並向EIP賦值。也就是說,這條指令其實是篡改了CPU記憶中下一條該執行的指令的地址,蒙了CPU一把。這樣一來,CPU在讀取下一條指令時,就會去讀取0x1234這個地址中的指令。你看,這不就相當於是做了一個跳轉嗎?
對了,如果你在彙編語言裡用MOV EIP,0x1234這種寫法是會出錯的,還是不要嘗試的好。在彙編語言中,應該使用JMP 0x1234來代替MOV EIP,0x1234。
如果在TSS中將EIP寄存器的值記錄下來,那麼當下次再返回這個任務的時候,CPU就可以明白應該從哪裡讀取程序來運行了。
按照常識,段寄存器應該是16位的才對,可是在TSS數據結構中卻定義成了int(也就是DWORD)類型。我們可以大膽想像一下,說不定英特爾公司的人將來會把段寄存器變成32位的,這樣想想也挺有意思的呢(笑)。
第4行的ldtr和iomap也和第1行的成員一樣,是有關任務設置的部分,因此在任務切換時不會被CPU寫入。也許你會想,那就和第1行一樣,暫時先忽略好了--但那可是絕對不行的!如果胡亂賦值的話,任務就無法正常切換了,在這裡我們先將ldtr置為0,將iomap置為0x40000000就好了。
關於TSS的話題暫且先告一段落,我們回來繼續講任務切換的方法。要進行任務切換,其實還得用JMP指令。JMP指令分為兩種,只改寫EIP的稱為near模式,同時改寫EIP和CS的稱為far模式,在此之前我們使用的JMP指令基本上都是near模式的。不記得CS是什麼了?CS就是代碼段(code segment)寄存器啦。
說起來我們其實用過一次far模式的JMP指令,就在asmhead.nas的"bootpack啟動"的最後一句(見8.5節)。
JMP DWORD 2*8:0x0000001b
這條指令在向EIP存入0x1b的同時,將CS置為2*8(=16)。像這樣在JMP目標地址中帶冒號(:)的,就是far模式的JMP指令。
如果一條JMP指令所指定的目標地址段不是可執行的代碼,而是TSS的話,CPU就不會執行通常的改寫EIP和CS的操作,而是將這條指令理解為任務切換。也就是說,CPU會切換到目標TSS所指定的任務,說白了,就是JMP到一個任務那裡去了。
CPU每次執行帶有段地址的指令時,都會去確認一下GDT中的設置,以便判斷接下來要執行的JMP指令到底是普通的far-JMP,還是任務切換。也就是說,從彙編程序翻譯出來的機器語言來看,普通的far-JMP和任務切換的far-JMP,指令本身是沒有任何區別的。
好了,枯燥的講解就到這裡,讓我們實際做一次任務切換吧。我們準備兩個任務:任務A和任務B,嘗試從A切換到B。
首先,我們需要創建兩個TSS:任務A的TSS和任務B的TSS。
本次的HariMain節選
struct TSS32 tss_a, tss_b;
向它們的ldtr和iomap分別存入合適的值。
本次的HariMain節選
tss_a.ldtr = 0;
tss_a.iomap = 0x40000000;
tss_b.ldtr = 0;
tss_b.iomap = 0x40000000;
接著將它們兩個在GDT中進行定義。
本次的HariMain節選
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號,段長限制為103字節,tss_b也採用類似的定義。
現在兩個TSS都創建好了,該進行實際的切換了。
我們向TR寄存器存入3 * 8這個值,這是因為我們剛才把當前運行的任務定義為GDT的3號。TR寄存器以前沒有提到過,它的作用是讓CPU記住當前正在運行哪一個任務。當進行任務切換的時候,TR寄存器的值也會自動變化,它的名字也就是"task register"(任務寄存器)的縮寫。我們每次給TR寄存器賦值的時候,必須把GDT的編號乘以8,因為英特爾公司就是這樣規定的。如果你有意見的話,可以打電話找英特爾的大叔投訴哦(笑)。
給TR寄存器賦值需要使用LTR指令,不過用C語言做不到。唉,各位是不是都已經見怪不怪了啊?啥?你早就料到了?(笑)所以說,正如你所料,我們只能把它寫進naskfunc.nas裡面。
本次的HariMain節選
load_tr(3 * 8);
本次的naskfunc.nas節選
_load_tr: ; void load_tr(int tr);
LTR [ESP+4] ; tr
RET
對了,LTR指令的作用只是改變TR寄存器的值,因此執行了LTR指令並不會發生任務切換。
要進行任務切換,我們必須執行far模式的跳轉指令,可惜far跳轉這事C語言還是無能為力,這種語言還真是不方便啊。沒辦法,這個函數我們也得在naskfunc.nas裡創建。
本次的naskfunc.nas節選
_taskswitch4: ; void taskswitch4(void);
JMP 4*8:0
RET
也許有人會問,在JMP指令後面寫個RET有意義嗎?也對,通常情況下確實沒意義,因為已經跳轉到別的地方了嘛,後面再寫什麼指令也不會被執行了。不過,用作任務切換的JMP指令卻不太一樣,在切換任務之後,再返回這個任務的時候,程序會從這條JMP指令之後恢復運行,也就是執行JMP後面的RET,從彙編語言函數返回,繼續運行C語言主程序。
另外,如果far-JMP指令是用作任務切換的話,地址段(冒號前面的4*8的部分)要指向TSS這一點比較重要,而偏移量(冒號後面的0的部分)並沒有什麼實際作用,會被忽略掉,一般來說像這樣寫0就可以了。
現在我們需要在HariMain的某個地方來調用taskswitch(),可到底該寫在哪裡呢?唔,有了,就放在顯示"10[sec]"的語句後面好了。也就是說,程序啟動10秒以後進行任務切換。
本次的HariMain節選
} else if (i == 10) { /* 10秒計時器*/
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
taskswitch4(); /*這裡!*/
} else if (i == 3) { /* 3秒計時器*/
大功告成了?不對,我們還沒準備好tss_b呢。在任務切換的時候需要讀取tss_b的內容,因此我們得在TSS中定義好寄存器的初始值才行。
本次的HariMain節選
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;
乍看之下,貌似會有很多看不懂的地方吧,我們從後半段對寄存器賦值的地方開始看。這裡我們給cs置為GDT的2號,其他寄存器都置為GDT的1號,asmhead.nas的時候也是一樣的。也就是說,我們這次使用了和bootpack.c相同的地址段。當然,如果你用別的地址段也沒問題,不過這次我們只是想隨便做個任務切換的實驗而已,這種麻煩的事情還是以後再說吧。
繼續看剩下的部分,關於eflags的賦值,如果把STI後的EFLAGS的值通過io_load_eflags賦給變量的話,該變量的值就顯示為0x00000202,因此在這裡就直接使用了這個值,僅此而已。如果還有看不懂的地方,大概就是eip和esp的部分了吧。
在eip中,我們需要定義在切換到這個任務的時候,要從哪裡開始運行。在這裡我們先把task_b_main這個函數的內存地址賦值給它。
本次的bootpack.c節選
void task_b_main(void)
{
for (;;) { io_hlt(); }
}
這個函數只執行了一個HLT,沒有任何實際作用,後面我們會對它進行各種改造,現在就先這樣吧。
task_b_esp是專門為任務B所定義的棧。有人可能會說,直接用任務A的棧不就好了嗎?那可不行,如果真這麼做的話,棧就會混成一團,程序也無法正常運行。
本次的HariMain節選
int task_b_esp;
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
總之先寫成這個樣子了。我們為任務B的棧分配了64KB的內存,併計算出棧底的內存地址。請各位回憶一下向棧PUSH數據(入棧)的動作,ESP中存入的應該棧末尾的地址,而不是棧開頭的地址。
好了,我們已經講解得夠多了,現在總算是萬事俱備啦,馬上"make run"一下吧。這個程序如果運行正常的話應該是什麼樣子呢?嗯,啟動之後的10秒內,還是跟以前一樣的,10秒一到便執行任務切換,task_b_main開始運行。因為task_b_main只有一句HLT,所以接下來程序就全部停止了,鼠標和鍵盤也應該都沒有反應了。
唔……這樣看起來好像很無聊啊,算了,總之我們先來"make run"吧。10秒鐘的等待還真是漫長……哇!停了停了!
看來我們的首次任務切換獲得了圓滿成功。
……

您曾經瀏覽過的商品

購物須知

大陸出版品因裝訂品質及貨運條件與台灣出版品落差甚大,除封面破損、內頁脫落等較嚴重的狀態,其餘商品將正常出貨。

特別提醒:部分書籍附贈之內容(如音頻mp3或影片dvd等)已無實體光碟提供,需以QR CODE 連結至當地網站註冊“並通過驗證程序”,方可下載使用。

無現貨庫存之簡體書,將向海外調貨:
海外有庫存之書籍,等候約45個工作天;
海外無庫存之書籍,平均作業時間約60個工作天,然不保證確定可調到貨,尚請見諒。

為了保護您的權益,「三民網路書店」提供會員七日商品鑑賞期(收到商品為起始日)。

若要辦理退貨,請在商品鑑賞期內寄回,且商品必須是全新狀態與完整包裝(商品、附件、發票、隨貨贈品等)否則恕不接受退貨。

優惠價:87 678
海外經銷商無庫存,到貨日平均30天至45天

暢銷榜

客服中心

收藏

會員專區