Linux2.6内核进程调度核心代码分析

一.schedule()函数的尝试分析

asmlinkage void __sched schedule(void)

{

struct task_struct *prev, *next;

       unsigned long *switch_count;

       struct rq *rq;

       int cpu;

need_resched:

       preempt_disable();   //暂时关闭内核抢占

       cpu = smp_processor_id();    //获取cpu

       rq = cpu_rq(cpu);        //取得cpu上的运行队列

       rcu_qsctr_inc(cpu);

       prev = rq->curr;     //取得运行队列上当前正在执行进程

       switch_count = &prev->nivcsw;

       release_kernel_lock(prev);

need_resched_nonpreemptible:

       schedule_debug(prev);

       hrtick_clear(rq);

       /*

        * Do the rq-clock update outside the rq lock:

        */

       local_irq_disable();

       update_rq_clock(rq);

       spin_lock(&rq->lock);

       clear_tsk_need_resched(prev);

检查prev(将被替换掉的进程)是否存有还未被处理的信号,若有,则保持prev的TASK_RUNNING状态,否则就使其无效(deactivate)

       if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

              if (unlikely(signal_pending_state(prev->state, prev)))

                     prev->state = TASK_RUNNING;

              else

                     deactivate_task(rq, prev, 1);

              switch_count = &prev->nvcsw;

       }

#ifdef CONFIG_SMP

       if (prev->sched_class->pre_schedule)

              prev->sched_class->pre_schedule(rq, prev);

#endif

       if (unlikely(!rq->nr_running))

              idle_balance(cpu, rq);

//将当前进程插入运行队列中(rq)

       prev->sched_class->put_prev_task(rq, prev);

//从运行队列中获取将要被调度的进程

       next = pick_next_task(rq, prev);

       if (likely(prev != next)) {
//开始切换

              sched_info_switch(prev, next);

//切换计数+1,当前正在执行进程curr指向next

              rq->nr_switches++;

              rq->curr = next;

              ++*switch_count;

//上下文切换,重点关注!

              context_switch(rq, prev, next); /* unlocks the rq */

              /*

* the context switch might have flipped the stack from under

               * us, hence refresh the local variables.

               */

              cpu = smp_processor_id();

              rq = cpu_rq(cpu);

       } else

              spin_unlock_irq(&rq->lock);

       hrtick_set(rq);

       if (unlikely(reacquire_kernel_lock(current) < 0))

              goto need_resched_nonpreemptible;

//重新打开内核抢占式机制

       preempt_enable_no_resched();

       if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))

              goto need_resched;

}

二.context_switch()函数分析

context_switch()函数实际上是个预处理宏

#define switch_to(prev, next, last)                             \

do {                                                         \

       /*                                                     \

        * Context-switching clobbers all registers, so we clobber  \

        * them explicitly, via unused output variables.          \

        * (EAX and EBP is not listed because EBP is saved/restored    \

        * explicitly for wchan access and EAX is the return value of    \

        * __switch_to())                                      \

        */                                                    \

       unsigned long ebx, ecx, edx, esi, edi;                         \

                                                               \

以下为C语言的内联汇编代码,也是关键上下文切换的核心所在

       asm volatile("pushfl\n\t"              /* save    flags */ \

                   "pushl %%ebp\n\t"             /* save    EBP   */    \

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

                   "movl %[next_sp],%%esp\n\t"    /* restore ESP   */ \

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

                   "pushl %[next_ip]\n\t" /* restore EIP   */ \

                   "jmp __switch_to\n"    /* regparm call  */       \

                   "1:\t"                                        \

"popl %%ebp\n\t" /* restore EBP */ \

                   "popfl\n"                    /* restore flags */   \

                                                               \

                   /* output parameters */                      \

                   : [prev_sp] "=m" (prev->thread.sp),          \

                     [prev_ip] "=m" (prev->thread.ip),         \

                     "=a" (last),                                   \

                                                               \

                     /* clobbered output registers: */             \

                     "=b" (ebx), "=c" (ecx), "=d" (edx),        \

                     "=S" (esi), "=D" (edi)                          \

                                                                 \

                     /* input parameters: */                          \

                   : [next_sp]  "m" (next->thread.sp),          \

                     [next_ip]  "m" (next->thread.ip),         \

                                                                 \

                     /* regparm parameters for __switch_to(): */   \

                     [prev]     "a" (prev),                         \

                     [next]     "d" (next));                        \

} while (0)

1.内联汇编参数分析

这里的C语言内联汇编代码比较晦涩,先简要分析一下参数,稍候我们将其还原为汇编代码来分析。

/* output parameters */                       \

: [prev_sp] "=m" (prev->thread.sp),            \

[prev_ip] "=m" (prev->thread.ip),        \

"=a" (last),                                 \

这里有三个输出参数:prev_sp、prev_ip、和加载代码“a”(代表EAX寄存器),分别用%0,%1和%2来表示,代表这段汇编代码执行完毕后被改动的变量,分别对应于prev->thread.sp,prev->thread.ip和last,后面还有其他被改动的寄存器,不过不是重点。

/* input parameters: */                        \

: [next_sp]  "m" (next->thread.sp),            \

[next_ip]  "m" (next->thread.ip),        \

/* regparm parameters for __switch_to(): */ \

[prev]     "a" (prev),                       \

[next]     "d" (next));                      \

和上面的分析类似,前两个参数代表将被调度运行的进程的堆栈和指令指针寄存器,然后是将被调度出去的进程和调度运行的进程prev,next,分别读入eax和edx寄存器中

2.汇编代码分析

       我们将上面的汇编代码改写一下:

1)        pushfl                                         /* save    flags */    

2)        pushl      %ebp                          /* save    EBP   */ 

3)        movl      %esp, %prev->thread.sp           /* save    ESP   */

4)        movl      %next->thread.sp, %esp           /* restore ESP   */

5)        movl      $1f,  %prev->thread.ip            /* save    EIP   */

6)        pushl      %next->thread.ip                 /* restore EIP   */   

7)        jmp __switch_to                                 /* regparm call  */     

8)        1:                                     

9)        popl       %ebp                            /* restore EBP   */ 

10)     popfl

这段代码虽然小,但是暗藏玄机,进程切换的精髓就在其中,第1,2行将标志寄存器和ebp寄存器压入堆栈,第3行将当前进程(prev)的系统空间堆栈指针存入prev->thread.sp。第四行就是进程切换的关键,在这一行中next进程的系统空间堆栈指针被拷贝给了esp寄存器,那么,从这一行开始就是在使用新调度进来的next进程的堆栈了,也就是说此时“当前进程”这个概念就是指next,而不是prev。随后,第5行将标号为1(f代表forward),也就是第9行代码的地址存入prev->thread.ip,作为prev进程下一次被调度而切入运行的“返回地址”;第6行将next->thread.ip的地址压入堆栈中,它指向的就是上一次next进程切出时在第5行存入的返回地址。第7行跳转到__switch_to函数(它主要处理TSS,继续切换其余存储在TSS中的上下文信息,如fs和gs等寄存器其中关键是将内核空间堆栈指针恢复成next->esp0),__switch_to函数ret指令返回后,刚才第6行压入堆栈的“返回地址”信息就被弹到eip寄存器中,于是next进程执行第9和10行,恢复ebp和标志寄存器,next进程也就正式开始执行了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 2.6内核标准教程》 是一本介绍Linux 2.6内核的教程以及相关知识的书籍。Linux 2.6是运行在计算机硬件上的操作系统内核,其版本是Linux内核发展的重要里程碑。 该教程详细介绍了Linux 2.6内核的架构、设计理念、功能模块和配置方法。它从内核的基本概念开始,解释了进程管理、内存管理、文件系统、设备驱动、网络协议栈等关键组件的工作原理和实现方法。读者通过学习该教程,可以获得对Linux 2.6内核的深入理解,为进一步深入学习和开发Linux系统打下坚实基础。 该教程的特点是全面、系统、简明易懂。作者通过通俗易懂的语言、图示和示例,将复杂的内核原理和实现细节讲解得浅显易懂,使读者在学习过程中能够更好地理解和掌握这些概念。此外,该教程还提供了大量的练习题和案例分析,帮助读者通过实践进一步巩固所学知识。 阅读该教程的收益是多方面的。首先,对于计算机科学相关专业的学生来说,了解和熟悉Linux内核是必要的。其次,该教程为想要从事Linux系统开发、系统管理等工作的人员提供了重要的参考资料。最后,对于对Linux内核感兴趣的爱好者来说,该教程提供了一个系统的学习路径,帮助他们深入了解Linux内核的原理和实现。 总而言之,《Linux 2.6内核标准教程》是一本权威且实用的教材,适合广大读者学习Linux内核。无论是想要深入学习Linux内核的学生、从事相关工作的专业人士还是对Linux内核感兴趣的个人,阅读该教程都能获得有益的知识和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值