理解进程调度时机跟踪分析进程调度与进程切换的过程

进程的调度

无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。

  • 调度发生的时机:
    通过系统调用资源让出运行;
    系统调用中因某种原因受阻;
    因某种原因唤醒一个进程;
    时钟中断服务程序发现当前进程运行太久;

  • 调度策略
    SCHED_FIFO:不使用时间片,进程处于可执行状态就一直执行,直到自己受到阻塞或显示的释放处理器为止。只有更高优先级的 SCHED_FIFO或者 SCHED_RR 任务才能抢占 SCHED_FIFO 任务。相同优先级的进程,会轮流执行,依然只有在愿意让出处理器时才退出。
    SCHED_RR: SCHED_RR 是带有时间片的 SCHED_FIFO,进程在消耗完实现非配给它的时间片之后就不能再继续执行。时间片只是用来重新调度同一优先级的进程。低优先级的进程决不能抢占 SCHED_RR 任务,即使它的时间片耗尽。
    SCHED_NORMAL: 传统的调度策略

进程的切换

如果新进程没有自己的 mm_struct(内核线程),就要在进入运行时向被切换出去的进程借用一个 mm_struct结构,进行系统空间的映射,因为 所有进程的系统空间映射都是相同的 。
如果一个新进程有自己的用户空间,就通过 switch_mm 进行用户空间的切换
linux进程切换源码:
schedule()

2860 asmlinkage __visible void __sched schedule(void)
2861 {
2862         struct task_struct *tsk = current;
2863 
2864         sched_submit_work(tsk);
2865         __schedule();//schedule()调用了这个函数
2866 }
2867 EXPORT_SYMBOL(schedule);

–schedule()

2765 static void __sched __schedule(void)
2766 {
2767         struct task_struct *prev, *next;
2768         unsigned long *switch_count;
2769         struct rq *rq;
.....
2819         next = pick_next_task(rq, prev);//调度策略函数
.....
2824         if (likely(prev != next)) {
2825                 rq->nr_switches++;
2826                 rq->curr = next;
2827                 ++*switch_count;
2828 
2829                 context_switch(rq, prev, next); //进程上下文切换
2830                 /*
2831                  * The context switch have flipped the stack from under us
2832                  * and restored the local variables which were saved when
2833                  * this task called schedule() in the past. prev == current
2834                  * is still correct, but it can be moved to another cpu/rq.
2835                  */
2836                 cpu = smp_processor_id();
2837                 rq = cpu_rq(cpu);
2838         } else
2839                 raw_spin_unlock_irq(&rq->lock);
2840 
2841         post_schedule(rq);
....
2846 }

context_switch

2331 context_switch(struct rq *rq, struct task_struct *prev,
2332                struct task_struct *next)
2333 {
2334         struct mm_struct *mm, *oldmm;
2335 
2336         prepare_task_switch(rq, prev, next);
2337 
2338         mm = next->mm;
2339         oldmm = prev->active_mm;
2345         arch_start_context_switch(prev);
2346 //下面判断该进程是否是内核进程
2347         if (!mm) {
2348                 next->active_mm = oldmm;
2349                 atomic_inc(&oldmm->mm_count);
2350                 enter_lazy_tlb(oldmm, next);
2351         } else
2352                 switch_mm(oldmm, mm, next);
2353 
2354         if (!prev->mm) {
2355                 prev->active_mm = NULL;
2356                 rq->prev_mm = oldmm;
2357         }
....
2368         switch_to(prev, next, prev);//进程的切换
....
2376         finish_task_switch(this_rq(), prev);
2377 }

switch_to

31 #define switch_to(prev, next, last)
32 do { 
 40         unsigned long ebx, ecx, edx, esi, edi;                          \
 41                                                                         \
 42         asm volatile("pushfl\n\t"               /* save    flags */     \
 43                      "pushl %%ebp\n\t"          /* save    EBP   */     \
 44                      "movl %%esp,%[prev_sp]\n\t"        /* save    ESP   */ \
 45                      "movl %[next_sp],%%esp\n\t"        /* restore ESP   */ \
 46                      "movl $1f,%[prev_ip]\n\t"  /* save    EIP   */     \
 47                      "pushl %[next_ip]\n\t"     /* restore EIP   */     \
 48                      __switch_canary                                    \
 49                      "jmp __switch_to\n"        /* regparm call  */     \
 50                      "1:\t"                                             \
 51                      "popl %%ebp\n\t"           /* restore EBP   */     \
 52                      "popfl\n"                  /* 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)
使用gdb追踪

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
打开另一个shell窗口,依次输入
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
然后依次打上断点
break schedule
pick_next_task
context_switch
switch_to //由上面的源码可知,这是一个宏,无法打上断点
显示结果如下:
break
break2

总结
  1. Linux进程调度执行顺序为:
    schedule–>_schedule–>pick_next_task,context_switch–>switch_to

  2. Linux系统的一般执行过程主要在进程X切换到进程Y

    • 正在运行的用户态进程X
    • 发生中断
    • SAVE_ALL
    • 中断处理过程中或中断返回前调用了schedule()
    • 开始运行用户态进程Y
    • restore_all
    • iret
    • 继续运行用户态进程Y
  3. 内核线程主动调用schedule(),只有进程上下文的切换,宏switch_to实现了进程之间的真正切换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值