head.s

http://blog.chinaunix.net/uid-11319766-id-3445575.html
在进入保护模式后,head.s程序重新建立和设置IDT,GDT表的主要原因是为了让程序在结构上比较清晰,也是为了与后面的linux 0.11内核源代码中这两个表的设置方式保持一致,当然。就本程序来说我们完全可以直接使用boot.s中设置的IDT和GDT表位置,填入适当的描述符即可。
#
#
LATCH = 11930 #定时器初始计数值
SCRN_SEL = 0x18 #屏幕显示内存段选择符
TSS0_SEL = 0x20 #任务0的TSS段选择符
LDT0_SEL = 0x28 #任务0的LDT段选择符
TSS1_SEL = 0x30 #任务1的TSS段选择符
LDT1_SEL = 0x38 #任务1的LDT段选择符
.text
startup_32
#首先加载数据段寄存器DS,堆栈段寄存器SS和堆栈指针ESP。所有段的线性基地址都是0
movl $0x10, %eax #0x10是GDT中数据段选择符
mov %ax, %ds #将选择符赋值给ds。ds寄存器
lss  init_stack, %esp #lss指令把,段地址:偏移地址存到SS:esp。init_stack见后面
#设置IDT和GDT表
call setup_idt #设置IDT表
call setup_gdt #设置GDT表
movl $0x10, %eax #在改变了GDT之后重新加载所有段寄存器,都加载为GDT第3个项内存数据段
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
lss init_stack, %esp
#设置8253定时芯片。把计数器通道0设置成每隔10毫秒向中断控制器发送一个中断请求信号
movb $0x36, %al #控制字:设置通道0工作方式3,计数初值采用二进制
movl $0x43, %edx #8253芯片控制字寄存器写端口
outb %al, %dx #IO端口操作
movl $LATCH, %eax #初始计数值设置为LATCH(1193180/100),即频率100Hz
movl $0x40, %edx #通道0的端口
outb %al, %dx #分两次把初始值写入通道0
movb %ah, %al
outb %al, %dx
#在IDT表第8个第128(0x80)项处分别设置定时中断门描述符和系统调用陷阱门描述符
movl $0x00080000, %eax #中断程序属内核,即EAX高字是内核代码段选择符0x0008
movw $timer_interrupt, %ax #设置定时中断门描述符
movw $0x8E00, %dx #中断门类型是14
movl $0x08, %ecx #将IDT描述符表向量8的地址放入ESI中
lea idt(,%ecx,8), %esi #idt(,%ecx,8)表示idt表(可看成数组)中,每项长度为8,索引值为eax地址
movl %eax, (%esi)
movl %edx, 4(%esi)
movw $system_interrupt, %ax #设置系统调用陷阱门描述符。取系统调用处理程序地址
movw $0xef00, %dx #陷阱门类型是15,特权级3的程序可执行
movl  $0x80, %ecx #系统调用的向量号是0x80
lea idt(,%ecx,8), %esi #将idt中向量为0x80的地址传给esi
movl  %eax, (%esi) #
movl %edx, 4(%esi)
#现在我们为移动到任务0中执行来操作堆栈内容,在堆栈中人工建立中断返回时的场景。
pushfl #标志位寄存器入栈 ()
andl $0xffffbfff, (%esp)
popfl
movl $TSS0_SEL, %eax #把任务0的TSS段选择符加载进eax
ltr %ax # LTR指令是专门用于装载任务状态段寄存器TR的指令
movl $LDT0_SEL, %eax #把任务0的LDT段选择符加载到ax
lldt %ax #转载LDTR寄存器。TR和LDTR只需要人工加载一次,以后CPU会自动处理
mov $0, current #将任务号0装入current变量
sti #开启中断
pushl $0x17 #把任务0当前局部空间数据段(堆栈段)选择符入栈
pushl $init_stack #把堆栈指针入栈
pushfl #标志寄存器入栈
pushl $0x0f #把当前局部空间代码段选择符入栈
pushl $task0 #把任务0的代码地址入栈
iret #执行中断返回指令,从而切换到特权级3的任务0中执行。

#以下是设置GDT和IDT中描述符项的子程序。
setup_gdt: #使用6字节操作数lgdt_opcode设置GDT表位置和长度
lgdt lgdt_opcode
ret

#这段代码暂时设置IDT表中所以256个中断门描述符都为同一个默认值,均使用默认的中断处理过程ignore_int.
#设置的具体方法是:首先在eax和edx寄存器对中分别设置好默认中断门描述符的0-3字节和4-7字节,然后利用该
#寄存器对循环往IDT表中填充默认中断门描述内容。
setup_idt:
lea ignore_int, %edx #将标号为ignore_int出的偏移地址,赋值给edx
movl $0x00080000, %eax #将0x00080000给eax,这里有用的是0x0008 对应GDT表内核代码段
movw %dx, %ax #将dx(是edx的低16位)的值给ax,eax就是中断门描述符的0-3字节
movw $0x8E00, %dx #edx是中断门描述符的4-7字节
lea idt, %edi #让edi(通用变址指针寄存器)中是标号为idt的地址。
mov $256, %ecx #ecx保存要循环的次数
rp_idt:
movl %eax,  (%edi) #将eax的值传给edi指向的内存。“()”是取地址
movl %edx,  4(%edi) #将edx的值传给edi+4指向的内存
addl $8,   %edi #地址edi加8
dec %ecx #次数ecx减一
jne rp_idt #循环
lidt lidt_opcode #装载lid表地址 到IDTR寄存器,lidt_opcode是6字节的内容包括基地址和限长
ret
#
#

#显示字符子程序。取当前光标位置并把AL中的字符显示在屏幕上,这个屏幕可显示80X25个字符
write_char:
push %gs #入栈,保存此调用可能用到的寄存器,gs,ebx
push %ebx
mov $SCRN_SEL, %ebx #将屏幕显示内存段的描述符存在ebx
mov %bx, %gs
movl scr_loc, %bx #将变量scr_loc放入bx,变量定义见后面
shl $1, %ebx #将ebx逻辑左移1 ,屏幕上每个字符还有一个属性字节,因此内存地址要乘2
movb %al, %gs:(%ebx) #将al(其中保存字符码),传到gs段的ebx地址。用于显示。
shr $1, %ebx #除2(左移1)
incl %ebx #加1
cmp1 $2000, %ebx #是否等于2000
jb 1f #如果不等于 跳过到1
movl $0, %ebx #ebx清零
1: movl %ebx, scr_loc #保存到变量scr_loc
popl %ebx #出栈
pop %gs
ret

#以下是3个中断处理程序,默认中断,定时中断和系统中断
.align 2
ignore_int:
push %ds
pushl %eax
movl $0x10, %eax
mov %ax, %ds #让DS指向内核数据段,因为中断程序属于内核
movl $67, %eax
call write_char #在AL中存放字符‘C’的代码,调用显示程序显示在屏幕上
popl %eax
pop %ds
iret
#定时中断处理程序,其中主要执行任务切换操作
.align 2
timer_interrupt:
push %ds
push %eax
movl $0x10, %eax
mov %ax, %ds # DS寄存器指向内核数据段(0x10的段选择符)
movb $0x20, %al #然后立刻允许其他硬件中断,即向8259A发送E01命令
outb %al $0x20
movl $1, %eax #任务切换,若是任务1,则执行任务0,反之亦然
cmpl %eax, current
je 1f
movl %eax, current #若是任务0
ljmp $TSS1_SEL, $0 # TSS1_SEL是GDT表中任务段tss0的段选择符,$0偏移地址无意义。
jmp 2f
1: movl $0, current #若当前任务是1,则把0存入current,并跳转到任务0
ljmp $TSS0_SEL, $0
2: popl %eax
pop %ds
iret

#系统调用中断int 0x80处理程序。该示例只有一个字符显示功能
.align 2
system_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %edx #首先让DS指向内核数据段
mov %dx, %ds
call write_char
pop1 %eax
pop1 %ebx
pop1 %ecx
popl %edx
pop %ds
iret

#32位变量
current: .long 0 #当前任务号 (0或1)
scr_loc: .long 0 #屏幕当前显示位置。按从左上到右下顺序

.align 2
lidt_opcode:
.word 256*8-1
.long  idt
lgdt_opcode:
.word(end_gdt-gdt) – 1
.long gdt
.align 3
idt: .fill 256,8,0 #IDT表空间,共256个门描述符。用fill指令分配了256×8个字节 = 2KB

gdt:
.quad 0x0000000000000000 #GDT 第一为空
.quad 0x00c09a00000007ff #2内核代码段描述符。其选择符是0x08,基地址0,
.quad 0x00c09200000007ff #3内核数据段描述符。选择符是0x10,基地址0,
.quad 0x00c0920b80000002 #4显示内存段描述符。选择符是0x18
.word 0x68, tss0, 0xe900, 0x0 #5 TSS0段的描述符。选择符是0x20,基地址tss0,限长0x68,参考4.3.6
.word 0x40, ldt0, 0xe200, 0x0 #6 LDT0段的描述符。选择符是0x28,基地址ldt0,限长0x40
.word 0x68, tss1, 0xe900, 0x0 #7 TSS1段的描述符。选择符是0x30,基地址tss1,限长0x68
.word 0x40, ldt0, 0xe900, 0x0 #8 LDT1段的描述符。选择符是0x38,基地址ldt0,限长0x40
end_gdt:
.fill  128, 4, 0 #初始内核堆栈空间
init_stack:
.long init_stack #4字节偏移地址。init_stack标号是在fill分配的空间的尾部,是栈顶
.wor 0x10 #2 字节段选择符。对应GDT表中3 内核数据段描述符

#下面是任务0的LDT表段中的描述符
.align 3
ldt0: .quad 0x0000000000000000 #空,不用
.quad 0x00c0fa00000003ff #局部代码段描述符。选择符是0x0f;限长1024 基地址0
.quad 0x00c0f200000003ff #局部数据段描述符选择符是0x17;限长1024 基地址0

#下面是任务0的TSS段的内容。注意其中的标号等字段在任务切换时不会改变。
tss0: .long  0 #前一个任务的链接(TSS段选择符)
.long  krn_stk0, 0x10 # krn_stk0(内核栈空间地址标号)作为ESP0;0x10作为SS0堆栈段,对应#GDT表中第3项
.long 0, 0, 0, 0, 0 #esp1, ss1, esp2, ss2, cr3
.long 0, 0, 0, 0, 0 #eip, eflags, eax, ecx, edx
.long 0, 0, 0, 0, 0 #ebx, esp, ebp, esi, edi
.long 0, 0, 0, 0, 0, 0 #es, cs, ss, ds, fs, gs
.long LDT0_SEL, 0x80000000 # ldt, LDT0_SEL = 0x28(LDT表的段选择符); trace bitmap(I\O位图基地址)


.fill 128, 4, 0 #分配任务0的内核栈空间
krn_stk0:
#下面是任务1的LDT表段内容和TSS段内容。
.align 3
ldt1: .quad 0x0000000000000000 #第一个不用
.quad 0x00c0fa00000003ff #代码段,选择符0x0f,基地址 =0x0
.quad 0x00c0f200000003ff #数据段,选择符是0x17, 基地址 =0x0

tss0: .long  0 #前一个任务的链接(TSS段选择符)
.long  krn_stk1, 0x10 # krn_stk1(内核栈空间地址标号)作为ESP0;0x10作为SS0堆栈段,对应#GDT表中第3项
.long 0, 0, 0, 0, 0 #esp1, ss1, esp2, ss2, cr3
.long 0, 0, 0, 0, 0 #eip, eflags, eax, ecx, edx
.long 0, 0, 0, 0, 0 #ebx, esp, ebp, esi, edi
.long 0, 0, 0, 0, 0, 0 #es, cs, ss, ds, fs, gs
.long LDT1_SEL, 0x80000000 # ldt, LDT1_SEL = 0x38(LDT表的段选择符); trace bitmap(I\O位图基地址)

.fill  128,4,0
krn_stk1;

#下面是任务0和任务1的程序,它们分别循环显示字符A和B
task0:
movl  $0x17, %eax #首先让DS指向任务局部数据段
movw  %ax, %ds #因为任务没有使用局部数据,所以这两句可省略
movl  $65, %al #把需要显示的字符A放入AL寄存器中
int    $0x80 #系统调用,显示字符
movl  $0xfff, %ecx #执行循环,起延时作用
1: loop  1b
jmp   task0 #跳转到任务开始处继续显示字符

task1:
movl  $66, %al #把需要显示的字符A放入AL寄存器中
int    $0x80 #系统调用,显示字符
movl  $0xfff, %ecx #执行循环,起延时作用
1: loop  1b
jmp   task1 #跳转到任务开始处继续显示字符

.fill  128,4,0 #这是任务1的用户栈空间
usr_stk1:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值