实验作业:进程调度时机跟踪分析进程调度与进程切换的过程

20135313吴子怡.北京电子科技学院

【第一部分】理解Linux系统中进程调度的时机

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

2.Linux调度时机主要有:

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

【第二部分】使用gdb跟踪分析一个schedule()函数 ,验证对Linux系统进程调度与进程切换过程的理解

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

①首先在schedule处停下来:

②查看当前进程tsk,观察到该进程pid=1,stack=0xC7858000

③继续执行,到__schedule中的关键函数pick_next_task停下

④查看队列rq

⑤context_switch

⑥switch_to宏&__switch_to函数

⑦在这里查看切换的进程prev&next,prev就是最开始tsk

【第三部分】分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系

1.关键函数的调用关系:

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()

该函数包含了一些:

针对抢占的处理
自旋锁(raw_spin_lock_irq(&rq->lock);)
检查prev的状态,并且重设state的状态
进程调度算法(next = pick_next_task(rq, prev);)
更新就绪队列的时钟
进程上下文切换(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中包含了:

①判断是否为内核线程,即是否需要上下文切换

如果next是一个普通进程,schedule( )函数用next的地址空间替换prev的地址空间
如果prev是内核线程或正在退出的进程,context_switch()函数就把指向prev内存描述符的指针保存到运行队列的prev_mm字段中,然后重新设置prev->active_mm

②切换堆栈和寄存器(switch_to(prev, next, prev);)

注意:宏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进程就成为当前进程而真正开始执行。
  • __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;
}

【第四部分】堆栈状态、CPU寄存器状态分析

1.内核堆栈情况:

 

【第五部分】总结

对“Linux系统一般执行过程”的理解

1.在调度时机方面,内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度。

2.schedule()函数实现进程调度,context_ switch完成进程上下文切换,switch_ to完成寄存器的切换。

3.用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

【第六部分】附录

作者:吴子怡

学号:20135313

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

Linux内核分析——第八周学习笔记的更多相关文章

  1. Linux 内核分析第八周学习笔记

    Linux 内核分析第八周学习笔记 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...

  2. Linux内核分析——第八周学习笔记20135308

    第八周 进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 1.进程调度与进程调度的时机分析 (1)进程分类 第一种分类 I/O-bound:等待I/O CPU-bound: ...

  3. LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

    LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

  4. LINUX内核分析第八周学习总结

    LINUX内核分析第八周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

  5. 《Linux内核分析》第一周学习笔记

    <Linux内核分析>第一周学习笔记 计算机是如何工作的 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

  6. 《Linux内核分析》第二周学习笔记

    <Linux内核分析>第二周学习笔记 操作系统是如何工作的 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/ ...

  7. linux内核分析第五周学习笔记

    linux内核分析第五周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  8. Linux内核分析第三周学习笔记

    linux内核分析第三周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  9. Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程

    Linux内核分析第六周学习笔记--分析Linux内核创建一个新进程的过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

随机推荐

  1. 团队作业——Alpha冲刺 6/12

    团队作业--Alpha冲刺 冲刺任务安排 杨光海天 今日任务:编辑界面完成标题栏的开发,以及与已经完成gallery开发的同学,商讨我负责的界面中,图片滑动的具体措施. 明日任务:除了图像识别内容嵌入 ...

  2. 第二次项目冲刺(Beta版本)2017/12/8

    一.任务分布 二.燃尽图 三.站立式会议 1.照片(就要传了) 2.任务安排 四.总结 这次吸收了上次的教训,将任务进行更加详细的分布,分工更加明确,果然效率就高多了,哈哈哈.

  3. 2879: [Noi2012]美食节

    Description CZ市为了欢迎全国各地的同学,特地举办了一场盛大的美食节.作为一个喜欢尝鲜的美食客,小M自然不愿意错过这场盛宴.他很快就尝遍了美食节所有的美食.然而,尝鲜的欲望是难以满足的.尽 ...

  4. visual studio 插件

    一:VS10x Code Map(打开后台cs文件,插件列出了所有的字段.方法,方便当代码行多的时候查找,提升工作效率) 1:下载 https://marketplace.visualstudio.c ...

  5. zabbix(2-server-agent)

    注意:以下步骤都是在LAMP配置之后进行的. 关于LAMP环境的简单快速搭建,见博客:http://afterdawn.blog.51cto.com/7503144/1923139 下面开始介绍在Ce ...

  6. C51中的关键字和ANSIC标准关键字

    C51中的关键字和ANSIC标准关键字 作       者:武力戡乱 修改日期:2017-09-05 备       注: 1.总备注信息 2.联系方式 3.其它博文链接:武力戡乱博客目录总表 内   ...

  7. html样式表格

    <html><body><table border="1">  <tr height="20px">    &l ...

  8. ORACLE SEQUENCE 具体解释

    1.    About Sequences(关于序列) 序列是数据库对象一种. 多个用户能够通过序列生成连续的数字以此来实现主键字段的自己主动.唯一增长,而且一个序列可为多列.多表同一时候使用. 序列 ...

  9. IDEA插件——lombok的简单使用

    一.介绍 lombok是一个可以通过注解来简化许多繁琐的get.set以及构造函数等的工具.它可以让我们在代码编写的时候省去代码,而在编译生成的字节码中生成相应的字节码! 官网:https://pro ...

  10. Latex数学公式编写

    小叙闲言 一直想用latex来编辑文档,但是没有需求,所以也没有去学习一下,但是最近由于要大量敲数学公式,有了latex数学公式的需求,所以来稍稍总结学习一下 1.在MathType中编写Latex数 ...