当前操作系统缺少黑体等字体_操作系统开发之——中断
這里先提交一個代碼的錯誤,之前運行過快,沒看出刷屏的問題:
// kernel/console.c...void init_console(void) {... // Before: // console_fixed_height = ScreenHeight - 16; // console_fixed_height = (ScreenHeight / 16 - 1) * 16;...}...void console_roll(void) {... // Before: // if (console_y > console_fixed_height) { // if (console_y >= console_fixed_height) {...}Logo字符和信息是筆者自己加的
接下來,終于到了內核開發的核心部分:中斷。
中斷基本概念
中斷,顧名思義就是中斷當前任務并轉去做其他事務:當你正在看一本書時,突然房間有個電話,你就先把書反扣在桌面上,然后去接電話,當你接完電話回來時,又把書扣回來繼續看。
粗糙地用計算機術語描述就是:CPU在有序執行一段程序時,中斷控制芯片突然傳送了個信息請求CPU處理,這時CPU暫停當前執行的程序,然后將當前執行程序的各個寄存器的值和其他數據壓入堆棧,轉而去執行中斷程序,然后回來將原來棧里的數據彈出,接著執行之前的程序。
沒有中斷,操作系統是沒有靈魂的:你在鍵盤上敲一個鍵,顯示一個圖像,字符緩沖區輸出一段文本,操作一個文件,鼠標的隨便滑動,網絡數據的傳輸,各種外設協同、驅動功能,進程調度等等都是建立在完善的中斷系統之下完成的,相信讀者此時已經了解到中斷的重要性。失去了中斷機制,操作系統就只是個單純的“死循環”。
中斷的分類
外部中斷(硬件中斷)
所謂外部中斷,就是指CPU外部發生的中斷,由硬件發起。常發生于輸入輸出設備、時鐘,計時器,電源,網卡等部件和外設。外部中斷有兩根信號線:INTR(INTeRrupt)和 NMI(Non Maskable Interrupt)。INTR傳來的中斷比較無關緊要,CPU甚至可以不去處理,例如網卡和硬盤的中斷請求,CPU可以晚一些執行,我們常稱作可屏蔽中斷;而NMI就很嚴重了,基本上都必須立馬處理,比如內存讀寫出錯,電源掉電等。
由于外設眾多,執行中斷程序的時候可能又會有另一個中斷發生,因此中斷程序一般都有個特點:執行要盡快,函數要可重入(常發生于多線程中的全局變量保護的問題)。
像這些可屏蔽中斷,在Linux中,分為上半部分和下半部分:上半部分處理比較重要的,要快速完成的程序,下部分就是沒那么重要的程序,一般在CPU空閑時或者合適的時期來處理,這里有個很生動的例子:
拿網卡舉例子,網絡中的數據通過網線到達網卡后,首先會被存儲到網卡自己的緩沖區中,這個緩沖區容量不大(比起內存來說是非常小的),即使很大也有寫滿的那天,所以里面的數據必須立即被 CPU拿走,否則由于網卡緩沖區中無空余空間,后續到來的數據只能丟掉。鑒于這個刻不容緩的理由,網卡會立即發中斷通知 CPU:“數據到了,趕緊取走”,這話說得無比堅定,絲毫沒有商量的意思,CPU 立即放下手里的工作(其實并不是真地立即放下,怎么也得把當前正在執行的指令執行完,指令的執行必須是原子操作一氣呵成,哪有執行一半指令的道理),馬上執行網卡的中斷處理程序,將網卡緩沖區中的數據拷貝到內核緩沖區中,至此,救火工作算是完成了,這就是所說的上半部。CPU 拿到網絡數據后,處理數據的工作就不那么緊急了,它將在下半部中完成,這部分將在適當的時機被啟動。來源:《操作系統真象還原》
隨著時代的發展,很多外設之間可以使用通道機制和DMA方式進行工作,大大得減輕了CPU的負擔。之前的圖形模式的Linear Frame Buffer就是其中一個例子。
內部中斷(軟件中斷和異常)
中斷源都是軟件(可能有些人對軟件的定義仍然是:Application。事實上,一系列按照特定順序組織的計算機數據和指令的集合都是軟件,你可以說操作系統是個很大的系統軟件,也可以說BIOS是在ROM里躺著的軟件,甚至一個dll、lib、so、數據、文檔都可以叫做軟件)發起的,常見于除數為0,運算溢出,指令的單步運行,程序運行至斷點等等。至于其他,Intel官方以及列出個表了:
| 中斷向量號 | 助記符 | 描述 | 起源 |
| 0 | #DE | 除 0 異常 | DIV和IDIV指令 |
| 1 | #DB | 調試異常 | 任何代碼或數據引用 |
| 2 | / | NMI 中斷 | 不可屏蔽的外部中斷 |
| 3 | #BP | 斷點異常 | INT 3指令 |
| 4 | #OF | 溢出 | INTO指令 |
| 5 | #BR | 對數組的引用超出邊界 | BOUND指令 |
| 6 | #UD | 無效或未定義的操作碼 | UD指令或保留的操作碼 |
| 7 | #NM | 設備不可用(無數學協處理器) | 浮點或WAIT / FWAIT指令 |
| 8 | #DF | 雙重故障(有錯誤代碼) | 可以生成異常,NMI或INTR的任何指令 |
| 9 | #MF | 協處理器跨段操作 | 浮點指令 |
| 10 | #TS | 無效TSS(有錯誤代碼) | 任務切換或TSS訪問 |
| 11 | #NP | 段不存在(有錯誤代碼) | 正在加載段寄存器或訪問系統段 |
| 12 | #SS | 棧錯誤(有錯誤代碼) | 堆棧操作和SS寄存器加載 |
| 13 | #GP | 常規保護(有錯誤代碼) | 任何內存引用和其他保護檢查 |
| 14 | #PF | 頁故障(有錯誤代碼) | 任何內存引用 |
| 15 | 保留 | ||
| 16 | #MF | 浮點處理單元錯誤 | 浮點或WAIT / FWAIT指令 |
| 17 | #AC | 對齊檢查 | 存儲器中的任何數據引用 |
| 18 | #MC | 機器檢查 | 錯誤代碼(如果有)和來源取決于型號 |
| 19 | #XM | SIMD(單指令多數據)浮點異常 | SIMD浮點指令 |
| 20 | #VE | 虛擬化 | EPT異常 |
| 21-31 | 保留 | ||
| 32-255 | 可屏蔽中斷 | 來自INTR引腳或INT n指令的外部中斷 |
翻譯過來非常糟糕,讀者可在intel白皮書的6.4.1看到原文和更多解釋。對于表中的中斷向量號,0~19號中斷被CPU占用,20-31號中斷被Intel保留,32~255號屬于用戶可自定義中斷。不過我們一般都會中斷按照習慣指定固定的設備。比如32號是timer中斷,33號是鍵盤中斷等等。下面我們開始介紹點實質性的東西。
中斷描述符表
中斷描述符表(Interrupt Descriptor Table,IDT)是保護模式下用于存儲中斷處理程序入口的表,CPU接收到一個中斷后,通過中斷向量號在表中定位描述符,在該描述符中找到該中斷處理程序的起始地址,接著執行該中斷處理程序。中斷描述符表里面可以包含以下任意一種門描述符:任務門描述符
中斷門描述符
陷阱門描述符
調用門描述符
實現中斷管理
接下來就是初始化整個IDT了,這部分內容比較多,但是都很簡單。我們先捋一捋中斷得整個流程,注意了,我們現在不考慮用戶態和內核態(也就是特權級的問題):首先就是CPU(我們目前只講單核處理器)接收到一個中斷(向量號),這時保存現場,Intel官方是這么說的:1.?將EFLAGS,CS和EIP寄存器的當前內容(按此順序)壓入堆棧。
2.?將錯誤代碼(如果適用)壓入堆棧。
3.?從中斷門加載新代碼段和新指令指針的段選擇器(或陷阱門)分別進入CS和EIP寄存器。
4.?如果調用是通過中斷門進行的,請清除EFLAGS寄存器中的IF標志。
5.?開始執行處理程序過程。
也就是說,我們要把當前所有寄存器和標準位保存起來,那么我們需要這個數據結構:
// include/interrupt.h...typedef struct Registers_S { uint32_t ds; uint32_t edi; uint32_t esi; uint32_t ebp; uint32_t esp; uint32_t ebx; uint32_t edx; uint32_t ecx; uint32_t eax; uint32_t Interrupt_Number; // 這里應該是Vector才對 uint32_t Error_Code; uint32_t eip; uint32_t cs; uint32_t eflags; uint32_t user_esp; uint32_t user_ss;}?Registers_S;...前面我們說過各個中斷號的歸屬,我們需要為每個中斷號實現一個中斷服務程序,以及中斷注冊函數,那么接下來我們的頭文件就是這樣:
// include/interrupt.h...extern void ISR0(void);extern void ISR1(void);extern void ISR2(void);extern void ISR3(void);extern void ISR4(void);extern void ISR5(void);extern void ISR6(void);extern void ISR7(void);extern void ISR8(void);extern void ISR9(void);extern void ISR10(void);extern void ISR11(void);extern void ISR12(void);extern void ISR13(void);extern void ISR14(void);extern void ISR15(void);extern void ISR16(void);extern void ISR17(void);extern void ISR18(void);extern void ISR19(void);extern void ISR20(void);extern void ISR21(void);extern void ISR22(void);extern void ISR23(void);extern void ISR24(void);extern void ISR25(void);extern void ISR26(void);extern void ISR27(void);extern void ISR28(void);extern void ISR29(void);extern void ISR30(void);extern?void?ISR31(void);void init_IDT(void);...但是具體的函數都是保存現場,實現起來都基本是一樣的(有錯誤號和無錯誤號差一個指令),一個個寫太費事了,nasm為開發者提供了一個一勞永逸的辦法:宏匯編。這可是個好東西:
; kernel/_Interrupt.asm[bits 32]extern ISR_Handler; nasm的宏定義; 有錯誤號使用空指令%define ERROR_CODE nop; 沒有錯誤號就Push無效錯誤號%define NO_ERROR_CODE push 0; %macro 宏函數 參數個數%macro ISR_CODE 2; 參數1:%1,參數2:%2,...[global ISR%1]ISR%1: cli ; 關閉中斷 %2 ; 估計情況決定是否放置Push無效錯誤號 push byte %1 ; Push中斷向量號 pusha mov ax,ds push eax ; 保存數據段描述符 mov ax,0x10 mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax push esp ; Registers_S指針 call ISR_Handler ; 調用相應中斷處理函數這里可以使用,當前僅為測試 ; ISR_Handler%1的方法實現對不同中斷的不同處理 ; 也可以使用[ISR_Handler + %1*4]函數指針數組的表示方法 add esp,4 pop ebx ; 恢復原來的數據段描述符 mov ds,bx mov es,bx mov fs,bx mov gs,bx mov ss,bx popa add esp,8 ; 跳過Error_Code iret ; 中斷處理函數不能返回,需要使用iret或iretd打斷;宏函數結束%endmacro;宏函數名 參數1,參數2,...ISR_CODE 0,NO_ERROR_CODEISR_CODE 1,NO_ERROR_CODEISR_CODE 2,NO_ERROR_CODEISR_CODE 3,NO_ERROR_CODEISR_CODE 4,NO_ERROR_CODEISR_CODE 5,NO_ERROR_CODEISR_CODE 6,NO_ERROR_CODEISR_CODE 7,NO_ERROR_CODEISR_CODE 8,ERROR_CODEISR_CODE 9,NO_ERROR_CODEISR_CODE 10,ERROR_CODEISR_CODE 11,ERROR_CODEISR_CODE 12,ERROR_CODEISR_CODE 13,ERROR_CODEISR_CODE 14,ERROR_CODEISR_CODE 15,NO_ERROR_CODEISR_CODE 16,NO_ERROR_CODEISR_CODE 17,ERROR_CODEISR_CODE 18,NO_ERROR_CODEISR_CODE 19,NO_ERROR_CODEISR_CODE 20,NO_ERROR_CODEISR_CODE 21,NO_ERROR_CODEISR_CODE 22,NO_ERROR_CODEISR_CODE 23,NO_ERROR_CODEISR_CODE 24,NO_ERROR_CODEISR_CODE 25,NO_ERROR_CODEISR_CODE 26,NO_ERROR_CODEISR_CODE 27,NO_ERROR_CODEISR_CODE 28,NO_ERROR_CODEISR_CODE 29,NO_ERROR_CODEISR_CODE 30,NO_ERROR_CODEISR_CODE 31,NO_ERROR_CODE有了這些,C語言這邊就小菜一碟了:
// kernel/interrupt.c#include #include #include IDT_S IDT[256];IDTR_S?IDTR;static void init_IDT_Descriptor(uint16_t Segment_Selector, uint32_t Offset, uint16_t Type, IDT_S *IDT) { IDT->Offset0_15 = Offset & 0xffff; IDT->Segment_Selector = Segment_Selector; IDT->Type.All = Type; IDT->Offset16_31 = (Offset & 0xffff0000) >> 16; return;}void?ISR_Handler(Registers_S?*Registers)?{ printk(KERN_EMERG"InterruptNumber: %d\n", Registers->Interrupt_Number);}void?init_IDT(void)?{????//?這里不加括號編譯器會警告! IDTR.Limite = (sizeof(IDT_S) << 8) - 1; IDTR.Base = (uint32_t)&IDT; memset((uint8_t*)&IDT, 0, sizeof(IDT_S) << 8); init_IDT_Descriptor(0x08, (uint32_t)ISR0, INT_GATE, &IDT[0]); init_IDT_Descriptor(0x08, (uint32_t)ISR1, INT_GATE, &IDT[1]); init_IDT_Descriptor(0x08, (uint32_t)ISR2, INT_GATE, &IDT[2]); init_IDT_Descriptor(0x08, (uint32_t)ISR3, INT_GATE, &IDT[3]); init_IDT_Descriptor(0x08, (uint32_t)ISR4, INT_GATE, &IDT[4]); init_IDT_Descriptor(0x08, (uint32_t)ISR5, INT_GATE, &IDT[5]); init_IDT_Descriptor(0x08, (uint32_t)ISR6, INT_GATE, &IDT[6]); init_IDT_Descriptor(0x08, (uint32_t)ISR7, INT_GATE, &IDT[7]); init_IDT_Descriptor(0x08, (uint32_t)ISR8, INT_GATE, &IDT[8]); init_IDT_Descriptor(0x08, (uint32_t)ISR9, INT_GATE, &IDT[9]); init_IDT_Descriptor(0x08, (uint32_t)ISR10, INT_GATE, &IDT[10]); init_IDT_Descriptor(0x08, (uint32_t)ISR11, INT_GATE, &IDT[11]); init_IDT_Descriptor(0x08, (uint32_t)ISR12, INT_GATE, &IDT[12]); init_IDT_Descriptor(0x08, (uint32_t)ISR13, INT_GATE, &IDT[13]); init_IDT_Descriptor(0x08, (uint32_t)ISR14, INT_GATE, &IDT[14]); init_IDT_Descriptor(0x08, (uint32_t)ISR15, INT_GATE, &IDT[15]); init_IDT_Descriptor(0x08, (uint32_t)ISR16, INT_GATE, &IDT[16]); init_IDT_Descriptor(0x08, (uint32_t)ISR17, INT_GATE, &IDT[17]); init_IDT_Descriptor(0x08, (uint32_t)ISR18, INT_GATE, &IDT[18]); init_IDT_Descriptor(0x08, (uint32_t)ISR19, INT_GATE, &IDT[19]); init_IDT_Descriptor(0x08, (uint32_t)ISR20, INT_GATE, &IDT[20]); init_IDT_Descriptor(0x08, (uint32_t)ISR21, INT_GATE, &IDT[21]); init_IDT_Descriptor(0x08, (uint32_t)ISR22, INT_GATE, &IDT[22]); init_IDT_Descriptor(0x08, (uint32_t)ISR23, INT_GATE, &IDT[23]); init_IDT_Descriptor(0x08, (uint32_t)ISR24, INT_GATE, &IDT[24]); init_IDT_Descriptor(0x08, (uint32_t)ISR25, INT_GATE, &IDT[25]); init_IDT_Descriptor(0x08, (uint32_t)ISR26, INT_GATE, &IDT[26]); init_IDT_Descriptor(0x08, (uint32_t)ISR27, INT_GATE, &IDT[27]); init_IDT_Descriptor(0x08, (uint32_t)ISR28, INT_GATE, &IDT[28]); init_IDT_Descriptor(0x08, (uint32_t)ISR29, INT_GATE, &IDT[29]); init_IDT_Descriptor(0x08, (uint32_t)ISR30, INT_GATE, &IDT[30]); init_IDT_Descriptor(0x08, (uint32_t)ISR31, INT_GATE, &IDT[31]); // // 加載IDTR // __asm__ ("lidtl (IDTR)");}下面當然是測試一下了:看,編譯一氣呵成!中斷函數注冊
每個中斷函數肯定不一樣,我們就可以使用函數指針數組,這種情況下,有些函數可以先不用實現:
// include/interrupt.h...typedef?void?(*Interrupt_Handler)(Registers_S*);void RegisterInterrupt(uint8_t Number, Interrupt_Handler Handler);...// kernel/interrupt.c...Interrupt_Handler?InterruptHandlers[256]?=?{NULL};...// 這里可以動態注冊中斷函數void RegisterInterrupt(uint8_t Number, Interrupt_Handler Handler) {????InterruptHandlers[Number]?=?Handler; return;}//?這里保存函數尚未實現時不會調用空函數void ISR_Handler(Registers_S *Registers) { if (InterruptHandlers[Registers->Interrupt_Number] != NULL) { InterruptHandlers[Registers->Interrupt_Number](Registers); } else { printk(KERN_EMERG"InterruptNumber: %d\n", Registers->Interrupt_Number); }}...運行結果還是一樣的,今天就到這了!
關注"GuEes"公眾號,了解更多消息
總結
以上是生活随笔為你收集整理的当前操作系统缺少黑体等字体_操作系统开发之——中断的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python虚拟环境搭建mac_mac搭
- 下一篇: python最早引入json的版本_详解