从零构建内核:多任务

在上一个进程的基础上,扩展多任务操作系统。

进程切换过程

第一步,时间中断。从内核代码(最初状态)或者某一进程陷入时间中断的服务例程;

第二步,切换堆栈。从代码一开始运行的堆栈进入PCB里的保存现场用的堆栈。切换的过程使用了预先设定好的TSS结构;

第三步,保护现场。一系列的push指令将寄存器的值依次保存进堆栈;

第四步,切换堆栈。从PCB里保护现场的堆栈切换进运行调度函数用的内核运行栈;

第五步,调度程序。运行调度函数,以某种算法选择合适的进程作为ready_task

第六步,切换GDT中的LDT。因为所有进程的LDT共用一个GDT中的描述符,在任务开始前这个描述符需要进行相应修改;

第七步,切换堆栈。将esp从内核运行栈中拿出来放到下一个将要运行进程PCB的保存现场栈中;

第八步,恢复现场。一系列的pop指令取出各寄存器的值;

第九步,回复执行。retd。

实现细节

每个进程需要分别被初始化的内容:

  • 进程名
  • 进程ID
  • LDT
  • 各个段寄存器(cs,ds,es,fs,ss,gs)
  • eip和esp
  • eflags = 0x1202
  • ldt selector = 32(进程共用一个GDT中LDT描述符)

所有进程共享的被初始化的内容:

  • ready_task指向默认进程表中第一个进程
  • tss

时间中断处理例程代码:

hwint00:
        sub    esp, 4
        pushad
        push    ds
        push    es
        push    fs
        push    gs
        mov    dx, ss    ; ss's DPL is 0 which others want to be
        mov    ds, dx
        mov    es, dx

        mov    al, 0x20    ; EndOfInterupt
        out    0x20, al

        inc    dword [k_reenter]
        cmp    dword [k_reenter], 0
        jne    .re_enter

    ; move to kernel stack
        mov    esp, (kernel_stack + KERNEL_STACK_SIZE)

        sti
    ; call functions
        call    scheduler

        cli
    ; leave kernel stack to pcb.regs
        mov    esp, [ready_task]
        lldt    [esp + (18*4)]
        lea    eax, [esp + (18*4)]
        mov    dword [itss + 4], eax

.re_enter:
        dec    dword [k_reenter]
        pop    gs
        pop    fs
        pop    es
        pop    ds
        popad
        add    esp, 4
        iretd