zoukankan      html  css  js  c++  java
  • 2019-2020-1 20199308《Linux内核原理与分析》第九周作业

    《Linux内核分析》

    第八章 可执行程序工作原理进程的切换和系统的一般执行过程

    8.1 知识点

    进程调度的时机

    • ntel定义的中断类型主要有以下几种
      • 硬中断(Interrupt)
      • 软中断/异常(Exception)
        • 故障(Fault)
        • 退出(Abort)
        • 陷阱(Trap)
    • schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
      • next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
      • context_switch(rq, prev, next);//进程上下文切换
      • switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
    • Linux系统的一般执行过程
      • 最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
        • 1.正在运行的用户态进程X
        • 2.发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
        • 3.SAVE_ALL //保存现场
        • 4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
        • 5.标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
        • 6.restore_all //恢复现场
        • 7.iret - pop cs:eip/ss:esp/eflags from kernel stack
        • 8.继续运行用户态进程Y
    • 几种特殊情况
      • 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
      • 内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
      • 创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
      • 加载一个新的可执行程序后返回到用户态的情况,如execve;
      • ch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程


    8.2 核心代码分析

    context_switch代码

    static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)
    {
        struct mm_struct *mm, *oldmm;
    
        prepare_task_switch(rq, prev, next);
    
        mm = next->mm;
        oldmm = prev->active_mm;
        /*
         * For paravirt, this is coupled with an exit in switch_to to
         * combine the page table reload and the switch backend into
         * one hypercall.
         */
        arch_start_context_switch(prev);
    
        if (!mm) {    //如果被切换进来的进程的mm为空切换,内核线程mm为空
            next->active_mm = oldmm;  //将共享切换出去的进程的active_mm
            atomic_inc(&oldmm->mm_count);  //有一个进程共享,所有引用计数加一
            enter_lazy_tlb(oldmm, next);  //普通mm不为空,则调用switch_mm切换地址空间
        } else
            switch_mm(oldmm, mm, next);
    
        if (!prev->mm) {
            prev->active_mm = NULL;
            rq->prev_mm = oldmm;
        }
        /*
         * Since the runqueue lock will be released by the next
         * task (which is an invalid locking op but in the case
         * of the scheduler it's an obvious special-case), so we
         * do an early lockdep release here:
         */
        spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
    
        context_tracking_task_switch(prev, next);
        // 这里切换寄存器状态和栈 
        switch_to(prev, next, prev);
    
        barrier();
        /*
         * this_rq must be evaluated again because prev may have moved
         * CPUs since it called schedule(), thus the 'rq' on its stack
         * frame will be invalid.
         */
        finish_task_switch(this_rq(), prev);
    }
    

    switch_to代码

    #define switch_to(prev, next, last) //prev指向当前进程,next指向被调度的进程                                   
    do {                                                                              
                                                            
             unsigned long ebx, ecx, edx, esi, edi;
                                      
             asm volatile("pushfl
    	"  //把prev进程的flag保存到prev进程的内核堆栈中
                          "pushl %%ebp
    	" //把prev进程的基址ebp保存到prev进程的内核堆栈中
               
                          "movl %%esp,%[prev_sp]
    	"//保存ESP
                          "movl %[next_sp],%%esp
    	"//更新ESP,将下一栈顶保存到ESP中 
                          
                          "movl $1f,%[prev_ip]
    	"//保存当前进程EIP*  
                          "pushl %[next_ip]
    	"//把next进程起点压入next进程的内核堆栈栈顶 
                          __switch_canary                                        
                          "jmp __switch_to
    "//prev进程中设置next进程堆栈
                                             //jmp不同于call,是通过寄存器传递参数,而不是通过堆栈传递参数,所以ret时弹出的是之前压入栈顶的next进程起点
                                             //wancheng EIP的切换
                          "1:	"                                                    
                          "popl %%ebp
    	"   
                          "popfl
    "                         
                                                                                    
                          /* output parameters */                                   
                          : [prev_sp] "=m"(prev->thread.sp),     //保存prev进程的esp
                            [prev_ip] "=m"(prev->thread.ip),     //保存prev进程的eip
                            "=a" (last),                                                
                                                                                       
                          /* clobbered output registers: */              
                            "=b" (ebx), "=c"(ecx), "=d" (edx),              
                            "=S" (esi), "=D"(edi)                            
                                                                                        
                           __switch_canary_oparam                                     
                                                                                        
                              /* input parameters: */                                  
                          : [next_sp]  "m" (next->thread.sp),      //next进程内核堆栈栈顶地址,即esp
                            [next_ip]  "m" (next->thread.ip),      //next进程的原eip
                                                                                       
                          /* regparm parameters for __switch_to():*/  
                          //jmp通过eax寄存器和edx寄存器传递参数
                            [prev]     "a" (prev),                                   
                            [next]     "d" (next)                                    
                                                                                         
                            __switch_canary_iparam                             
                                                                                        
                          : /* 重新加载段寄存器            
                         "memory");                                           
    } while (0)  
    

    8.3 实验

    • 克隆menu,编译内核,启动gdb



    • 在schedule(),context_switch(),pick_next_task()打入断点

    • 按c执行,停在schedule函数处

    • 按c继续执行到pick_next_task断点处

    • 按c继续执行到context_switch断点处,用来实现进程的切换。

    总结

    一次一般的进程切换过程,其中必须完成的关键操作是:切换地址空间、切换内核堆栈、切换内核控制流程,加上一些必要的寄存器保存和恢复。这里,除去地址空间的切换,其他操作要强调“内核”一词。这是因为,这些操作并非针对用户代码,切换完成后,也没有立即跑到next的用户空间中执行。用户上下文的保存和恢复是通过中断和异常机制,在内核态和用户态相互切换时才发生的。schedule()是内核和其他部分用于调用进程调度器的入口,选择哪个进程可以运行,何时将其投入运行。就如switch_to中的方法,通过压栈出栈交换prev_ip和next_ip。然后返回,从而完成进程调度。而用哪个作为下来的进程,则通过优先级的算法和进程调度算法来决定。

  • 相关阅读:
    Vue3使用video插件
    Syntax Error: Error: PostCSS received undefined instead of CSS string
    基于Frida的脱壳工具
    java byte[]与十六进制字符串相互转换
    Linux 安裝mitmproxy抓包基础教程
    Windows 安装mitmproxy 抓包基础教程
    python之get/post请求指定URL返回的网页内容,出现gzip乱码解决
    一张图说明java层与so层分析技巧
    app动态调试so层环境搭建
    frida hook基本操作命令
  • 原文地址:https://www.cnblogs.com/hsj910/p/11875576.html
Copyright © 2011-2022 走看看