- 论坛徽章:
- 0
|
# 这段代码已经被boot.s调入到了内存0x10000,并且在保护模式下
KRN_BASE = 0x10000
# 在boot.s中我们已经了解了段寄存器的结构,它是指向gdt表的索引
# gdt表的一个表项占8个字节。每个用户进程占两个gdt表项,tss和ldt
# 第一项不用
# 第二项是系统的代码段
# 第三项是系统的数据段
# 第三项是系统的显存段
# 那么第四项就是从第32(0x20)个字节开始,依次类推
TSS0_SEL = 0x20
LDT0_SEL = 0x28
TSS1_SEL = 0X30
LDT1_SEL = 0x38
.text
startup_32:
# 将所有段寄存器都指向系统数据段,gdt表第三项
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
# 装入堆栈段地址
lss stack_ptr,%esp
#
# linux-0.00有两个进程,其中一个一直打印A,另外一个一直打印B
# 直到8253产生中断切换进程
# 操作系统采用分页机制前采用分段机制。现代操作系统中的段
# 有人说Linux巧妙的绕了过去,有人说是不用了...
#
# 对于采用分段机制的系统每个用户进程占gdt表的两项,一个是tss段,另外一个是ldt段
# 下面的十几行代码设置两个进程的tss段和ldt段
# 将这两个段的绝对地址写到gdt表项相应的地方
# gdt表项的结构请参考C程序
#
http://blog.chinaunix.net/u/23177/showart_209429.html
#
movl $KRN_BASE, %ebx
movl $gdt, %ecx
lea tss0, %eax
movl $TSS0_SEL, %edi
call set_base
lea ldt0, %eax
movl $LDT0_SEL, %edi
call set_base
lea tss1, %eax
movl $TSS1_SEL, %edi
call set_base
lea ldt1, %eax
movl $LDT1_SEL, %edi
call set_base
# setup_idt将256个中断向量的处理函数都设置为了ignore_int
call setup_idt
# 将全局描述符表的长度以及起始地址载入到寄存器gdtr中
call setup_gdt
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt.
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss stack_ptr,%esp
# setup up timer 8253 chip.
movb $0x36, %al
movl $0x43, %edx
outb %al, %dx
movl $11930, %eax # timer frequency 100 HZ
movl $0x40, %edx
outb %al, %dx
movb %ah, %al
outb %al, %dx
#
# 操作系统有三种中断,软件中断,硬件中断和处理器异常
# timer_interrupt是硬件中断处理程序,告诉系统时间片到了,切换进程
# system_interrupt是软件中断处理程序,用户进程可以利用该系统调用打印字母A或者B
#
# 实模式下的IDT(中断描述符表)在内存中从地址0x0开始的地方
# 保护模式下有一个寄存器idtr专门指向IDT开始的地方
# 因此IDT不必放在地址0x0开始的地方了
# 实模式下一个中断向量占4个字节,256项共1k字节
# 保护模式下一个中断描述符占8个字节,256项共2k字节
#
# 保护模式下的中断描述符也就是中断门、陷阱门或者任务门。有的书上说还有一种调用门
# 中断门和陷阱门的唯一区别就是中断门不允许进一步中断,也就是说执行中断门所指的
# 中断处理程序时是关中断的。中断门用来处理硬件中断
# 陷阱门相反,允许中断,处理软件中断。在下面的代码中就体现了这一点
# 任务门提供了一种切换进程的硬件级支持。现在的Linux内核并没有使用这种机制
# Linux只使用了TSS结构中的I/O端口使用位图和桟指针
# 不过linux-0.00确实使用了这种硬件切换机制
#
# 设置中断处理程序所在的段是系统代码段,也就是gdt表的第二项
# 并将中断处理程序在段中偏移的高16位置为0
movl $0x00080000, %eax
# 设置中断处理程序在段中偏移的低16位
movw $timer_interrupt, %ax
# 内核态中断门,并且该门有效
movw $0x8E00, %dx
# timer_interrupt是32号中断向量
movl $0x20, %ecx
lea idt(,%ecx,8), %esi
# 将设置好的中断门的内容写到idt相应的表项(32)中
movl %eax,(%esi)
movl %edx,4(%esi)
# 设置中断处理程序在段中偏移的低16位
movw $system_interrupt, %ax
# 用户态陷阱门,并且该门有效
movw $0xef00, %dx
# system_interrupt是128号中断向量
movl $0x80, %ecx
lea idt(,%ecx,8), %esi
# 将设置好的陷阱门的内容写到idt相应的表项(128)中
movl %eax,(%esi)
movl %edx,4(%esi)
# unmask the timer interrupt.
movl $0x21, %edx
inb %dx, %al
andb $0xfe, %al
outb %al, %dx
#
# 下面三行代码设置EFLAGS的NT为0
# NT=0为下面的任务切换做准备
# 《Linux内核完全注释》第102页403行代码如下(kernel/sched.c):
# __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
# 下面三行就是上面代码的展开
#
# NT是nested task的缩写,EFLAGS第15个标志位。当NT=0时不发生任务切换
# 在后面的tss0和tss1结构体中eflags=0x0200,也就是NT=1,发生任务切换
#
# x86一共有四种方式切换任务
# 1. ljmp $task_selector, $0
# 2. lcall $task_selector, $0
# 3. int 0x80
# 4. iret
# ljmp和lcall的区别是ljmp再也跳转不回来了,而lcall还可以跳转回来
# int 0x80必须跳转到任务门
# iret必须将NT设置为1
# 后面的计时器中断处理函数timer_interrupt采用的是ljmp
# ljmp $TSS0_SEL, $0
# ljmp $TSS1_SEL, $0
#
pushfl
andl $0xffffbfff, (%esp)
popfl
# 载入进程0的任务寄存器
movl $TSS0_SEL, %eax
ltr %ax
# 载入进程0的局部描述符表寄存器
movl $LDT0_SEL, %eax
lldt %ax
# 设置当前进程为0
movl $0, current
# 开中断。iret后两个任务就可以交替运行
sti
pushl $0x17
pushl $stack0_ptr
pushfl
pushl $0x0f
pushl $task0
# 不发生任务切换(NT=0),仅仅跳转到task0处以用户态执行
# 我觉得也可以理解为跳转到了一个特殊的任务0进程
iret
/****************************************/
setup_gdt:
lgdt lgdt_opcode
ret
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt lidt_opcode
ret
# in: %eax - logic addr; %ebx = base addr ;
# %ecx - table addr; %edi - descriptors offset.
set_base:
addl %ebx, %eax
addl %ecx, %edi
movw %ax, 2(%edi)
rorl $16, %eax
movb %al, 4(%edi)
movb %ah, 7(%edi)
rorl $16, %eax
ret
write_char:
push %gs
pushl %ebx
pushl %eax
# 载入显存段
mov $0x18, %ebx
mov %bx, %gs
# 得到上一次显示字符的位置
movl scr_loc, %bx
# 每个字符位占两个字节,第一个字节就是我们要显示的字节
shl $1, %ebx
# 将字符'A'或'B'直接写到显存中
movb %al, %gs:(%ebx)
# 一个字符位占两个字节,第二个字节是属性字节
# 例如0x07就是正常显示,黑底白字;0x70就是反相显示,白底黑字
# 当程序设置了一个属性时,它就保持这种属性,之后的所有字符都具有相同的属性
# 直到另外一个操作改变了属性
# 如果在这里添加如下三行,就会得到白底黑字的效果
# inc %ebx
# movb $0x70, %gs:(%ebx)
# dec %ebx
shr $1, %ebx
incl %ebx
# CGA提供了两种文本模式40x25和80x25,如果是80x25也就是说一屏可以有2000个字符
# 每个字符占8x8个像素,则CGA有两种分辨率320x200和640x200
# 如果将下面的2000改为1000,我们会发现只出现前半屏的输出
cmpl $2000, %ebx
# 如果写满了一屏,则重新开始
jb 1f
# 将计数器赋为0
movl $0, %ebx
# 保存当前光标位置
1: movl %ebx, scr_loc
popl %eax
popl %ebx
pop %gs
ret
/***********************************************/
/* This is the default interrupt "handler" :-) */
.align 2
ignore_int:
push %ds
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movl $67, %eax /* print 'C' */
call write_char
popl %eax
pop %ds
iret
/* Timer interrupt handler */
.align 2
timer_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movb $0x20, %al
outb %al, $0x20
movl $1, %eax
cmpl %eax, current
je 1f
movl %eax, current
ljmp $TSS1_SEL, $0
jmp 2f
1: movl $0, current
ljmp $TSS0_SEL, $0
2: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret
/* system call handler */
.align 2
system_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %edx
mov %dx, %ds
call write_char
popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret
/*********************************************/
current:.long 0
scr_loc:.long 0
.align 2
.word 0
lidt_opcode:
.word 256*8-1 # idt contains 256 entries
.long idt + KRN_BASE # This will be rewrite by code.
.align 2
.word 0
lgdt_opcode:
.word (end_gdt-gdt)-1 # so does gdt
.long gdt + KRN_BASE # This will be rewrite by code.
.align 3
idt: .fill 256,8,0 # idt is uninitialized
gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a01000007ff /* 8Mb 0x08, base = 0x10000 */
.quad 0x00c09201000007ff /* 8Mb 0x10 */
# 对照段描述符表项定义可以知道该段的基址是0xb8000(736K)
# 《Linux内核完全注释》一书206页649行代码如下
# vidio_mem_start = 0xb8000
# 说明了彩色模式下显示内存的起始地址是0xb8000
.quad 0x00c0920b80000002 /* screen 0x18 - for display */
.quad 0x0000e90100000068 # TSS0 descr 0x20
.quad 0x0000e20100000040 # LDT0 descr 0x28
.quad 0x0000e90100000068 # TSS1 descr 0x30
.quad 0x0000e20100000040 # LDT1 descr 0x38
end_gdt:
.fill 128,4,0
stack_ptr:
.long stack_ptr
.word 0x10
/*************************************/
.align 3
ldt0: .quad 0x0000000000000000
.quad 0x00c0fa01000003ff # 0x0f, base = 0x10000
.quad 0x00c0f201000003ff # 0x17
tss0:
.long 0 /* back link */
.long stack0_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task0 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack0_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT0_SEL /* ldt */
.long 0x8000000 /* trace bitmap */
.fill 128,4,0
stack0_krn_ptr:
.long 0
/************************************/
.align 3
ldt1: .quad 0x0000000000000000
.quad 0x00c0fa01000003ff # 0x0f, base = 0x10000
.quad 0x00c0f201000003ff # 0x17
tss1:
.long 0 /* back link */
.long stack1_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task1 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack1_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT1_SEL /* ldt */
.long 0x8000000 /* trace bitmap */
.fill 128,4,0
stack1_krn_ptr:
.long 0
/************************************/
task0:
movl $0x17, %eax
movw %ax, %ds
movl $65, %al /* print 'A' */
int $0x80
movl $0xfff, %ecx
1: loop 1b
jmp task0
.fill 128,4,0
stack0_ptr:
.long 0
task1:
movl $0x17, %eax
movw %ax, %ds
movl $66, %al /* print 'B' */
int $0x80
movl $0xfff, %ecx
1: loop 1b
jmp task1
.fill 128,4,0
stack1_ptr:
.long 0
/*** end ***/
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/23177/showart_284630.html |
|