自己动手写内核(第4课:中断和异常1)(原创)

4课:中断和异常1


声明:转载请保留

译者http://www.cppblog.com/jinglexy(新的博客地址是:http://blog.csdn.net/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)程序片:例如INTOINT 3INT 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

未使用

 

 

如果读者仔细的话,可能注意到中断和异常编号会有冲突。IRQ0IRQ7中断向量和异常的0x080x10重叠了,所以我们得处理一下。中断的屏蔽可以通过8259A PICprogrammable interrupt controllers)设置。PIC1处理IRQ0IRQ7PIC2处理IRQ8IRQ15,当中断到来时,PIC得到信号并通知CPUCPU收到后会停止当前任务执行,并转向中断向量中指向的处理代码。我们称之为ISRinterrupt service routine)。8259A设置的中断向量编号可重新分配,为了做到这点,我们需要对8259A进行编程,这个是有难度的事情,你可以在网络上搜索相关的文章自己看下(否则本文可能很难看下去,如果你一点基础也没有的话,最好能很好的理解ICWOCW的几个命令字),下面的程序就是干这事的:


04/init.c

static void
pic_install(void) {
    outb(0x11, 0x20);            // ICW1
命令字,使用边沿触发中断,多片8259A级联
    outb(0x11, 0xa0);
    outb(0x20, 0x21);            // ICW2
命令字,重新分配中断向量编号IRQ0IRQ7
    outb(0x28, 0xa1);            // ICW2
命令字,重新分配中断向量编号IRQ8IRQ15
    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执行完后,返回控制到原来的任务中去。那么处理器又是怎么样路由这些中断的呢?原因是系统中存在一个叫IDTRIDT register)的寄存器,它指向内存中的一个叫中断描述符表的缓冲,这个描述符表就定义了所有中断例程(即ISR)的逻辑地址等。它和GDT看起来很像,只有个别的位不同而已。IDT中为每个中断或异常都准备了独立的一项,我们常称之为向量(就是上面重新分配的中断向量编号)。IDT可以看作是一个64位长整型的数组,最多可有256项。LIDT指令可以加载IDT的地址到IDTR中,就像LGDT加载GDT地址到GDTR一样。现在我们来看一下IDT描述符:

 

                    图-0

 

 

 63_______________56__55__54__53__52__51_____________48_

|                  偏移地址(3116位)                 |

|_______________________________________________________|

 

 _47__46__45________________________________36________32_

| P |  DPL |           01110000            |  未使用    |

|_______________________________________________________|

 

 31____________________________________________________16

|                     描述符选择子                      |

|_______________________________________________________|

 

 16_____________________________________________________

|                 偏移地址150位)                   |

|_______________________________________________________|

大多数的域我们已经很熟悉了,后面的代码中我会详细讲解到。实际上,有多种描述符,但这里我们只用到中断门。现在我们设置CPU使其路由到正确的中断例程ISR中去,那么ISR到底是什么呢?因为中断和异常会停止当前执行的任务并且需要返回回来继续执行,所以ISR需要保存当前任务的运行环境,就是一大堆寄存器。需要在进入ISR的时候保存这些寄存器,并在离开的时候回复它们。

如果ISR代码和当前任务特权级相同,那么ISR将会使用当前任务堆栈(这个任务一般是内核线程,当然也有例外),或者就是切换到内核栈中, 即从TSS(后面介绍这个咚咚)中加载新的CSEIP和栈,并按顺序保存所有的寄存器到新的堆栈中。当堆栈切换后(如果有的话),CPU会自动保存SS, ESP, EEFLAGS, CS  EIP等寄存器到栈中。有些异常会带一个错误号(表示错误的一些信息),这个错误号也会自动压入栈中。之后IDT中的CSEIP将会被加载从而执行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函数将会初始化硬件和系统表(idtgdt):
04/init.c

unsigned long long *idt = ((unsigned long long *)IDT_ADDR);
unsigned long long *gdt = ((unsigned long long *)GDT_ADDR);

为了方便存取,我们使用一个long longia32上是8个字节)表示一个描述符,GCC也许会给出警告(对于这种类型),使用--Wno-long-long 就可以了.

 

这个函数用来填充IDT项,index参数是索引,第二个参数offsetISR例程的地址。
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是存在的且特权级DPL0

    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数组存放着所有的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

 

关于宏的说明,可以参考linuxas的帮助页。使用pinfo可以很好的浏览,

这个指令在我的博客上有介绍(http://www.cppblog.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                      //
依次把寄存器AXCXDXBXSPBPSIDI压栈
        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里面来的。赵博的书上讲的很详细了。

 

 

你可能感兴趣的:(操作系统开发)