最近刚刚看完了一个简单的多任务内核代码(8086),貌似linus最开始开发linux时,最先完成的就是这样一个实例吧^_^
那么为了更好理解,现在整理一下最近的知识。
首先这个内核代码的功能是分为两个任务,分别向屏幕打印A和B,在时钟中断的控制之下,两个任务循环切换,显示字符的功能为一个系统调
用。
共分为两个文件,一个是用as86写的boot.s,另一个是用GUN as写的head.s 。boot.s为引导程序,目的是在计算机加电时,把head.s的
代码加载入内存。
具体来说,计算机加电后,BIOS会把启动盘第一扇区(引导扇区)的代码加载到0x7c00(31KB)处,并将执行权转移到0x7c00处执行代码
(即boot.s)。然后boot.s将head代码加载入内存,设置临时GDT表等信息后,将处理器运行于保护模式下,最后将执行权转交head。
加载head分为两步,第一先将head加载至0x10000(64KB)处,然后再移至内存0处,之所以不直接移至0处的原因是因为BIOS使用的中断
向量表处在内存0处开始的地方。移动代码:
! 加载内核代码到内存0x10000开始处。
16 load_system:
17mov dx,#0x0000 ! 利用BIOS中断int 0x13功能2从启动盘读取head代码。
18mov cx,#0x0002 ! DH - 磁头号;DL - 驱动器号;CH - 10位磁道号低8位;
19mov ax,#SYSSEG ! CL - 位7、6是磁道号高2位,位5~0起始扇区号(从1计)。
20mov es,ax ! ES:BX - 读入缓冲区位置(0x1000:0x0000)。
21xor bx,bx ! AH - 读扇区功能号;AL - 需读的扇区数(17)。
22mov ax,#0x200+SYSLEN
23int 0x13
24jnc ok_load ! 若没有发生错误则跳转继续运行,否则死循环。
25 die: jmp die
26
27 ! 把内核代码移动到内存0开始处。共移动8KB(内核长度不超过8KB)。
28 ok_load:
29cli ! 关中断。
30mov ax, #SYSSEG ! 移动开始位置DS:SI = 0x1000:0;目的位置ES:DI=0:0。
31mov ds, ax
32xor ax, ax
33mov es, ax
34mov cx, #0x1000 ! 设置共移动4K次,每次移动一个字(word)。
35sub si,si
36sub di,di
37rep movw ! 执行重复移动指令。
之后的工作是需要进行一些设置,主要是IDT、GDT及进入保护模式
GDT描述:
! 下面是全局描述符表GDT的内容。其中包含3个段描述符。第1
个不用,另2个是代码和数据段描述符。
50 gdt: .word0,0,0,0 ! 段描述符0,不用。每个描述符项占8字节。
51
52.word0x07FF ! 段描述符1。8Mb - 段限长值=2047 (2048*4096=8MB)。
53.word0x0000 ! 段基地址=0x00000。
54.word0x9A00 ! 是代码段,可读/执行。
55.word0x00C0 ! 段属性颗粒度=4KB,80386。
56
57.word0x07FF ! 段描述符2。8Mb - 段限长值=2047 (2048*4096=8MB)。
58.word0x0000 ! 段基地址=0x00000。
59.word0x9200 ! 是数据段,可读写。
60.word0x00C0 ! 段属性颗粒度=4KB,80386。
加载IDTR和GDTR:
39mov ax, #BOOTSEG
40mov ds, ax ! 让DS重新指向0x7c0段。
41lidt idt_48 ! 加载IDTR。6字节操作数:2字节表长度,4字节线性基地址。
42lgdt gdt_48 ! 加载GDTR。6字节操作数:2字节表长度,4字节线性基地址。
所用操作数:
62 idt_48: .word0 ! IDT表长度是0。
63.word0,0 ! IDT表的线性基地址也是0。
64 gdt_48: .word0x7ff ! GDT表长度是2KB,可容纳256个描述符项。
65.word0x7c00+gdt,0 ! GDT表的线性基地址在0x7c0段的偏移gdt处。
进保护模式:
! 设置控制寄存器CR0(即机器状态字),进入保护模式。段
选择符值8对应GDT表中第2个段描述符。
45mov ax,#0x0001 ! 在CR0中设置保护模式标志PE(位0)。
46lmsw ax ! 然后跳转至段选择符值指定的段中,偏移0处。
47jmpi 0,8 ! 注意此时段值已是段选择符。该段的线性基地址是0。
完整代码:http://book.51cto.com/art/200812/103275.htm
接下来详细看下GDT与IDT的设置过程。
41lidt idt_48 ! 加载IDTR。6字节操作数:2字节表长度,4字节线性基地址。
42lgdt gdt_48 ! 加载GDTR。6字节操作数:2字节表长度,4字节线性基地址。
以上为加载过程,不是很清楚ligdt与lgdt命令,查文档如下:
Description
Loads the values in the source operand into the global descriptor table register (GDTR) or the interrupt descriptor
table register (IDTR). The source operand specifies a 6-byte memory location that contains the base address
(a linear address) and the limit (size of table in bytes) of the global descriptor table (GDT) or the interrupt descriptor
table (IDT). If operand-size attribute is 32 bits, a 16-bit limit (lower 2 bytes of the 6-byte data operand) and a 32-bit
base address (upper 4 bytes of the data operand) are loaded into the register. If the operand-size attribute is 16 bits,
a 16-bit limit (lower 2 bytes) and a 24-bit base address (third, fourth, and fifth byte) are loaded. Here, the high-order
byte of the operand is not used and the high-order byte of the base address in the GDTR or IDTR is filled with zeros.
最后补充一个概念,感觉自己貌似对源操作数,目标操作数的问题上有点迷惑,于是百度了下,得如下答案:
源操作数,指的是你准备要操作的那个“数”,而这个“数”可以有很多种寻址方法(不知道你明不明白),包括直接操作数
(意思是直接给你一个具体的数,例如100),寄存器寻址,间址寻址等等记不太清了;目标操作数指的是你对源操作数进行完操作之后
(例如加减操作等)将其结果输出到某个地址(包括各种寻址方法),那个地址就是你的目标操作数。