日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

当前操作系统缺少黑体等字体_操作系统开发之——中断

發布時間:2024/10/14 windows 71 豆豆
生活随笔 收集整理的這篇文章主要介紹了 当前操作系统缺少黑体等字体_操作系统开发之——中断 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這里先提交一個代碼的錯誤,之前運行過快,沒看出刷屏的問題:

// 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#XMSIMD(單指令多數據)浮點異常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接收到一個中斷后,通過中斷向量號在表中定位描述符,在該描述符中找到該中斷處理程序的起始地址,接著執行該中斷處理程序。中斷描述符表里面可以包含以下任意一種門描述符:
  • 任務門描述符

  • 中斷門描述符

  • 陷阱門描述符

  • 調用門描述符

他們的數據結構如下(每個門的上面是高32位,下面是低32位):

調用門的結構是這樣的:

上面的英文我們見過很多次了,筆者就不再翻譯了,我們只需要中斷門描述符,其他門描述符讀者感興趣可以自行研究。我們直接用C語言表示該數據結構:// include/interrupt.h#ifndef _INTERRUPT_H#define _INTERRUPT_H#include typedef union Type_S { struct { uint16_t Reserved:5; uint16_t SetZero:3; uint16_t P:1; uint16_t DPL:2; uint16_t Type_Flag:5; // |0 D 1 1 0| D: Size of gate: 1 = 32 bits; 0 = 16 bits } __attribute__((packed)); uint16_t All;} Type_S;typedef struct IDT_S { // High 32bits uint16_t Offset0_15; uint16_t Segment_Selector; // Low 32bits Type_S Type; uint16_t Offset16_31;} __attribute__((packed)) IDT_S;typedef struct IDTR_S { uint16_t Limite; uint32_t Base;} __attribute__((packed)) IDTR_S;#define IDT_BASE 0x00000000#define IDT_SIZE 0xFF#define INT_GATE 0x8E00 // 1000 1110 0000 0000#endif // _INTERRUPT_HType_S其實寫成uint16_t就可以了,只不過筆者“嚴格”遵守門描述符的數據結構而已,__attribute__((packed))的是GNU專有的語法,gcc編譯器在編譯結構體部分的時候會自動根據結構體的數據結構添加一些數據類型進行內存對齊,使其運行效率提高,但是底層的東西不能這么做,是什么就是什么,packed就是為了告訴編譯器不要“自作聰明”。接下來我們還要有一個IDT描述符的初始化函數:// kernel/interrupt.c#include #include #include static void init_IDT_Descriptor(uint16_t Segment_Selector, uint32_t Offset, uint16_t Type, IDT_S *IDT);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;}說到string.h,我們需要自己實現,里面的模塊以后會越用越多,全部實現的篇幅太長,這里只展示暫時用到的:// libraries/string.c#include void* memcpy(void* dst, const void* src, uint8_t size) { char *d; const char *s; if (dst == NULL || src == NULL) return NULL; if ((char*)dst > ((char*)src + sizeof(src)) || ((char*)dst < (char*)src)) { d = (char*)dst; s = (char*)src; while (size--) *d++ = *s++; } else { d = ((char*)dst + size - 1); s = ((char*)src + size -1); while (size --) *d-- = *s--; } return dst;}void* memset(void* dst, uint32_t val, uint32_t size) { for (; 0 < size; size--) { *(char*)dst = val; dst++; } return dst;}uint32_t memcmp(void* buf1,void* buf2, uint32_t size) { while (size --> 0) { if (*(uint32_t*)buf1++ != *(uint32_t*)buf2++) { return 0; } } return 1;}void* memmove(void *dst, const void *src, size_t n) { char *tmp; const char *s; if (dst <= src) { tmp = dst; s = src; while (n--) *tmp++ = *s++; } else { tmp = dst; tmp += n; s = src; s += n; while (n--) *--tmp = *--s; } return dst;}

實現中斷管理

接下來就是初始化整個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"公眾號,了解更多消息

總結

以上是生活随笔為你收集整理的当前操作系统缺少黑体等字体_操作系统开发之——中断的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。