一下是从skelix/06/中的代码总结出来的,网上很多资料讲的东西比较分散,这里从该代码的角度来总结的,一些不足的地方待以后添加:
skelix/06/init.c文件。
程序在load.s进入保护模式之后立即调用call init进入该函数,功能请看注释。
/* Skelix by Xiaoming Mo (xiaoming.mo@skelix.org)
* Licence: GPLv2 */
#include
#include
#include
#include
#include
#include
#include
#include
#include
unsigned long long *idt = ((unsigned long long *)IDT_ADDR);
unsigned long long *gdt = ((unsigned long long *)GDT_ADDR);
static void
isr_entry(int index, unsigned long long offset) {
unsigned long long idt_entry = 0x00008e0000000000ULL |
((unsigned long long)CODE_SEL<<16);
idt_entry |= (offset<<32) & 0xffff000000000000ULL;
idt_entry |= (offset) & 0xffff;
idt[index] = idt_entry;
}
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};
for (i=0; i isr_entry(i, (unsigned int)(isr[(i<<1)+1]));
for (++i; i<256; ++i)
isr_entry(i, (unsigned int)default_isr);
__asm__ ("lidt %0\n\t"::"m"(idt_descr));
}
static void
pic_install(void) {
outb(0x11, 0x20);
outb(0x11, 0xa0);
outb(0x20, 0x21); /* 这里注册始终中断处理和键盘中断处理函数,他们的中断向量号分别是
0x20, 0x21,调用do_timer()和do_kb()函数 */
outb(0x28, 0xa1);
outb(0x04, 0x21);
outb(0x02, 0xa1);
outb(0x01, 0x21);
outb(0x01, 0xa1);
outb(0xff, 0x21);
outb(0xff, 0xa1);
}
extern void task1_run(void);
extern void task2_run(void);
/* task1和task2的堆栈, 为什么有两个还不清楚 */
static long task1_stack0[1024] = {0xf, };
static long task1_stack3[1024] = {0xf, };
static long task2_stack0[1024] = {0xf, };
static long task2_stack3[1024] = {0xf, };
static void
new_task(struct TASK_STRUCT *task, unsigned int eip,
unsigned int stack0, unsigned int stack3) {
memcpy(task, &TASK0, sizeof(struct TASK_STRUCT));
task->tss.esp0 = stack0;
task->tss.eip = eip;
task->tss.eflags = 0x3202;
task->tss.esp = stack3;
task->priority = INITIAL_PRIO;
task->state = TS_STOPPED;
task->next = current->next;
current->next = task;
task->state = TS_RUNABLE;
}
void
do_task1(void) {
__asm__ ("incb 0xb8000+160*24+2");
}
void
do_task2(void) {
__asm__ ("incb 0xb8000+160*24+4");
}
void
init(void) {
char wheel[] = {'\\', '|', '/', '-'};
int i = 0;
struct TASK_STRUCT task1;
struct TASK_STRUCT task2;
idt_install();
pic_install();
kb_install();
timer_install(100);
set_tss((unsigned long long)&TASK0.tss);
set_ldt((unsigned long long)&TASK0.ldt);
__asm__ ("ltrw %%ax\n\t"::"a"(TSS_SEL));
__asm__ ("lldt %%ax\n\t"::"a"(LDT_SEL)); /* 重要,load LDT_SEL to LDT register,LDT_SEL值是0x20,是一个GDT中的描述符的索引值,从这里可以看出LDT寄存器中的值的作用,是存储GDT中描述符的索引值的 */
sti(); /* 开中断,这样时钟和键盘中断可以被执行 */
/* 将taks1和task2加入到task链表中,这样在执行do_timer中的skeduler()函数是就会调度到
这两个函数 */
new_task(&task1,
(unsigned int)task1_run,
(unsigned int)task1_stack0+sizeof task1_stack0,
(unsigned int)task1_stack3+sizeof task1_stack3);
new_task(&task2,
(unsigned int)task2_run,
(unsigned int)task2_stack0+sizeof task2_stack0,
(unsigned int)task2_stack3+sizeof task2_stack3);
// __asm__ ("lop:\tjmp lop\n\t");
/* it works fine if the fllowing __asm__ is commented, what function it
does? 这里的汇编去掉之后程序能正常运行,将一些数据压入堆栈之后又用iret中断返回,
重新获得压入堆栈的值,是什么意思呢? */
__asm__ ("movl %%esp,%%eax\n\t" \
"pushl %%ecx\n\t" \
"pushl %%eax\n\t" \
"pushfl\n\t" \
"pushl %%ebx\n\t" \
"pushl $1f\n\t" \
"iret\n" \
"1:\tmovw %%cx,%%ds\n\t" \
"movw %%cx,%%es\n\t" \
"movw %%cx,%%fs\n\t" \
"movw %%cx,%%gs" \
::"b"(USER_CODE_SEL),"c"(USER_DATA_SEL));
for (;;) {
__asm__ ("movb %%al, 0xb8000+160*24"::"a"(wheel[i]));
if (i == sizeof wheel)
i = 0;
else
++i;
}
}
所以LDT寄存器中存储的是一个索引,用于在GDT表中查找描述符。
每个struct task中有unsigned long long ldt[2],存储属于该任务的ldt描述符。这里面的两个描述符的索引是由USER_CODE_SEL(0x07)和USER_DATA_SEL(0x0f)来索引,低三位是DPL等级别,第四为以上才是索引值,所以0x70索引ldt[0],0x0f索引ldt[2]。
至于USER_CODE_SEL和USER_DATA_SEL是怎样索引到ldt的有待研究。
阅读(3170) | 评论(0) | 转发(0) |