• linux进程切换问题


    #define switch_to(prev,next,last) do { 
            unsigned long esi,edi; 
            asm volatile("pushfl
    	" 
                         "pushl %%ebp
    	" 
                         "movl %%esp,%0
    	" /* save ESP */ 
                         "movl %5,%%esp
    	" /* restore ESP */ 
                         "movl $1f,%1
    	" /* save EIP */ 
                         "pushl %6
    	" /* restore EIP */ 
                         "jmp __switch_to
    " 
                         "1:	" 
                         "popl %%ebp
    	" 
                         "popfl" 
                         :"=m" (prev->thread.esp),"=m" (prev->thread.eip), 
                          "=a" (last),"=S" (esi),"=D" (edi) 
                         :"m" (next->thread.esp),"m" (next->thread.eip), 
                          "2" (prev), "d" (next)); 
    } while (0)
    

    注意:switch_to是宏,所以prev,next,last参数是通过寄存器传递的,直接就是栈中的局部变量,switch_to时请注意:这是一个宏,不是函数,它的参数prev, next, last不是值拷贝,而是它的调用者context_switch()的局部变量。局部变量是通过%ebp寄存器来索引的,也就是通过n(%ebp),n是编译时决定的,在不同的进程的同一段代码中,同一局部变量的n是相同的。

    在A进程中的prev和next,是其局部变量,可以成为prev_A,next_B,指的是进程描述符的内存地址

    在A进程中,switch_to(prev,next,prev)

    prev,next,last这三个都是schedue函数的局部变量,寻址都是以EBP为基地址的

    step1:复制两个局部变量到寄存器:
        
        eax <== prev_A 或eax <==%p(%ebp_A)
        edx <== next_A 或edx <==%n(%ebp_A)
        这里prev和next都是A进程的局部变量。

    step2:保存进程A的ebp和eflags
        pushfl
        pushl %ebp
        注意,因为现在esp还在A的堆栈中,所以这两个东西被保存到A进程的内核堆栈中。

    step3:保存当前esp到A进程内核描述符中://thread_struct的thread字段:存储了esp和eip等大部分寄存器
        "movl %%esp,%[prev_sp]/n/t"    /*save    ESP   */
        它可以表示成:prev_A->thread.sp <== esp_A
        在调用switch_to时,prev是指向A进程自己的进程描述符的。

    step4:从next(进程B)的描述符中取出之前从B切换出去时保存的esp_B:其实是将next->thread.esp装入esp

       "movl%[next_sp],%%esp/n/t" /* restore ESP */
        它可以表示成:esp_B<== next_A->thread.sp
        注意,在A进程中的next是指向B的进程描述符的。
        从这个时候开始,CPU当前执行的进程已经是B进程了,因为esp已经指向B的内核堆栈。但是,现在的ebp仍然指向A进程的内核堆栈,所以所有局部变量仍然是A中的局部变量,比如next实质上是%n(%ebp_A),也就是next_A,即指向B的进程描述符。

    此时,已经完成了进程的堆栈的切换了,但是ebp指向的却是A进程的内核堆栈。

    step5,6:把标号为1的指令地址保存到A进程描述符的ip域:

    step6:将返回地址保存到堆栈,然后调用__switch_to()函数,__switch_to()函数完成硬件上下文切换。
        "pushl %[next_ip]/n/t"    /*restore EIP   */由于上步导致的esp和ebp不是指向同一个堆栈,所以这部next的变量属于A进程的局部变量,代表的是B的进程描述符指针,next->thread.ip压入next的内核栈(也就是B的内核栈)
        "jmp __switch_to/n"    /* regparmcall  */

    step7:从__switch_to()返回后继续从1:标号后面开始执行,修改ebp到B的内核堆栈,恢复B的eflags:
        "popl %%ebp/n/t"       /* restore EBP   */    
        "popfl/n"           /* restore flags */
        如果从__switch_to()返回后从这里继续运行,那么说明在此之前B肯定被switch_to调出过,因此此前肯定备份了ebp_B和flags_B,这里执行恢复操作。

      注意,这时候ebp已经指向了B的内核堆栈,所以上面的prev,next等局部变量已经不是A进程堆栈中的了,而是B进程堆栈中的(B上次被切换出去之前也有这两个变量,所以代表B堆栈中prev、next的值了),因为prev == %p(%ebp_B),而在B上次被切换出去之前,该位置保存的是B进程的描述符地址。如果这个时候就结束switch_to的话,在后面的代码中(即 context_switch()函数中switch_to之后的代码)的prev变量是指向B进程的,因此,进程B就不知道是从哪个进程切换回来。而从context_switch()中switch_to之后的代码中,我们看到finish_task_switch(this_rq(), prev)中需要知道之前是从哪个进程切换过来的,因此,我们必须想办法保存A进程的描述符到B的堆栈中,这就是last的作用。

    step8:将eax写入last,以在B的堆栈中保存正确的prev信息。
        "=a" (last)  即 last_B <== %eax
        而从context_switch()中看到的调用switch_to的方法是:
        switch_to(prev, next, prev);
        所以,这里面的last实质上就是prev,因此在switch_to宏执行完之后,prev_B就是正确的A的进程描述符了。   
        这里,last的作用相当于把进程A堆栈中的A进程描述符地址复制到了进程B的堆栈中。

    eax总是保持被切换进程的值

  • 相关阅读:
    PyCharm 2020 激活到 2100 年
    PyTorch-22 学习 PyTorch 的 Examples
    机器视觉的主要研究内容和细分方向(超全超赞)
    CVPR 2020 全部论文 分类汇总和打包下载
    2020年,加油鸭
    SpiningUP 强化学习 中文文档
    大学C语言基础——30天挑战PAT甲级(一)
    明天提交辞职报告
    要开始新的技术之旅了
    记忆是件奇怪的东西
  • 原文地址:https://www.cnblogs.com/kkshaq/p/4546875.html
Copyright © 2020-2023  润新知