從零實現一個操作系統-day12

我的博客:startcraft

中斷控制芯片的初始化

參考:https://blog.csdn.net/longintchar/article/details/79439466
中斷控制芯片8259A由主從兩個芯片級聯而成

從片的輸出接在主片的IRQ2上,所以一共可以接受15個設備的中斷信號,中斷控制芯片的作用就是管理外接設備的中斷
首先要對芯片進行初始化,初始化該芯片就是初始化一組寄存器ICW寄存器
當地址線的A0=0時主片的端口的0x20,從片的端口是0xA0

ICW1

首先設置ICW1,ICW1寄存器一共有8位,作用分別爲

這裏我們給它設置爲0x11即0001 0001,就是D4和D0爲1
在idt.c中的init_idt()函數最前面加上
ICW芯片組的初始化是有順序的,必須從ICW1到ICW4

//初始化主從芯片的ICW1
//0001 0001
outb(0x20,0x11);
outb(0xA0,0x11);

outb就是之前在common.c裏實現的cpu對cpu外設備的寫函數

ICW2

然後是初始化ICW2,它的高五位表示送出的中斷號的高五位,低三位由芯片根據當前的IR0-IR7自動填入對應的二進制串
通過前面的設計知道0-31號中斷是cpu保留的,所以這裏主片的IR0就從第32號中斷開始,從片的IR0就是第40號中斷。
如何設置ICW2呢,在ICW1設置之後,地址線A0會變成1,這時代表設置的就是ICW2,當A0爲1時,主片的端口是0x21,從片的端口是0xA1
所以我們在上面的代碼下面加上

//初始化主片的ICW2,讓主片的中斷號從32-39
//0010 0xxx
outb(0x21,0x20);
//初始化從片的ICW2,讓從片的中斷號從40-47
//0010 1xxx
outb(0xA1,0x28);

ICW3

此時地址線A0還是1
主片的ICW3的8位代表的是連接從片的情況,哪一位爲1就代表哪一位的IRx連接了從片,這裏是IR2連接從片所以值爲0x04(0000 0100)
從片的ICW3高五位爲0,低三位表示的是該從片連接的是主片的哪一個IR,所以這裏設置成0x02代表該從片連接的是主片的IR2
加上如下代碼

//設置從片連接主片的IR2
outb(0x21,0x04);
outb(0xA1,0x02);

ICW4


這裏設置成0x01。表示 8259A 芯片被設置成普通全嵌套、非緩衝、非自動結束中斷方式,並且用於 8086 及其兼容系統。

//設置芯片以8086方式工作
outb(0x21,0x01);
outb(0xA1,0x01);

設置完ICW之後芯片就可以工作了,在工作過程中可以設置OCW芯片組來控制工作狀態,OCW的使用不需要按順序了

OCW1

現在設置一下OCW1,OCW1的八位,哪一位爲1代表屏蔽哪一級中斷,現在我們都不屏蔽

//開放所有中斷
outb(0x21,0x00); 
outb(0xA1,0x00);

現在初始化就完成了。

IRQ的處理

IRQ的處理和ISR函數的處理類似,首先定義IRQ函數
在idt.h最後加上

// IRQ 處理函數
void irq_handler(pt_regs *regs);

// 定義IRQ
#define  IRQ0     32    // 電腦系統計時器
#define  IRQ1     33    // 鍵盤
#define  IRQ2     34    // 與 IRQ9 相接,MPU-401 MD 使用
#define  IRQ3     35    // 串口設備
#define  IRQ4     36    // 串口設備
#define  IRQ5     37    // 建議聲卡使用
#define  IRQ6     38    // 軟驅傳輸控制使用
#define  IRQ7     39    // 打印機傳輸控制使用
#define  IRQ8     40    // 即時時鐘
#define  IRQ9     41    // 與 IRQ2 相接,可設定給其他硬件
#define  IRQ10    42    // 建議網卡使用
#define  IRQ11    43    // 建議 AGP 顯卡使用
#define  IRQ12    44    // 接 PS/2 鼠標,也可設定給其他硬件
#define  IRQ13    45    // 協處理器使用
#define  IRQ14    46    // IDE0 傳輸控制使用
#define  IRQ15    47    // IDE1 傳輸控制使用

// 聲明 IRQ 函數
// IRQ:中斷請求(Interrupt Request)
void irq0();        // 電腦系統計時器
void irq1();        // 鍵盤
void irq2();        // 與 IRQ9 相接,MPU-401 MD 使用
void irq3();        // 串口設備
void irq4();        // 串口設備
void irq5();        // 建議聲卡使用
void irq6();        // 軟驅傳輸控制使用
void irq7();        // 打印機傳輸控制使用
void irq8();        // 即時時鐘
void irq9();        // 與 IRQ2 相接,可設定給其他硬件
void irq10();       // 建議網卡使用
void irq11();       // 建議 AGP 顯卡使用
void irq12();       // 接 PS/2 鼠標,也可設定給其他硬件
void irq13();       // 協處理器使用
void irq14();       // IDE0 傳輸控制使用
void irq15();       // IDE1 傳輸控制使用

然後去idt_s.s中添加相應的處理過程,這部分和isr很相似,唯一不同的是IRQ的宏有兩個參數,因爲IRQx的中斷號不是x

; 構造中斷請求的宏
%macro IRQ 2
[GLOBAL irq%1]
irq%1:
    cli
    push 0
    push %2
    jmp irq_common_stub
%endmacro

IRQ   0,    32  ; 電腦系統計時器
IRQ   1,    33  ; 鍵盤
IRQ   2,    34  ; 與 IRQ9 相接,MPU-401 MD 使用
IRQ   3,    35  ; 串口設備
IRQ   4,    36  ; 串口設備
IRQ   5,    37  ; 建議聲卡使用
IRQ   6,    38  ; 軟驅傳輸控制使用
IRQ   7,    39  ; 打印機傳輸控制使用
IRQ   8,    40  ; 即時時鐘
IRQ   9,    41  ; 與 IRQ2 相接,可設定給其他硬件
IRQ  10,    42  ; 建議網卡使用
IRQ  11,    43  ; 建議 AGP 顯卡使用
IRQ  12,    44  ; 接 PS/2 鼠標,也可設定給其他硬件
IRQ  13,    45  ; 協處理器使用
IRQ  14,    46  ; IDE0 傳輸控制使用
IRQ  15,    47  ; IDE1 傳輸控制使用

[GLOBAL irq_common_stub]
[EXTERN irq_handler]
irq_common_stub:
    pusha                    ; pushes edi, esi, ebp, esp, ebx, edx, ecx, eax
    
    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
    call irq_handler
    add esp, 4
    
    pop ebx                   ; 恢復原來的數據段描述符
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    mov ss, bx
    
    popa                     ; Pops edi,esi,ebp...
    add esp, 8           ; 清理壓棧的 錯誤代碼 和 ISR 編號
    iret                 ; 出棧 CS, EIP, EFLAGS, SS, ESP
.end:

然後就是去idt.c中構造這些IRQ的中斷描述符和實現IRQ處理函數了

init_idt()
{
    ......
    idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
    idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);
    idt_set_gate(34, (uint32_t)irq2, 0x08, 0x8E);
    idt_set_gate(35, (uint32_t)irq3, 0x08, 0x8E);
    idt_set_gate(36, (uint32_t)irq4, 0x08, 0x8E);
    idt_set_gate(37, (uint32_t)irq5, 0x08, 0x8E);
    idt_set_gate(38, (uint32_t)irq6, 0x08, 0x8E);
    idt_set_gate(39, (uint32_t)irq7, 0x08, 0x8E);
    idt_set_gate(40, (uint32_t)irq8, 0x08, 0x8E);
    idt_set_gate(41, (uint32_t)irq9, 0x08, 0x8E);
    idt_set_gate(42, (uint32_t)irq10, 0x08, 0x8E);
    idt_set_gate(43, (uint32_t)irq11, 0x08, 0x8E);
    idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
    idt_set_gate(45, (uint32_t)irq13, 0x08, 0x8E);
    idt_set_gate(46, (uint32_t)irq14, 0x08, 0x8E);
    idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E);
    ......
}
// IRQ處理函數
void irq_handler (pt_regs *regs)
{
    //32-39是主片處理的中斷
    if(regs->int_no<40)
        //給主片發送重置信號
        outb(0x20,0x20);
    else
        //給從片發送重置信號
        outb(0xA0,0x20);
    if(interrupt_handlers[regs->int_no])
        interrupt_handlers[regs->int_no](regs);
}

這裏的重置信號其實是OCW2控制的

設置時鐘中斷

時鐘中斷是操作系統運行的脈搏,通過定時產生一箇中斷來讓操作系統進行對進程進行調度之類的操作
時鐘中斷由Intel 8253 (8254)芯片產生,所以要先對它進行初始化
driver/timer.c

#include "timer.h"
#include "debug.h"
#include "common.h"
#include "idt.h"

void timer_callback(pt_regs *regs)
{
    static uint32_t tick = 0;
    printk_color(rc_black, rc_blue, "Tick: %d\n", tick++);
}

void init_timer(uint32_t frequency)
{
    // 註冊時間相關的處理函數
    register_interrupt_handler(IRQ0, timer_callback);

    // Intel 8253/8254 PIT芯片 I/O端口地址範圍是40h~43h
    // 輸入頻率爲 1193180/frequency 即每秒中斷次數
    uint32_t divisor = 1193180 / frequency;

    // D7 D6 D5 D4 D3 D2 D1 D0
    // 0  0  1  1  0  1  1  0
    // 即就是 36 H
    // 設置 8253/8254 芯片工作在模式 3 下
    outb(0x43, 0x36);

    // 拆分低字節和高字節
    uint8_t low = (uint8_t)(divisor & 0xFF);
    uint8_t hign = (uint8_t)((divisor >> 8) & 0xFF);
    
    // 分別寫入低字節和高字節
    outb(0x40, low);
    outb(0x40, hign);
}

timer_callback函數就產生中斷是調用的中斷處理函數,這裏只是測試就寫了打印
8253/8254芯片的工作模式3下是方波發生器方式,該方式下產生的方波的頻率爲輸入時鐘頻率的N分之一,該芯片輸入時鐘是1193180HZ,N就是我們設置給它的參數,所以我們想它每10ms(100HZ)產生一次IRQ0的信號也就是每10ms產生一個方波,那麼N取值就是1193180/100
對應的頭文件include/timer.h

#ifndef INCLUDE_TIMER_H_ 
#define INCLUDE_TIMER_H_

#include "types.h"

void init_timer(uint32_t frequency);

#endif // INCLUDE_TIMER_H_

修改init/entry.c測試一下

#include "console.h" 
#include "timer.h"
#include "debug.h"
#include "gdt.h" 
#include "idt.h"
int kern_entry()
{
    init_debug();
    init_gdt();
    init_idt();
    console_clear();

    printk_color(rc_black, rc_green, "Hello, OS kernel!\n");
    init_timer(100);
    //開啓中斷
    asm volatile("sti");
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章