进程的调度
无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。
调度发生的时机:
通过系统调用资源让出运行;
系统调用中因某种原因受阻;
因某种原因唤醒一个进程;
时钟中断服务程序发现当前进程运行太久;调度策略
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 //由上面的源码可知,这是一个宏,无法打上断点
显示结果如下:
总结
Linux进程调度执行顺序为:
schedule–>_schedule–>pick_next_task,context_switch–>switch_toLinux系统的一般执行过程主要在进程X切换到进程Y
- 正在运行的用户态进程X
- 发生中断
- SAVE_ALL
- 中断处理过程中或中断返回前调用了schedule()
- 开始运行用户态进程Y
- restore_all
- iret
- 继续运行用户态进程Y
内核线程主动调用schedule(),只有进程上下文的切换,宏switch_to实现了进程之间的真正切换