进程调度跟踪分析

此文仅用于MOOCLinux内核分析作业

张依依+原创作品转载请注明出处 + 《Linux内核分析》**
MOOC课程**http://mooc.study.163.com/course/USTC-1000029000


进程调度

Linux的调度程序是一个叫schedule()的函数,这个函数被调用的频率很高,由它来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等等。

Linux调度时机主要有:

  1. 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule()
  2. 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
  3. 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

代码分析

关键函数的调用关系:

schedule() --> context_switch() --> switch_to --> __switch_to()
  • schedule()

这里调用__schedule(),tsk为当前进程.

asmlinkage __visible void __sched schedule(void)
{
    struct task_struct *tsk = current;

    sched_submit_work(tsk);
    __schedule();
}
  • __schedule();

该函数包含了一些:

  1. 针对抢占的处理
  2. 自旋锁(raw_spin_lock_irq(&rq->lock);)
  3. 检查prev的状态,并且重设state的状态
  4. 进程调度算法(next = pick_next_task(rq, prev);)
  5. 更新就绪队列的时钟
  6. 进程上下文切换(context_switch(rq, prev, next);)
static void __sched __schedule(void)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;
    int cpu;

...
//调度算法
    next = pick_next_task(rq, prev);
    clear_tsk_need_resched(prev);
    clear_preempt_need_resched();
    rq->skip_clock_update = 0;

    if (likely(prev != next)) {
        rq->nr_switches++;
        rq->curr = next;
        ++*switch_count;

//进程上下文切换
        context_switch(rq, prev, next);
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
    } else
        raw_spin_unlock_irq(&rq->lock);

    post_schedule(rq);

    sched_preempt_enable_no_resched();
    if (need_resched())
        goto need_resched;
}

  • context_switch

在挑选得到了下一个即将被调度进来的进程之后,如果被选中的进程不是当前正在运行的进程,那么需要进行上下文切换以执行被选中的进程即context_switch.

context_switch中包含了:

  1. 判断是否为内核线程,即是否需要上下文切换(mm)
    • 如果next是一个普通进程,schedule( )函数用next的地址空间替换prev的地址空间
    • 如果prev是内核线程或正在退出的进程,context_switch()函数就把指向prev内存描述符的指针保存到运行队列的prev_mm字段中,然后重新设置prev->active_mm
  2. 切换堆栈和寄存器(switch_to(prev, next, prev);)

ps:宏switch_to用来进行关键上下文切换

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;

    arch_start_context_switch(prev);

    if (!mm) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
    } else
        switch_mm(oldmm, mm, next);

    if (!prev->mm) {
        prev->active_mm = NULL;
        rq->prev_mm = oldmm;
    }

    spin_release(&rq->lock.dep_map, 1, _THIS_IP_);

    context_tracking_task_switch(prev, next);
    /* Here we just switch the register state and the stack. */
    switch_to(prev, next, prev);

    barrier();

    finish_task_switch(this_rq(), prev);
}

  • 宏switch_to

#define switch_to(prev, next, last)
do {

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

    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   */
             __switch_canary
             "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)

               __switch_canary_oparam

               /* 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)

               __switch_canary_iparam

             : /* reloaded segment registers */
            "memory");
} while (0)

这个宏实现了进程之间的真正切换:

  • 首先在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。
  • 然后将prev的内核堆栈指针ebp存入prev->thread.esp中。
  • 把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中
  • 将popl指令所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度
  • 通过jmp指令(而不是call指令)转入一个函数__switch_to()
  • 恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行。

内核堆栈情况:

stack1.png
stack2.png
stack3.png

  • __switch_to函数

在宏switch_to中,用jmp跳转到该函数运行.

该函数主要进行一些针对TSS的操作,不再赘述

__visible __notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
    struct thread_struct *prev = &prev_p->thread,
                 *next = &next_p->thread;
    int cpu = smp_processor_id();
    struct tss_struct *tss = &per_cpu(init_tss, cpu);
    fpu_switch_t fpu;


    fpu = switch_fpu_prepare(prev_p, next_p, cpu);


    load_sp0(tss, next);


    lazy_save_gs(prev->gs);


    load_TLS(next, cpu);


    if (get_kernel_rpl() && unlikely(prev->iopl != next->iopl))
        set_iopl_mask(next->iopl);


    task_thread_info(prev_p)->saved_preempt_count = this_cpu_read(__preempt_count);
    this_cpu_write(__preempt_count, task_thread_info(next_p)->saved_preempt_count);


    if (unlikely(task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV ||
             task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT))
        __switch_to_xtra(prev_p, next_p, tss);


    arch_end_context_switch(next_p);

    this_cpu_write(kernel_stack,
          (unsigned long)task_stack_page(next_p) +
          THREAD_SIZE - KERNEL_STACK_OFFSET);


    if (prev->gs | next->gs)
        lazy_load_gs(next->gs);

    switch_fpu_finish(next_p, fpu);

    this_cpu_write(current_task, next_p);

    return prev_p;
}

GDB调试

使用MenuOS进行调试,并设置合适的断点.

  • 首先在schedule处停下来:
process1.png
  • 查看当前进程tsk,观察到该进程pid=1,stack=0xC7858000
process2.png
  • 继续执行,到__schedule中的关键函数pick_next_task停下
process3.png
  • 查看队列rq
process4.png
  • context_switch
process5.png
  • switch_to宏&__switch_to函数
process6.png
  • 在这里查看切换的进程prev&next,prev就是最开始tsk
process7.png
process8.png

总结

  1. Linux的调度程序是一个叫schedule()的函数,这个函数被调用的频率很高,由它来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等等。
  2. Linux系统的一般执行过程主要在进程X切换到进程Y
    • 正在运行的用户态进程X
    • 发生中断
    • SAVE_ALL
    • 中断处理过程中或中断返回前调用了schedule()
    • 开始运行用户态进程Y
    • restore_all
    • iret
    • 继续运行用户态进程Y
  3. 内核线程主动调用schedule(),只有进程上下文的切换
  4. switch_to实现了进程之间的真正切换

参考

  1. 进程管理之schedule->context_switch()
  2. 深入分析Linux内核源码
  3. Context switch in Linux
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容