第4課:中斷和異常1
聲明:轉(zhuǎn)載請保留:
譯者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目標
下載源程序
在本課中,我們學(xué)習(xí)如何處理中斷和異常。并且加入中斷處理程序到skelix內(nèi)核中,這樣可以在中斷或異常來臨時打印一些可以看得到的東西,這些程序是后面課程的基礎(chǔ)。
那么中斷和異常到底是什么咚咚呢?
舉例來說吧,小胖正在家里吃飯,吃的很歪歪的時候,MM來了一個電話,于是接電話告訴她晚上七點在村口YY樹下不見不散,這就是一個突發(fā)事件,接完電話繼續(xù)吃飯。突然小胖在米飯里面發(fā)現(xiàn)一個蟑螂,errr~~~~。只好結(jié)束晚餐了,這個就是異常了。中斷是可以返回來的,比如上面的接電話,但異常就不能返回繼續(xù)做剛才的事了。
簡單的說,中斷和異常都是停止CPU正在做的事情,強制它做另外一件事,然后回到原來的控制流上繼續(xù)執(zhí)行。中斷和異常的區(qū)別在于它們的外部觸發(fā)源,對于硬件設(shè)備來說,如鍵盤輸入,系統(tǒng)時鐘等就會觸發(fā)中斷。而保護模式下的指令執(zhí)行才會引發(fā)異常,例如除零錯誤,雙重錯誤:),INT 3指令就是主動觸發(fā)異常(可以在用戶態(tài)進行測試),表示這個進程進入調(diào)試狀態(tài)。
處理器為每個中斷或異常分配一個獨立的編號,這個編號實際上就是中斷向量表,在實模式下它是從物理地址0開始的,現(xiàn)在早已被我們的內(nèi)核‘skelix’覆蓋了,即便沒有覆蓋,我們?nèi)匀粺o法在保護模式中使用這些實模式的中斷。在Intel手冊上,有兩種外部中斷和兩種異常。
中斷分類
1)可屏蔽中斷:通過INTR引腳告訴CPU來了一個中斷,可以被寄存器設(shè)置屏蔽調(diào)
2)非可屏蔽中斷:通過NMI引腳告訴CPU來了一個中斷,不可屏蔽
下面是系統(tǒng)啟動默認設(shè)置的中斷向量:
IRQ 引腳
|
中斷向量
|
中斷
|
IRQ0
|
08
|
系統(tǒng)時鐘
|
IRQ1
|
09
|
鍵盤
|
IRQ2
|
0A
|
PIC2橋接,即從8259A
|
IRQ3
|
0B
|
COM2
|
IRQ4
|
0C
|
COM1
|
IRQ5
|
0D
|
LPT2
|
IRQ6
|
0E
|
軟盤
|
IRQ7
|
0F
|
LPT1
|
IRQ8
|
70
|
CMOS Real Time Clock
|
IRQ9
|
71
|
|
IRQ10
|
72
|
|
IRQ11
|
73
|
|
IRQ12
|
74
|
PS/2 Mouse
|
IRQ13
|
75
|
數(shù)學(xué)協(xié)處理器
|
IRQ14
|
76
|
硬盤設(shè)備 IDE0
|
IRQ15
|
77
|
硬盤設(shè)備 IDE1
|
上面表格中的IRQ是中斷控制器的物理引腳,直接連到外部硬件設(shè)備上的。在AT兼容機器上有16個引腳。
異常分離
1)處理器檢查:頁故障,陷阱等
2)程序片:例如INTO,INT 3,INT n等系統(tǒng)調(diào)用
中斷向量
|
異常
|
00
|
除零錯
|
01
|
調(diào)試異常
|
02
|
非可屏蔽中斷 (NMI)
|
03
|
斷點 (INT 3 指令)
|
04
|
溢出 (INTO 指令)
|
05
|
越界 (BOUND指令)
|
06
|
無效的指令
|
07
|
無協(xié)處理器
|
08
|
雙重錯誤
|
09
|
協(xié)處理器越界
|
0A
|
無效的 TSS
|
0B
|
段不存在
|
0C
|
棧溢出
|
0D
|
通用保護異常(內(nèi)存引用或其他檢查保護),Windows 9x藍屏就是它的杰作
|
0E
|
頁錯誤
|
0F
|
Intel 保留
|
10
|
協(xié)處理器錯誤
|
11-19
|
Intel保留
|
1A-FF
|
未使用
|
如果讀者仔細的話,可能注意到中斷和異常編號會有沖突。IRQ0到IRQ7中斷向量和異常的0x08-0x10重疊了,所以我們得處理一下。中斷的屏蔽可以通過8259A PIC(programmable interrupt controllers)設(shè)置。PIC1處理IRQ0到IRQ7,PIC2處理IRQ8到IRQ15,當中斷到來時,PIC得到信號并通知CPU。CPU收到后會停止當前任務(wù)執(zhí)行,并轉(zhuǎn)向中斷向量中指向的處理代碼。我們稱之為ISR(interrupt service routine)。8259A設(shè)置的中斷向量編號可重新分配,為了做到這點,我們需要對8259A進行編程,這個是有難度的事情,你可以在網(wǎng)絡(luò)上搜索相關(guān)的文章自己看下(否則本文可能很難看下去,如果你一點基礎(chǔ)也沒有的話,最好能很好的理解ICW和OCW的幾個命令字),下面的程序就是干這事的:
04/init.c
static void
pic_install(void) {
outb(0x11,
0x20); //
ICW1命令字,使用邊沿觸發(fā)中斷,多片8259A級聯(lián)
outb(0x11, 0xa0);
outb(0x20,
0x21); //
ICW2命令字,重新分配中斷向量編號IRQ0-IRQ7
outb(0x28, 0xa1); //
ICW2命令字,重新分配中斷向量編號IRQ8-IRQ15
outb(0x04,
0x21); //
ICW3命令字,主從8259A鏈接設(shè)置
outb(0x02, 0xa1);
outb(0x01,
0x21); //
ICW4命令字,設(shè)置EIO/AEIO模式,緩沖方式等
outb(0x01, 0xa1);
outb(0xff,
0x21); //
屏蔽IRQ0-IRQ7所有中斷
outb(0xff,
0xa1); //
屏蔽IRQ8-IRQ15所有中斷
}
一個好消息:我們現(xiàn)在開始終于使用C語言了。類似outb這樣的宏會替代一些匯編語言,這樣看起來會好過一些。不熟悉AT&T匯編的可能要補一下了,否則可能認為進度太快了。
04/include/asm.h
#define cli() __asm__ ("cli\n\t")
#define sti() __asm__ ("sti\n\t")
#define halt() __asm__ ("cli;hlt\n\t");
#define idle() __asm__ ("jmp .\n\t");
#define inb(port) (__extension__({ \
unsigned char __res; \
__asm__ ("inb %%dx,
%%al\n\t" \
:"=a"(__res) \
:"dx"(port)); \
__res; \
}))
#define outb(value, port) __asm__ ( \
"outb %%al,
%%dx\n\t"::"al"(value), "dx"(port))
#define insl(port, buf, nr) \
__asm__ ("cld;rep;insl\n\t" \
::"d"(port), "D"(buf), "c"(nr))
#define outsl(buf, nr, port) \
__asm__ ("cld;rep;outsl\n\t" \
::"d"(port), "S" (buf), "c" (nr))
現(xiàn)在我們知道了怎么重新分配中斷向量,但是另外一個問題:實模式下的中斷向量表已經(jīng)被內(nèi)核覆蓋了,怎么辦呢?我們不得不重寫它們了。這一點也不有趣。
IDT and ISR
處理器執(zhí)行中斷和異常的方法都是一樣的,當某個中斷或異常發(fā)生時,處理器停止當前任務(wù)跳轉(zhuǎn)到特定的例程中去,這個例程就是ISR。當ISR執(zhí)行完后,返回控制到原來的任務(wù)中去。那么處理器又是怎么樣路由這些中斷的呢?原因是系統(tǒng)中存在一個叫IDTR(IDT register)的寄存器,它指向內(nèi)存中的一個叫中斷描述符表的緩沖,這個描述符表就定義了所有中斷例程(即ISR)的邏輯地址等。它和GDT看起來很像,只有個別的位不同而已。IDT中為每個中斷或異常都準備了獨立的一項,我們常稱之為向量(就是上面重新分配的中斷向量編號)。IDT可以看作是一個64位長整型的數(shù)組,最多可有256項。LIDT指令可以加載IDT的地址到IDTR中,就像LGDT加載GDT地址到GDTR一樣。現(xiàn)在我們來看一下IDT描述符:
圖-0
63_______________56__55__54__53__52__51_____________48_
| 偏移地址(31到16位) |
|_______________________________________________________|
_47__46__45________________________________36________32_
| P | DPL
| 01110000 |
未使用 |
|_______________________________________________________|
31____________________________________________________16
|
描述符選擇子
|
|_______________________________________________________|
16_____________________________________________________
| 偏移地址(15到0位)
|
|_______________________________________________________|
大多數(shù)的域我們已經(jīng)很熟悉了,后面的代碼中我會詳細講解到。實際上,有多種描述符,但這里我們只用到中斷門。現(xiàn)在我們設(shè)置CPU使其路由到正確的中斷例程ISR中去,那么ISR到底是什么呢?因為中斷和異常會停止當前執(zhí)行的任務(wù)并且需要返回回來繼續(xù)執(zhí)行,所以ISR需要保存當前任務(wù)的運行環(huán)境,就是一大堆寄存器。需要在進入ISR的時候保存這些寄存器,并在離開的時候回復(fù)它們。
如果ISR代碼和當前任務(wù)特權(quán)級相同,那么ISR將會使用當前任務(wù)堆棧(這個任務(wù)一般是內(nèi)核線程,當然也有例外),或者就是切換到內(nèi)核棧中, 即從TSS(后面介紹這個咚咚)中加載新的CS,EIP和棧,并按順序保存所有的寄存器到新的堆棧中。當堆棧切換后(如果有的話),CPU會自動保存SS, ESP, EEFLAGS, CS 和 EIP等寄存器到棧中。有些異常會帶一個錯誤號(表示錯誤的一些信息),這個錯誤號也會自動壓入棧中。之后IDT中的CS和EIP將會被加載從而執(zhí)行ISR例程。因為我們使用的是中斷門,所以IF標識(EFLAGS寄存器)將會被清掉。從ISR例程返回逆序做上面的步驟即可,不值得一提。
圖1-特權(quán)級不變
_________________ _________________
|
|
|
| |
|_________________|
|_________________| |
|
|
|
| |
|_________________|
|_________________| | 棧增長方向
| Old EFLAGS
| | Old EFLAGS
| |
|_________________|
|_________________| |
| Old
CS |
| Old
CS | |
|_________________|
|_________________| |
| Old
EIP |
| Old
EIP | \|/
|_________________|
|_________________|
|
| | Error Code
|
|_________________|
|_________________|
圖2-特權(quán)級變化
_________________ _________________
| Old
SS |
| Old SS
| |
|_________________|
|_________________| |
| Old
ESP |
| Old ESP
| |
|_________________|
|_________________| | 棧增長方向
| Old EFLAGS
| | Old EFLAGS
| |
|_________________| |_________________|
|
| Old
CS |
| Old
CS | |
|_________________|
|_________________| |
| Old
EIP |
| Old
EIP | \|/
|_________________|
|_________________|
|
| | Error Code
|
|_________________|
|_________________|
我假定讀者看到這里還沒有昏菜,如果是的話找些保護模式的書補一補哦。也許看到下面的程序可能更清晰一些。在前面課程中我們在load.s程序的最后面打印"Hello World!",本課中我們先跳轉(zhuǎn)到C代碼中去執(zhí)行,稍后就是中斷和異常相關(guān)程序了。
04/load.s
.text
.globl pm_mode
.include "kernel.inc"
.org 0
pm_mode:
movl $DATA_SEL,%eax
movw
%ax, %ds
movw
%ax, %es
movw
%ax, %fs
movw
%ax, %gs
movw
%ax, %ss
movl $STACK_BOT,%esp
cld
movl $0x10200,%esi
movl $0x200, %edi
movl
$KERNEL_SECT<<7,%ecx
rep
movsl
call init
# 進入到C語言編寫的程序中
init函數(shù)將會初始化硬件和系統(tǒng)表(idt和gdt):
04/init.c
unsigned long long *idt = ((unsigned long long
*)IDT_ADDR);
unsigned long long *gdt = ((unsigned long long *)GDT_ADDR);
為了方便存取,我們使用一個long long(ia32上是8個字節(jié))表示一個描述符,GCC也許會給出警告(對于這種類型),使用--Wno-long-long 就可以了.
這個函數(shù)用來填充IDT項,index參數(shù)是索引,第二個參數(shù)offset是ISR例程的地址。
static void
isr_entry(int index, unsigned long long offset)
{ //
IDT描述符格式請參考圖-0
unsigned long long idt_entry = 0x00008e0000000000ULL |
((unsigned long
long)CODE_SEL<<16);
// 通過上面設(shè)置后,idt項值變?yōu)?span lang="EN-US">0x00008e0000080000,表示偏移地址為0,使用0x8作為描述符選擇子(即內(nèi)核代碼段描述符),該ISR是存在的且特權(quán)級DPL為0
idt_entry |= (offset<<32) &
0xffff000000000000ULL;
idt_entry |= (offset) & 0xffff;
// 上面兩行填充ISR地址
idt[index] = idt_entry;
// 填充值到idt中
}
// 這個函數(shù)安裝所有的256 ISR例程
static void
idt_install(void) {
unsigned int i = 0;
struct DESCR {
unsigned short length;
unsigned long address;
} __attribute__((packed)) idt_descr = {256*8-1,
IDT_ADDR}; // 防止默認4字節(jié)對齊,該變量占6字節(jié)
for (i=0; i<VALID_ISR;
++i) // isr數(shù)組存放著所有的ISR例程地址,后面會講到
isr_entry(i, (unsigned
int)(isr[(i<<1)+1]));
// 安裝異常,就是 i * 2 + 1
for (++i; i<256; ++i)
isr_entry(i, (unsigned
int)default_isr); //
安裝中斷
__asm__ __volatile__ ("lidt
%0\n\t"::"m"(idt_descr));
}
static void
pic_install(void) {
This function has been explained earlier outb(0x11, 0x20);
outb(0x11, 0xa0);
outb(0x20, 0x21);
outb(0x28, 0xa1);
outb(0x04, 0x21);
outb(0x02, 0xa1);
outb(0x01, 0x21);
outb(0x01, 0xa1);
outb(0xff, 0x21);
outb(0xff, 0xa1);
}
void
init(void) {
int a = 3, b = 0;
idt_install();
pic_install();
sti();
a /=
b; //
測試除零異常,看看發(fā)生了什么
}
好了,現(xiàn)在進入核心程序。我們不得不又和一些匯編程序打交道。我們知道,當硬件向PIC發(fā)送一個信號后,PIC通知處理器停止當前任務(wù)執(zhí)行,然后處理器查找ISR例程,然后執(zhí)行ISR,再返回控制流。所以ISR就是我們關(guān)注的地方:
04/isr.s
.text
.include "kernel.inc"
.globl default_isr, isr
.macro
isrnoerror
nr // 用于無錯誤碼異常的宏
isr\nr:
pushl
$0 //
push一個額外的0
pushl $\nr
jmp
isr_comm
.endm
.macro
isrerror nr
isr\nr:
pushl $\nr
jmp
isr_comm
.endm
關(guān)于宏的說明,可以參考linux中as的幫助頁。使用pinfo可以很好的瀏覽,
這個指令在我的博客上有介紹(http://www.shnenglu.com/jinglexy),
查找2007.4月份文檔即可。
使用上面兩個宏,我們可以很方便的定義中斷和異常函數(shù)及編號:
isr: .long
divide_error, isr0x00, debug_exception, isr0x01
.long breakpoint, isr0x02,
nmi, isr0x03
.long overflow,
isr0x04, bounds_check, isr0x05
.long invalid_opcode,
isr0x06, cop_not_avalid, isr0x07
.long double_fault,
isr0x08, overrun, isr0x09
.long invalid_tss,
isr0x0a,
seg_not_present, isr0x0b
.long stack_exception,
isr0x0c,
general_protection, isr0x0d
.long page_fault,
isr0x0e, reversed, isr0x0f
.long
coprocessor_error, isr0x10, reversed, isr0x11
.long reversed,
isr0x12, reversed, isr0x13
.long reversed,
isr0x14, reversed, isr0x15
.long reversed,
isr0x16, reversed, isr0x17
.long reversed,
isr0x18, reversed, isr0x19
.long reversed, isr0x1a, reversed, isr0x1b
.long reversed, isr0x1c, reversed, isr0x1d
.long reversed,
isr0x1e, reversed, isr0x1f
上面就是init.c中使用的isr例程數(shù)組,注意類似isr0x00的咚咚就是代碼地址。
圖-3
+-----------+
| old ss
| 76
+-----------+
| old esp |
72
+-----------+
| eflags
| 68
+-----------+
|
cs | 64
+-----------+
| eip
| 60
+-----------+
| 0/err
| 56
+-----------+
| isr_nr | tmp = esp
+-----------+
| eax
| 48
+-----------+
| ecx
| 44
+-----------+
| edx
| 40
+-----------+
| ebx
| 36
+-----------+
| tmp
| 32
+-----------+
| ebp
| 28
+-----------+
| esi
| 24
+-----------+
| edi
| 20
+-----------+
|
ds | 16
+-----------+
|
es | 12
+-----------+
|
fs | 8
+-----------+
|
gs | 4
+-----------+
|
ss | 0
+-----------+
恐怖的堆棧圖,我花了很多時間才把它畫出來,汗一個先:)有沒有推薦更好的工具?
對于所有的中斷和異常來說,該堆棧幀結(jié)構(gòu)都是一樣的。
isr_comm:
pushal //
依次把寄存器AX、CX、DX、BX、SP、BP、SI和DI壓棧
pushl
%ds //
入棧,入棧,入棧......
pushl %es
pushl %fs
pushl %gs
pushl %ss
movw
$DATA_SEL,%ax // 所有數(shù)據(jù)段特權(quán)級都是0
movw
%ax, %ds
movw
%ax, %es
movw
%ax, %fs
movw
%ax, %gs
movl
52(%esp),%ecx // 看上面的堆棧圖,52就是ISR例程的編號
call *isr(, %ecx,
8) // 不帶參數(shù)執(zhí)行isr例程
addl
$4, %esp // 我們當然不能popl %ss,所以就這樣跳過去了
popl %gs
popl %fs
popl %es
popl %ds
popal
addl
$8, %esp // 跳過 isr_nr 和 err_code
iret //
返回到原來的控制流繼續(xù)執(zhí)行
isrNoError
0x00
isrNoError
0x01
isrNoError
0x02
isrNoError
0x03
isrNoError
0x04
isrNoError
0x05
isrNoError
0x06
isrNoError
0x07
isrError
0x08
isrNoError
0x09
isrError
0x0a
isrError
0x0b
isrError
0x0c
isrError
0x0d
isrError
0x0e
isrNoError
0x0f
isrError
0x10
isrNoError
0x11
isrNoError
0x12
isrNoError
0x13
isrNoError
0x14
isrNoError
0x15
isrNoError
0x16
isrNoError
0x17
isrNoError
0x18
isrNoError
0x19
isrNoError
0x1a
isrNoError
0x1b
isrNoError
0x1c
isrNoError
0x1d
isrNoError
0x1e
isrNoError
0x1f
default_isr: #
硬件中斷處理例程
incb 0xb8000
movb
$2, 0xb8001
movb $0x20, %al
outb
%al, $0x20 # 發(fā)送OCW2,告訴 PIC1 ISR執(zhí)行完畢
outb %al,
$0xa0 # 發(fā)送OCW2,告訴 PIC2 ISR執(zhí)行完畢
iret
可以在這里找到OCW2的資料:
http://docs.huihoo.com/gnu_linux/own_os/interrupt-8259_5.htm,
我一直都想找一份端口大全的資料,如果哪位有可以發(fā)一份給我:
jinglexy at yahoo dot com dot cn
在現(xiàn)在階段中,所有異常暫時打印一些寄存器,只是演示一下而已:
04/exceptions.c
void
divide_error(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
info 是這個文件最后定義的一個函數(shù),它的參數(shù)就是上面圖-3中的堆棧
void
debug_exception(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
breakpoint(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
nmi(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
overflow(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
bounds_check(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
invalid_opcode(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
cop_not_avalid(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
double_fault(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
overrun(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
invalid_tss(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
seg_not_present(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
stack_exception(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
general_protection(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
page_fault(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
reversed(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
void
coprocessor_error(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
// 很好理解的函數(shù),不費口舌了這里。
info(enum KP_LEVEL kl,
unsigned int ret_ip, unsigned int ss, unsigned int gs,
unsigned int fs,
unsigned int es, unsigned int ds, unsigned int edi,
unsigned int esi,
unsigned int ebp, unsigned int esp, unsigned int ebx,
unsigned int edx,
unsigned int ecx, unsigned int eax, unsigned int
isr_nr, unsigned int err,
unsigned int eip, unsigned int cs, unsigned int
eflags,
unsigned int old_esp, unsigned int old_ss) {
static const char *exception_msg[] = {
"DIVIDE ERROR",
"DEBUG EXCEPTION",
"BREAK POINT",
"NMI",
"OVERFLOW",
"BOUNDS CHECK",
"INVALID OPCODE",
"COPROCESSOR NOT VALID",
"DOUBLE FAULT",
"OVERRUN",
"INVALID TSS",
"SEGMENTATION NOT PRESENT",
"STACK EXCEPTION",
"GENERAL PROTECTION",
"PAGE FAULT",
"REVERSED",
"COPROCESSOR_ERROR",
};
unsigned int cr2, cr3;
(void)ret_ip;
__asm__ ("movl
%%cr2, %%eax":"=a"(cr2));
__asm__ ("movl
%%cr3, %%eax":"=a"(cr3));
if (isr_nr < sizeof exception_msg)
kprintf(kl, "EXCEPTION %d:
%s\n=======================\n",
isr_nr, exception_msg[isr_nr]);
else
kprintf(kl, "INTERRUPT
%d\n=======================\n", isr_nr);
kprintf(kl, "cs:\t%x\teip:\t%x\teflags:\t%x\n",
cs, eip, eflags);
kprintf(kl, "ss:\t%x\tesp:\t%x\n", ss, esp);
kprintf(kl, "old ss:\t%x\told esp:%x\n", old_ss,
old_esp);
kprintf(kl, "errcode:%x\tcr2:\t%x\tcr3:\t%x\n",
err, cr2, cr3);
kprintf(kl, "General
Registers:\n=======================\n");
kprintf(kl, "eax:\t%x\tebx:\t%x\n", eax, ebx);
kprintf(kl, "ecx:\t%x\tedx:\t%x\n", ecx, edx);
kprintf(kl, "esi:\t%x\tedi:\t%x\tebp:\t%x\n", esi,
edi, ebp);
kprintf(kl, "Segment
Registers:\n=======================\n");
kprintf(kl, "ds:\t%x\tes:\t%x\n", ds, es);
kprintf(kl, "fs:\t%x\tgs:\t%x\n", fs, gs);
}
最后,還得改一下Makefile。
04/Makefile
AS=as -Iinclude
LD=ld
CC=gcc #
不用說,我們開始使用gcc了
CPP=gcc -E -nostdinc -Iinclude
CFLAGS=-Wall -pedantic -W -nostdlib -nostdinc
-Wno-long-long -I include -fomit-frame-pointer
-Wall -pedantic -W 打開所有的編譯警告,-nostdlib 告訴 GCC 不使用標準庫, -nostdinc -I include 告訴 GCC 只在本目錄的include文件夾下找尋頭文件。-Wno-long-long 上面已經(jīng)說了。-fomit-frame-pointer 告訴編譯器可能優(yōu)化而不使用棧寄存器,個人覺得不要使用這個選項為好,用-fnoomit-frame-pointer就一定可以正確的回溯堆棧。
KERNEL_OBJS= load.o init.o isr.o libcc.o scr.o kprintf.o
exceptions.o
Adds new modules into kernel.s.o:
${AS} -a $< -o $*.o >$*.map
all: final.img
final.img: bootsect kernel
cat bootsect kernel > final.img
@wc -c final.img
bootsect: bootsect.o
${LD} --oformat binary -N -e start -Ttext 0x7c00 -o bootsect $<
kernel: ${KERNEL_OBJS}
${LD} --oformat binary -N -e pm_mode -Ttext 0x0000 -o $@
${KERNEL_OBJS}
@wc -c kernel
clean:
rm -f *.img kernel bootsect *.o
dep:
sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
(for i in *.c;do ${CPP} -M $$i;done) >> tmp_make
mv tmp_make Makefile
上面的這個自動產(chǎn)生依賴是從linux-0.11里面來的。趙博的書上講的很詳細了。