CosOS任务切换的实现

来源:互联网 发布:手机照片写字软件 编辑:程序博客网 时间:2024/05/14 15:04

CosOS是我为我设计编写的操作系统取的名称,有关CosOS的介绍请阅读:CosOS 0.01 alpha 操作系统预览

 

CosOS通过时钟中断来中断当前任务的执行,并进行任务切换,当时钟中断触发时,中断处理函数irq_common_stub将得到执行,以下部分将以注释的形式,详细介绍CosOS中任务切换的具体实现。

 

 

 

 

irq_common_stub:

pushadEAXECXEDXEBXESPEBPESIEDI全部压入栈中。

pushad  

;由于不能直接把ds段寄存器压入栈,所以先把ds的值传递到ax寄存器中。

mov ax, ds

;这时,eax的低16位中保存有ds的值,压入栈中。

push eax

;以上代码完成当前进程的现场保护。以便下次被调度时还原进程。

;由于段寄存器不能直接使用立即数设置,所以先把值放入ax寄存器中。

mov ax, 0x10

    mov ds, ax

    mov es, ax

    mov fs, ax

mov gs, ax

;以上代码把所有的段寄存器设置0x10,即内核数据段。

    push esp

;调用IRQ处理函数,由于触发的是时钟中断,irq_handler内会调用timer_callback函数。

call irq_handler

    add esp, 4

    jmp switch_stack

 

每次时钟中断触发时,timer_callback函数都将得到执行。

static void timer_callback(registers_t *regs)

{

tick++;

//tick中保存有自开机以来,时钟中断触发的次数。

    if(key_pressed)

    {

        inform_int(TTY_TASK);

    }

//以上代码用于唤醒等待消息中的TTY任务,与任务切换无关。

schedule();

}

//schedule进行任务调度

 

任务调度的方案是选择剩余时间片最多的进程,并且它处于等待就绪状态,如果所有进程的时间片都已经用完,则按照它们的优先级重新设置时间片。

 

void schedule()

{

    task_t* select_task = NULL;

    while(1)

    {

//  首先选择时间片最多的进程

        select_task = get_greatest_ticks_task();

//  若时间片最多的进程时间片为0,则说明所有进程的时间片都已经用完。需要重新设置它们时间片。

if(select_task->ticks == 0)

        {

            volatile task_t *p_task = NULL;

            for(p_task = ready_queue; p_task != NULL;p_task = p_task->next)

            {

//  如果进程处于就绪态,则根据优先级设置时间片

                if(p_task->flags == 0)

                {

                    p_task->ticks = MAX_TICKS - p_task->priority;

//时间片时钟为最大时间片减去优先级,因此,priority越小优先级越大

                }

            }

        }

        if(select_task->flags != 0)

        {

//  若时间片最多的进程不处于就绪态,则将其时间片设置为0,以免下一while轮循环再次被选中。

            select_task->ticks = 0;

        }

        else

        {

//  若时间片最多的进程处于就绪态,则跳出while循环。

            break;

        }

    }

//  把当前进程设置为调度算法所选择的进程

    current_task = select_task;

//  将其时间片减去一。

    current_task->ticks--;

//  将当前所使用的页表切换为当前进程的页表

    current_directory = current_task->page_directory;

//  设置TSS结构体中ESP字段,用户下次切换到内核态时,切换到正确的内核栈。

    set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);

//  以上代码只是设置相应的变量,并没有开始进行真正的任务切换。

//  切换页表,内部通过设置CR3寄存器更新页目录地址来完成。

    switch_page_directory(current_directory);

}

 

irq_handler返回之后,将执行以下代码:

Switch_stack将堆栈切换到调度之后当前任务的内核栈。

current_taskC语言代码中的变量,为了在汇编中使用,必须将其申明为外部变量

extern current_task

switch_stack:

  内核栈的地址保存在task结构体的第一步成员中,首先获取到task结构体的首地址,保存在eax中。

    mov eax,dword [current_task]

  保存有内核栈地址的字段为task结构体的第一个成员,所以其地址和task结构体首地址相等,通过mov指令,将内核栈地址复制到ebx中。

    mov ebx,dword [eax]

  以下三条指令,计算出进程信息所在的位置,并将esp移到改位置。

add ebx,0x2000 ;kernel stack size

    sub ebx,0x40

    mov esp,ebx

  跳转到 ret_from_irq,继续执行

    jmp ret_from_irq

 

ret_from_irq:

  从栈中还原段寄存器

    pop ebx

    mov ds, bx

    mov es, bx

    mov fs, bx

    mov gs, bx

  从栈中还原EAXECXEDXEBXESPEBPESIEDI寄存器

popad

add esp, 8

  跳过栈中一些无关的数据,如中断号等。

  执行iret指令之后,将结束时钟中断过程,这时CPU将回到用户态,并且EIP将指向被中断处的下一条指令,继续执行,完成进程的恢复。

    iret

 

实际上,只要触发了中断,中断处理程序都会进行现场保护,只不过时钟中断会调用schedule改变current_task,从而在恢复现场的时候切换到其它进程。其它中断触发时,虽然也进行了现场保护和现场恢复,但current_task未改变所以恢复到的进程还是原先的进程。

 

更多详情请关注 http://www.ecjtu.org/