第4課:中斷和異常1
聲明:轉載請保留:
譯者:http://m.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目標
下載源程序
在本課中,我們學習如何處理中斷和異常。并且加入中斷處理程序到skelix內核中,這樣可以在中斷或異常來臨時打印一些可以看得到的東西,這些程序是后面課程的基礎。
那么中斷和異常到底是什么咚咚呢?
舉例來說吧,小胖正在家里吃飯,吃的很歪歪的時候,MM來了一個電話,于是接電話告訴她晚上七點在村口YY樹下不見不散,這就是一個突發事件,接完電話繼續吃飯。突然小胖在米飯里面發現一個蟑螂,errr~~~~。只好結束晚餐了,這個就是異常了。中斷是可以返回來的,比如上面的接電話,但異常就不能返回繼續做剛才的事了。
簡單的說,中斷和異常都是停止CPU正在做的事情,強制它做另外一件事,然后回到原來的控制流上繼續執行。中斷和異常的區別在于它們的外部觸發源,對于硬件設備來說,如鍵盤輸入,系統時鐘等就會觸發中斷。而保護模式下的指令執行才會引發異常,例如除零錯誤,雙重錯誤:),INT 3指令就是主動觸發異常(可以在用戶態進行測試),表示這個進程進入調試狀態。
處理器為每個中斷或異常分配一個獨立的編號,這個編號實際上就是中斷向量表,在實模式下它是從物理地址0開始的,現在早已被我們的內核‘skelix’覆蓋了,即便沒有覆蓋,我們仍然無法在保護模式中使用這些實模式的中斷。在Intel手冊上,有兩種外部中斷和兩種異常。
中斷分類
1)可屏蔽中斷:通過INTR引腳告訴CPU來了一個中斷,可以被寄存器設置屏蔽調
2)非可屏蔽中斷:通過NMI引腳告訴CPU來了一個中斷,不可屏蔽
下面是系統啟動默認設置的中斷向量:
IRQ 引腳
|
中斷向量
|
中斷
|
IRQ0
|
08
|
系統時鐘
|
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
|
數學協處理器
|
IRQ14
|
76
|
硬盤設備 IDE0
|
IRQ15
|
77
|
硬盤設備 IDE1
|
上面表格中的IRQ是中斷控制器的物理引腳,直接連到外部硬件設備上的。在AT兼容機器上有16個引腳。
異常分離
1)處理器檢查:頁故障,陷阱等
2)程序片:例如INTO,INT 3,INT n等系統調用
中斷向量
|
異常
|
00
|
除零錯
|
01
|
調試異常
|
02
|
非可屏蔽中斷 (NMI)
|
03
|
斷點 (INT 3 指令)
|
04
|
溢出 (INTO 指令)
|
05
|
越界 (BOUND指令)
|
06
|
無效的指令
|
07
|
無協處理器
|
08
|
雙重錯誤
|
09
|
協處理器越界
|
0A
|
無效的 TSS
|
0B
|
段不存在
|
0C
|
棧溢出
|
0D
|
通用保護異常(內存引用或其他檢查保護),Windows 9x藍屏就是它的杰作
|
0E
|
頁錯誤
|
0F
|
Intel 保留
|
10
|
協處理器錯誤
|
11-19
|
Intel保留
|
1A-FF
|
未使用
|
如果讀者仔細的話,可能注意到中斷和異常編號會有沖突。IRQ0到IRQ7中斷向量和異常的0x08-0x10重疊了,所以我們得處理一下。中斷的屏蔽可以通過8259A PIC(programmable interrupt controllers)設置。PIC1處理IRQ0到IRQ7,PIC2處理IRQ8到IRQ15,當中斷到來時,PIC得到信號并通知CPU。CPU收到后會停止當前任務執行,并轉向中斷向量中指向的處理代碼。我們稱之為ISR(interrupt service routine)。8259A設置的中斷向量編號可重新分配,為了做到這點,我們需要對8259A進行編程,這個是有難度的事情,你可以在網絡上搜索相關的文章自己看下(否則本文可能很難看下去,如果你一點基礎也沒有的話,最好能很好的理解ICW和OCW的幾個命令字),下面的程序就是干這事的:
04/init.c
static void
pic_install(void) {
outb(0x11,
0x20); //
ICW1命令字,使用邊沿觸發中斷,多片8259A級聯
outb(0x11, 0xa0);
outb(0x20,
0x21); //
ICW2命令字,重新分配中斷向量編號IRQ0-IRQ7
outb(0x28, 0xa1); //
ICW2命令字,重新分配中斷向量編號IRQ8-IRQ15
outb(0x04,
0x21); //
ICW3命令字,主從8259A鏈接設置
outb(0x02, 0xa1);
outb(0x01,
0x21); //
ICW4命令字,設置EIO/AEIO模式,緩沖方式等
outb(0x01, 0xa1);
outb(0xff,
0x21); //
屏蔽IRQ0-IRQ7所有中斷
outb(0xff,
0xa1); //
屏蔽IRQ8-IRQ15所有中斷
}
一個好消息:我們現在開始終于使用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))
現在我們知道了怎么重新分配中斷向量,但是另外一個問題:實模式下的中斷向量表已經被內核覆蓋了,怎么辦呢?我們不得不重寫它們了。這一點也不有趣。
IDT and ISR
處理器執行中斷和異常的方法都是一樣的,當某個中斷或異常發生時,處理器停止當前任務跳轉到特定的例程中去,這個例程就是ISR。當ISR執行完后,返回控制到原來的任務中去。那么處理器又是怎么樣路由這些中斷的呢?原因是系統中存在一個叫IDTR(IDT register)的寄存器,它指向內存中的一個叫中斷描述符表的緩沖,這個描述符表就定義了所有中斷例程(即ISR)的邏輯地址等。它和GDT看起來很像,只有個別的位不同而已。IDT中為每個中斷或異常都準備了獨立的一項,我們常稱之為向量(就是上面重新分配的中斷向量編號)。IDT可以看作是一個64位長整型的數組,最多可有256項。LIDT指令可以加載IDT的地址到IDTR中,就像LGDT加載GDT地址到GDTR一樣。現在我們來看一下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位)
|
|_______________________________________________________|
大多數的域我們已經很熟悉了,后面的代碼中我會詳細講解到。實際上,有多種描述符,但這里我們只用到中斷門。現在我們設置CPU使其路由到正確的中斷例程ISR中去,那么ISR到底是什么呢?因為中斷和異常會停止當前執行的任務并且需要返回回來繼續執行,所以ISR需要保存當前任務的運行環境,就是一大堆寄存器。需要在進入ISR的時候保存這些寄存器,并在離開的時候回復它們。
如果ISR代碼和當前任務特權級相同,那么ISR將會使用當前任務堆棧(這個任務一般是內核線程,當然也有例外),或者就是切換到內核棧中, 即從TSS(后面介紹這個咚咚)中加載新的CS,EIP和棧,并按順序保存所有的寄存器到新的堆棧中。當堆棧切換后(如果有的話),CPU會自動保存SS, ESP, EEFLAGS, CS 和 EIP等寄存器到棧中。有些異常會帶一個錯誤號(表示錯誤的一些信息),這個錯誤號也會自動壓入棧中。之后IDT中的CS和EIP將會被加載從而執行ISR例程。因為我們使用的是中斷門,所以IF標識(EFLAGS寄存器)將會被清掉。從ISR例程返回逆序做上面的步驟即可,不值得一提。
圖1-特權級不變
_________________ _________________
|
|
|
| |
|_________________|
|_________________| |
|
|
|
| |
|_________________|
|_________________| | 棧增長方向
| Old EFLAGS
| | Old EFLAGS
| |
|_________________|
|_________________| |
| Old
CS |
| Old
CS | |
|_________________|
|_________________| |
| Old
EIP |
| Old
EIP | \|/
|_________________|
|_________________|
|
| | Error Code
|
|_________________|
|_________________|
圖2-特權級變化
_________________ _________________
| 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!",本課中我們先跳轉到C代碼中去執行,稍后就是中斷和異常相關程序了。
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函數將會初始化硬件和系統表(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個字節)表示一個描述符,GCC也許會給出警告(對于這種類型),使用--Wno-long-long 就可以了.
這個函數用來填充IDT項,index參數是索引,第二個參數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);
// 通過上面設置后,idt項值變為0x00008e0000080000,表示偏移地址為0,使用0x8作為描述符選擇子(即內核代碼段描述符),該ISR是存在的且特權級DPL為0
idt_entry |= (offset<<32) &
0xffff000000000000ULL;
idt_entry |= (offset) & 0xffff;
// 上面兩行填充ISR地址
idt[index] = idt_entry;
// 填充值到idt中
}
// 這個函數安裝所有的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字節對齊,該變量占6字節
for (i=0; i<VALID_ISR;
++i) // isr數組存放著所有的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; //
測試除零異常,看看發生了什么
}
好了,現在進入核心程序。我們不得不又和一些匯編程序打交道。我們知道,當硬件向PIC發送一個信號后,PIC通知處理器停止當前任務執行,然后處理器查找ISR例程,然后執行ISR,再返回控制流。所以ISR就是我們關注的地方:
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
關于宏的說明,可以參考linux中as的幫助頁。使用pinfo可以很好的瀏覽,
這個指令在我的博客上有介紹(http://m.shnenglu.com/jinglexy),
查找2007.4月份文檔即可。
使用上面兩個宏,我們可以很方便的定義中斷和異常函數及編號:
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例程數組,注意類似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
+-----------+
恐怖的堆棧圖,我花了很多時間才把它畫出來,汗一個先:)有沒有推薦更好的工具?
對于所有的中斷和異常來說,該堆棧幀結構都是一樣的。
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 // 所有數據段特權級都是0
movw
%ax, %ds
movw
%ax, %es
movw
%ax, %fs
movw
%ax, %gs
movl
52(%esp),%ecx // 看上面的堆棧圖,52就是ISR例程的編號
call *isr(, %ecx,
8) // 不帶參數執行isr例程
addl
$4, %esp // 我們當然不能popl %ss,所以就這樣跳過去了
popl %gs
popl %fs
popl %es
popl %ds
popal
addl
$8, %esp // 跳過 isr_nr 和 err_code
iret //
返回到原來的控制流繼續執行
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 # 發送OCW2,告訴 PIC1 ISR執行完畢
outb %al,
$0xa0 # 發送OCW2,告訴 PIC2 ISR執行完畢
iret
可以在這里找到OCW2的資料:
http://docs.huihoo.com/gnu_linux/own_os/interrupt-8259_5.htm,
我一直都想找一份端口大全的資料,如果哪位有可以發一份給我:
jinglexy at yahoo dot com dot cn
在現在階段中,所有異常暫時打印一些寄存器,只是演示一下而已:
04/exceptions.c
void
divide_error(void) {
__asm__ ("pushl
%%eax;call info"::"a"(KPL_PANIC));
halt();
}
info 是這個文件最后定義的一個函數,它的參數就是上面圖-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();
}
// 很好理解的函數,不費口舌了這里。
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 上面已經說了。-fomit-frame-pointer 告訴編譯器可能優化而不使用棧寄存器,個人覺得不要使用這個選項為好,用-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
上面的這個自動產生依賴是從linux-0.11里面來的。趙博的書上講的很詳細了。