最近在学习Linux内核,这0.00据说当年使还是学生的linus异常兴奋,只是满屏幕的AABBBBAAA而已。 本人只是写下学习笔记,以备日后复习之用。 :01 ! boot.s 程序 02 ! 首先利用BIOS中断把内核代码(head代码)加载到内存0x10000处,然后移动到内存0处。 03 ! 最后进入保护模式,并跳转到内存0(head代码)开始处继续运行。 04 BOOTSEG = 0x07c0 ! 引导扇区(本程序)被BIOS加载到内存0x7c00处。 05 SYSSEG = 0x1000 ! 内核(head)先加载到0x10000处,然后移动到0x0处。 06 SYSLEN = 17 ! 内核占用的最大磁盘扇区数。 07 entry start 08 start: 09 jmpi go,#BOOTSEG ! 段间跳转至0x7c0:go处。当本程序刚运行时所有段寄存器值 10 go: mov ax,cs ! 均为0。该跳转语句会把CS寄存器加载为0x7c0(原为0)。 11 mov ds,ax ! 让DS和SS都指向0x7c0段。 12 mov ss,ax 13 mov sp,#0x400 ! 设置临时栈指针。其值需大于程序末端并有一定空间即可。 14 15 ! 加载内核代码到内存0x10000开始处。 16 load_system: 17 mov dx,#0x0000 ! 利用BIOS中断int 0x13功能2从启动盘读取head代码。 18 mov cx,#0x0002 ! DH - 磁头号;DL - 驱动器号;CH - 10位磁道号低8位; 19 mov ax,#SYSSEG ! CL - 位7、6是磁道号高2位,位5~0起始扇区号(从1计)。 20 mov es,ax ! ES:BX - 读入缓冲区位置(0x1000:0x0000)。 21 xor bx,bx ! AH - 读扇区功能号;AL - 需读的扇区数(17)。 22 mov ax,#0x200+SYSLEN 23 int 0x13 24 jnc ok_load ! 若没有发生错误则跳转继续运行,否则死循环。 25 die: jmp die 26 27 ! 把内核代码移动到内存0开始处。共移动8KB(内核长度不超过8KB)。 28 ok_load: 29 cli ! 关中断。 30 mov ax, #SYSSEG ! 移动开始位置DS:SI = 0x1000:0;目的位置ES:DI=0:0。 31 mov ds, ax 32 xor ax, ax 33 mov es, ax 34 mov cx, #0x1000 ! 设置共移动4K次,每次移动一个字(word)。 35 sub si,si 36 sub di,di 37 rep movw ! 执行重复移动指令。 38 ! 加载IDT和GDT基地址寄存器IDTR和GDTR。 39 mov ax, #BOOTSEG 40 mov ds, ax ! 让DS重新指向0x7c0段。 41 lidt idt_48 ! 加载IDTR。6字节操作数:2字节表长度,4字节线性基地址。 42 lgdt gdt_48 ! 加载GDTR。6字节操作数:2字节表长度,4字节线性基地址。 43 44 ! 设置控制寄存器CR0(即机器状态字),进入保护模式。段选择符值8对应GDT表中第2个段描述符。 45 mov ax,#0x0001 ! 在CR0中设置保护模式标志PE(位0)。 46 lmsw ax ! 然后跳转至段选择符值指定的段中,偏移0处。 47 jmpi 0,8 ! 注意此时段值已是段选择符。该段的线性基地址是0。 48 49 ! 下面是全局描述符表GDT的内容。其中包含3个段描述符。第1个不用,另2个是代码和数据段描述符。 50 gdt: .word 0,0,0,0 ! 段描述符0,不用。每个描述符项占8字节。 51 52 .word 0x07FF ! 段描述符1。8Mb - 段限长值=2047 (2048*4096=8MB)。 53 .word 0x0000 ! 段基地址=0x00000。 54 .word 0x9A00 ! 是代码段,可读/执行。 55 .word 0x00C0 ! 段属性颗粒度=4KB,80386。 56 57 .word 0x07FF ! 段描述符2。8Mb - 段限长值=2047 (2048*4096=8MB)。 58 .word 0x0000 ! 段基地址=0x00000。 59 .word 0x9200 ! 是数据段,可读写。 60 .word 0x00C0 ! 段属性颗粒度=4KB,80386。 61 ! 下面分别是LIDT和LGDT指令的6字节操作数。 62 idt_48: .word 0 ! IDT表长度是0。 63 .word 0,0 ! IDT表的线性基地址也是0。 64 gdt_48: .word 0x7ff ! GDT表长度是2KB,可容纳256个描述符项。 65 .word 0x7c00+gdt,0 ! GDT表的线性基地址在0x7c0段的偏移gdt处。 66 .org 510 67 .word 0xAA55 ! 引导扇区有效标志。必须处于引导扇区最后2字节处。 !多任务内核程序head.s !在进入保护模式后,head.s程序重新建立和设置IDT、GDT表的主要原因是为了让程序在结构上比较清晰,也为了与 !后面Linux 0.12内核源代码中这两个表的设置方式保持一致。当然,就本程序来说我们完全可以直接使用boot.s中 !设置的IDT和GDT表位置,填入适当的描述符项即可。 01 # head.s 包含32位保护模式初始化设置代码、时钟中断代码、系统调用中断代码和两个任务的代码。 02 # 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换操作。 03 LATCH = 11930 # 定时器初始计数值,即每隔10ms发送一次中断请求。 04 SCRN_SEL = 0x18 # 屏幕显示内存段选择符。 05 TSS0_SEL = 0x20 # 任务0的TSS段选择符。 06 LDT0_SEL = 0x28 # 任务0的LDT段选择符。 07 TSS1_SEL = 0X30 # 任务1的TSS段选择符。 08 LDT1_SEL = 0x38 # 任务1的LDT段选择符。 09 .text 10 startup_32: 11 # 首先加载数据段寄存器DS、堆栈段寄存器SS和堆栈指针ESP。所有段的线性基地址都是0。 12 movl $0x10,%eax # 0x10是GDT中数据段选择符。 13 mov %ax,%ds 14 lss init_stack,%esp 15 # 在新的位置重新设置IDT和GDT表。 16 call setup_idt # 设置IDT。先把256个中断门都填默认处理过程的描述符。 17 call setup_gdt # 设置GDT。 18 movl $0x10,%eax # 在改变了GDT之后重新加载所有段寄存器。 19 mov %ax,%ds 20 mov %ax,%es 21 mov %ax,%fs 22 mov %ax,%gs 23 lss init_stack,%esp 24 # 设置8253定时芯片。把计数器通道0设置成每隔10ms向中断控制器发送一个中断请求信号。 25 movb $0x3