zoukankan      html  css  js  c++  java
  • 进程调度和切换---linux内核学习笔记(八)

    内容一:实验报告相关说明

     

    所学课程:《Linux内核分析》MOOC课程  

    链接:http://mooc.study.163.com/course/USTC-1000029000

     

    内容二:linux系统的调度时机

      主要有以下时机:

    • 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();

    • 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;

    • 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

    内容三:进程切换

       为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换,任务切换或上下文切换。

      进程切换可能只发生在精心定义的点:schedule()函数,至于如何选择下一个待切换的进程,有很多相关的算法,为了方便理解,就简单抽象为使用某种调度算法从运行

    队列中找到需要调度的进程。

      通过上课总结了一下几点:

      3.1:schedule()为内核函数,并非系统调用,所以用户态只能被动调用。

      3.2:内核进程可以直接调用schedule()函数。

      3.3:中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();

      3.4:schedule()函数调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换

      3.5:switch_to 利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程。

    内容四:switch_to分析

      源代码如下:

    31#define switch_to(prev, next, last)                    
    32do {                                    
    33    /*                                
    34     * Context-switching clobbers all registers, so we clobber    
    35     * them explicitly, via unused output variables.        
    36     * (EAX and EBP is not listed because EBP is saved/restored    
    37     * explicitly for wchan access and EAX is the return value of    
    38     * __switch_to())                        
    39     */                                
    40    unsigned long ebx, ecx, edx, esi, edi;                
    41                                    
    42    asm volatile("pushfl
    	"        /* save    flags */    
    43             "pushl %%ebp
    	"        /* save    EBP   */    
    44             "movl %%esp,%[prev_sp]
    	"    /* save    ESP   */ 
    45             "movl %[next_sp],%%esp
    	"    /* restore ESP   */ 
    46             "movl $1f,%[prev_ip]
    	"    /* save    EIP   */    
    47             "pushl %[next_ip]
    	"    /* restore EIP   */    
    48             __switch_canary                    
    49             "jmp __switch_to
    "    /* regparm call  */    
    50             "1:	"                        
    51             "popl %%ebp
    	"        /* restore EBP   */    
    52             "popfl
    "            /* restore flags */    
    53                                    
    54             /* output parameters */                
    55             : [prev_sp] "=m" (prev->thread.sp),        
    56               [prev_ip] "=m" (prev->thread.ip),        
    57               "=a" (last),                    
    58                                    
    59               /* clobbered output registers: */        
    60               "=b" (ebx), "=c" (ecx), "=d" (edx),        
    61               "=S" (esi), "=D" (edi)                
    62                                           
    63               __switch_canary_oparam                
    64                                    
    65               /* input parameters: */                
    66             : [next_sp]  "m" (next->thread.sp),        
    67               [next_ip]  "m" (next->thread.ip),        
    68                                           
    69               /* regparm parameters for __switch_to(): */    
    70               [prev]     "a" (prev),                
    71               [next]     "d" (next)                
    72                                    
    73               __switch_canary_iparam                
    74                                    
    75             : /* reloaded segment registers */            
    76            "memory");                    
    77} while (0)

      分析的参考资料来自如下博客:

      作者:visayafan 
      出处:http://www.cnblogs.com/visayafan/ 

      4.1:当schedule()需要暂停A进程的执行而继续B进程的执行时,就发生了进程之间的切换。进程切换主要有两部分:1、切换全局页表项;2、切换内核堆栈和硬件上下文。这个切换工作由

    context_switch()完成。其中switch_to和__switch_to()主要完成第二部分。更详细的,__switch_to()主要完成硬件上下文切换,switch_to主要完成内核堆栈切换。

      4.2:switch_to时请注意:这是一个宏,不是函数,它的参数prev, next, last不是值拷贝,而是它的调用者context_switch()的局部变量。局部变量是通过%ebp寄存器来索引的,也就是

    通过n(%ebp)。

      4.3:switch_to切换主要有以下三部分:

    进程切换

    即esp的切换

    由于从esp可以找到进程的描述符

    硬件上下文切换

    _switch_to()

    以前通过x86硬件支持,现在使用软件切换

    堆栈的切换

    即ebp的切换

    ebp是栈底指针,它确定了当前变量空间属于哪个进程

     

     

     

     

     

      

      大概的步骤如下:(假设进程A切换到进程B)

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

    "pushfl
    	"        /* save    flags */    
    "pushl %%ebp
    	"        /* save    EBP   */    

      step2:保存当前esp到A进程内核描述符中(即进程A的PCB中)

    "movl %%esp,%[prev_sp]
    	"  /* save    ESP   */ 

      step3:从next(进程B)的描述符中取出之前B在切换出去时保存的内核堆栈栈顶

    "movl %[next_sp],%%esp
    	"    
    
    

      step4:把标号为1的指令地址保存到A进程描述符的ip域,当A进程下次被switch_to回来时,会从这条指令开始执行。

    "movl $1f,%[prev_ip]
    	"    /* save    EIP   */    
    
    

      step5:将返回地址保存到堆栈,然后调用__switch_to()函数,__switch_to()函数完成硬件上下文切换。

    "pushl %[next_ip]
    	"    /* restore EIP   */     
     "jmp __switch_to
    "    /* regparm call  */      

      如果之前B也被switch_to出去过,那么[next_ip]里存的就是下面这个1f的标号,但如果进程B刚刚被创建,之前没有被switch_to出去过,那么[next_ip]里存的将是ret_ftom_fork(参看copy_thread()函数)。这就是这里为什么不用call __switch_to而用jmp,因为call会导致自动把下面这句话的地址(也就是1:)压栈,然后__switch_to()就必然只能ret到这里,而无法根据需要ret到ret_from_fork。

        另外请注意,这里__switch_to()返回时,将返回值prev_A又写入了%eax,这就使得在switch_to宏里面eax寄存器始终保存的是prev_A的内容,或者,更准确的说,是指向A进程描述符的“指针”。

      step6:从__switch_to()返回后继续从1:标号后面开始执行,修改ebp到B的内核堆栈,恢复B的eflags

    "1:	"                        
    "popl %%ebp
    	"     /* restore EBP   */    
    "popfl

    内容六:小结 

      1:通过本次课的学习,把握了进程调度的时机,掌握了进程A切换到进程B发生的事情和基本流程,以及一些特殊情况。

      2:区分了进程上下文切换和中断上下文的切换。

      3:通过查阅资料和听课,对switch_to的功能有了较好的把握,理解的关键就在于:next进程如果之前也被switch_to出去过,那么[next_ip]里存的就是下面这个1f的标号,

    但如果next进程刚刚被创建,之前没有被switch_to出去过,那么[next_ip]里存的将是ret_ftom_fork,这样又回到了进程创建的知识点上了。这样前后的知识就连贯起来了。

     

  • 相关阅读:
    亲历dataguard的一些经验问答题
    [转]ORA-38500: USING CURRENT LOGFILE option not available without stand
    修改npm全局安装模式的路径
    Vue 环境搭建
    Linux下查看系统版本号信息的方法
    每天一个Linux命令(12):su命令
    Ubuntu 首次给root用户设置密码
    适用于Linux的windows子系统
    IDEA的terminal设置成Linux的终端一样
    Windows模拟linux终端工具Cmder+Gow
  • 原文地址:https://www.cnblogs.com/esxingzhe/p/4458360.html
Copyright © 2011-2022 走看看