Chinaunix首页 | 论坛 | 博客
  • 博客访问: 10787
  • 博文数量: 6
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-28 15:46
文章分类
文章存档

2015年(6)

我的朋友

分类: LINUX

2015-04-25 14:02:06

前言:这个星期的内容有点散,课程的主线是进程的切换,首先讲了不同进程有不同的调度需求,然后讲到了现在Linux的切换机制,最后剖析源代码,分析其机制。

署名部分:
作者:彭家进
原创作品转载请注明出处 
《Linux内核分析》MOOC课程 ”

1,进程如何分类。
第一种分类方法:(主要是对进程的需求进行分类)


第二种分类方法:(根据进程的交互性进行分类)



2,进程调度的策略
由于不同的进程的资源需求不一样,如果不加以区分地进行调度的话,性能将会下降。所以Linux有一个进程调度策略。

策略的基本思路如下:(使用类似于权重的方法区分各进程)



至于算法是如何实现的,老师没有详细介绍,留给了我们一个术语:OOD:


3,进程调度的机制。
负责进程调度的函数是schedule(),这个进程的工作为:在运行队列中找到一个进程,然后把CPU分配给它。由于schedule()是一个没有系统调用号的内核函数,所以用户态进程不能主动调用schedule()。而内核因为拥有ring 0的权限,所以可以直接调用schedule()进行进程切换,也可以在终端进行调度。

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

4,代码分析。
1,首先是schedule()函数


2,进入了__schedule()函数后,程序首先会在pick_next_task()中通过OOD的策略选出下一个要运行的程序。


3,然后到了内容的切换,context_switch(),这里传入了prev和next的指针。代码中,使用汇编将当前进程的程序地址进行保存,跳转到__switch_to,执行新的程序。这部分是前后两个进程的切换点,进程的界限比较模糊。


5,代码分析完成了,我们抽象出正常的用户进程X到用户进程Y的过程:(摘自课件富文本)

1,正在运行的用户态进程X
2,发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
3,SAVE_ALL //保存现场
4,中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
5,标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
6,restore_all //恢复现场
7,iret - pop cs:eip/ss:esp/eflags from kernel stack
8,继续运行用户态进程Y

PS:我们一旦抽象过的东西,都不一定是准确的,还有几种特殊情况:
1,通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
2,内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
3,创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
加载一个新的可执行程序后返回到用户态的情况,如execve;

6,内核与用户进程的关系,就是Taxing Girl和嫖客的关系,当用户进程有需求时,内核发生中断,帮助用户进程完成请求,然后再返回到用户进程。就好像Taxi将用户载了一圈之后又把用户放下来的感觉。


7,实验部分:
1,开启QEMU以及GDB:



2,设置断点:


3,由于schedule()的调用十分频繁,所以就不用刻意找条件触发了,按c继续,在core.c里面调用了schedule()。


4,list查看上下代码:


5,源代码的注释如下,大概是一个关闭抢占进程的函数。注释如下:

点击(此处)折叠或打开

  1. /**
  2.  * schedule_preempt_disabled - called with preemption disabled
  3.  *
  4.  * Returns with preemption disabled. Note: preempt_count must be 1
  5.  */

6,step into到了__schedule()


8,一路next过来,到了pick_nexi_task(),PS:在GDB中输入n XX可以执行XX行代码。


9,再继续next,来到context_switch


10,这里出现了一个问题,按s是进入不了context_switch函数的,很奇怪,于是我设置了另外一个断点,就是prepare_task_switch,这个函数在context_switch内部。

11,但是我没有跟踪到switch_to的函数,可能是因为其是ASM写的关系,然而switch_to会调用__switch_to,这个倒是跟踪到了。__switch_to位于linux-3.18.6\arch\x86\kernel\process_32.c,代码如下:

点击(此处)折叠或打开

  1. __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
  2. {
  3.  struct thread_struct *prev = &prev_p->thread,
  4.      *next = &next_p->thread;
  5.  int cpu = smp_processor_id();
  6.  struct tss_struct *tss = &per_cpu(init_tss, cpu);
  7.  fpu_switch_t fpu;

  8.  /* never put a printk in __switch_to... printk() calls wake_up*() indirectly */

  9.  fpu = switch_fpu_prepare(prev_p, next_p, cpu);

  10.  /*
  11.   * Reload esp0.
  12.   */
  13.  load_sp0(tss, next);

  14.  /*
  15.   * Save away %gs. No need to save %fs, as it was saved on the
  16.   * stack on entry. No need to save %es and %ds, as those are
  17.   * always kernel segments while inside the kernel. Doing this
  18.   * before setting the new TLS descriptors avoids the situation
  19.   * where we temporarily have non-reloadable segments in %fs
  20.   * and %gs. This could be an issue if the NMI handler ever
  21.   * used %fs or %gs (it does not today), or if the kernel is
  22.   * running inside of a hypervisor layer.
  23.   */
  24.  lazy_save_gs(prev->gs);

  25.  /*
  26.   * Load the per-thread Thread-Local Storage descriptor.
  27.   */
  28.  load_TLS(next, cpu);

  29.  /*
  30.   * Restore IOPL if needed. In normal use, the flags restore
  31.   * in the switch assembly will handle this. But if the kernel
  32.   * is running virtualized at a non-zero CPL, the popf will
  33.   * not restore flags, so it must be done in a separate step.
  34.   */
  35.  if (get_kernel_rpl() && unlikely(prev->iopl != next->iopl))
  36.   set_iopl_mask(next->iopl);

  37.  /*
  38.   * If it were not for PREEMPT_ACTIVE we could guarantee that the
  39.   * preempt_count of all tasks was equal here and this would not be
  40.   * needed.
  41.   */
  42.  task_thread_info(prev_p)->saved_preempt_count = this_cpu_read(__preempt_count);
  43.  this_cpu_write(__preempt_count, task_thread_info(next_p)->saved_preempt_count);

  44.  /*
  45.   * Now maybe handle debug registers and/or IO bitmaps
  46.   */
  47.  if (unlikely(task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV ||
  48.        task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT))
  49.   __switch_to_xtra(prev_p, next_p, tss);

  50.  /*
  51.   * Leave lazy mode, flushing any hypercalls made here.
  52.   * This must be done before restoring TLS segments so
  53.   * the GDT and LDT are properly updated, and must be
  54.   * done before math_state_restore, so the TS bit is up
  55.   * to date.
  56.   */
  57.  arch_end_context_switch(next_p);

  58.  this_cpu_write(kernel_stack,
  59.     (unsigned long)task_stack_page(next_p) +
  60.     THREAD_SIZE - KERNEL_STACK_OFFSET);

  61.  /*
  62.   * Restore %gs if needed (which is common)
  63.   */
  64.  if (prev->gs | next->gs)
  65.   lazy_load_gs(next->gs);

  66.  switch_fpu_finish(next_p, fpu);

  67.  this_cpu_write(current_task, next_p);

  68.  return prev_p;
  69. }

12,本次实验到此结束。

8,在进程调度的后面,老师还进行了操作系统的一个概览。

操作系统的基本概念:


Linux的架构,其中每一个部分都可以复杂到开一门课:


执行ls命令看起来简单,实际上其从操作系统的层面上来说,这是一个极其复杂的过程:


物理内存与虚拟空间是不一样的,一般会有一个映射关系:


好了,本次作业到此结束。
    本课程的作业也到此结束了。
    感谢孟宁老师的辛勤付出。
    感谢各位同学的慷慨帮助。


阅读(594) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~