Linux之进程(一)

进程切换
本质:任务中断时将堆栈指针指向另外一个堆栈B,该堆栈中保存有B进程运行时,cpu 各类寄存器状态,当利用iretd返回时,CPU的寄存器值会被堆栈B中的状态填充,变成了进程B的状态,sp指针也指向任务B,此时任务跳到了B中,完成了任务切换

今天讲述的是一个比较简陋的进程切换,步骤如下
1.进程表初始化
2.调用restart()函数。

int kernel_main()
{
        disp_str("-----\"kernel_main\" begins-----\n");

        PROCESS* p_proc = proc_table;

        p_proc->ldt_sel = SELECTOR_LDT_FIRST;
        memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS>>3], sizeof(DESCRIPTOR));
        p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5;     // change the DPL
        memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS>>3], sizeof(DESCRIPTOR));
        p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5;   // change the DPL

        p_proc->regs.cs = (0 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
        p_proc->regs.ds = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
        p_proc->regs.es = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
        p_proc->regs.fs = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
        p_proc->regs.ss = (8 & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
        p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
        p_proc->regs.eip= (u32)TestA;
        p_proc->regs.esp= (u32) task_stack + STACK_SIZE_TOTAL;
        p_proc->regs.eflags = 0x1202;   // IF=1, IOPL=1, bit 2 is always 1.

        p_proc_ready    = proc_table;
        restart();

        while(1){}
}

可以看到 在kernel_main()前面大部分都是进程表结构体的初始化,接着调用restart如下,在下面程序中第四行、第五行的作用是将s_proc这个结构体中第一个结构体成员的末地址赋值给tss中ring0堆栈指针域esp.注意,这里是末地址,堆栈中已经存好值了,因为刚开始的时候,初始化便在里面存好了值。所以是末地址!!!

; ========================================================
;                                   restart
; ==========================================================
restart:
        mov     esp, [p_proc_ready]
        lldt    [esp + P_LDT_SEL]
        lea     eax, [esp + P_STACKTOP]
        mov     dword [tss + TSS3_S_SP0], eax

        pop     gs
        pop     fs
        pop     es
        pop     ds
        popad

        add     esp, 4
        iretd

主要作用为 :将指向进程表的指针指向下一个进程表;加载局部描述符;将s_proc第一个结构体成员赋给TSS 中ring0堆栈指针,这个函数的作用是用来开始第一个跳转的时候使用的,在后期的跳转中,因为已经有了

在完成指针的转换操作后, 可以看到程序利用pop指令,将堆栈中保存的值都放到了寄存起中,然后通过iretd完成了任务切换(指针指向了任务B的esp)

在接下来的介绍中,我们需要用到中断,当中断到来,进行任务切换,这才是一般操作系统的用法,而不是运行到某函数,自然而然进行中断。

首先需要做的是实现一个中断,在前面我们看到有实现过定时器的中断,首先是打开时钟中断

void init_8259A(void)
{
        /* Master 8259, ICW1. */
        out_byte(INT_M_CTL,     0x11);

        /* Slave  8259, ICW1. */
        out_byte(INT_S_CTL,     0x11);

        /* Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. */
        out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0);

        /* Slave  8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 */
        out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8);

        /* Master 8259, ICW3. IR2 对应 '从8259'. */
        out_byte(INT_M_CTLMASK, 0x4);

        /* Slave  8259, ICW3. 对应 '主8259' 的 IR2. */
        out_byte(INT_S_CTLMASK, 0x2);

        /* Master 8259, ICW4. */
        out_byte(INT_M_CTLMASK, 0x1);

        /* Slave  8259, ICW4. */
        out_byte(INT_S_CTLMASK, 0x1);

        /* Master 8259, OCW1.  */
        out_byte(INT_M_CTLMASK, 0xFE);

        /* Slave  8259, OCW1.  */
        out_byte(INT_S_CTLMASK, 0xFF);
}

接着是在中断处理函数中,我们需要自己的定制

ALIGN   16
hwint00:  
        pushad
        push  ds
        push  es
        push  fs
        push  gs

        inc     byte[gs:0] ;  改变屏幕第0行 第0列的字符  用作调试 证明进入了中断           
        mov     al, EOI  ;让时钟中断可以不断进入
        out     INT_M_CTL, al;

        pop    gs
        pop    fs
        pop    es
        pop    ds
        popad

        iretd           ; 中断返回  装载堆栈内的数据

在中断中,为了防止内部的一些函数调用会改变 ds es fs gs 等等 ,因此用了压栈和出栈的操作用来保护这些寄存器的值 ,在这个中断处理函数的最后已经有了 出栈和iretd操作,可以让内核切换到新的任务中去

但是新的任务的压栈过程还没有写

当前进程被中断切到内核态时,当前的各个就承诺期应该诶立即保存(压栈过程),也就是说,每个进程在运行时,tss.esp0应该是当前进程的进程表中保存寄存器值的地方,即struct s_proc中struct s_stackframe的最高地址处。这样进程被挂起后才恰好保存寄存器到正确的位置。我们假设进程A在运行,那么tss.esp0的值就是进程表A中regs的最高处,因为我们是不可能在进程A运行时来设置tss.esp0的值的,所以我们必须在A被恢复运行之前,即iretd执行之前做这件事。换句话说,我们需要在时钟中断处理函数结束之前做这件事。
这里我们需要的就不能是末地址了,而必须是堆栈的初地址。

设置tss.esp0
代码如下,其中新添加的
lea eax, [esp+P_STACKTOP]
mov dword[tss+TSS3_S_SP0], eax
便是为了设置tss.esp0, 此处添加的代码与restart中的代码一模一样,指向了堆栈底部。压栈压的是当前任务的栈,然后堆栈指针指向下一个任务的栈,出栈时就出的是下一个任务的栈,再用irted,任务就完全跳转到下一个任务了。


ALIGN   16
hwint00:                ; Interrupt routine for irq 0 (the clock).

        sub     esp, 4
        pushad
        push    ds
        push    es
        push    fs
        push    gs

        mov     dx, ss
        mov     ds, dx
        mov     es, dx


        inc     byte [gs:0]
        mov     al, EOI
        out     INT_M_CTL, al;

        lea     eax, [esp+P_STACKTOP]
        mov     dword[tss+TSS3_S_SP0], eax

        pop     gs
        pop     fs
        pop     es
        pop     ds
        popad

        add     esp, 4

        iretd           ; 中断返回  装载堆栈内的数据

到目前为止,任务所能实现的状态为中断 任务A 中断 任务A 中断 任务A
还没有进行两个任务的中断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值