操作系统学习-4.Linux进程调度学习2-调度器源码分析


代码分析根据3.10版本

  • 通过对前面的学习我们知道Linux的调度分为两种

  • 周期调度 完成周期性算法参数的更新和系统其它实际的检查

主调的 真正的调度过程

我们现在来看下主调的的代码框架。

入口

根《调度发生的情况》的学习,我们知道所有的调度最后都会到 schedule 函数中。因此我们就先从schedule函数入手。

 asmlinkage void __sched schedule(void)
{
      struct task_struct *tsk = current;
     
      sched_submit_work(tsk);//获取当前current 并且加锁。
      __schedule();
    }

schedule 函数并没有太多的操作,只是获取当前task,然后加锁,最后都交给了**__schedule()**函数。

核心函数分析

主要函数__schedule分析

 static void __sched __schedule(void)
    {
      struct task_struct *prev, *next;
      unsigned long *switch_count;
      struct rq *rq;
      int cpu;
     
    need_resched:
      preempt_disable();
      cpu = smp_processor_id();
      rq = cpu_rq(cpu);//找到当前cpu拿取队列
     
      prev = rq->curr;//保持此时此刻的任务
    .....
      pre_schedule(rq, prev);//做一些预处理
     
    ....
     
      put_prev_task(rq, prev);
      next = pick_next_task(rq);//选取下一个进程
      clear_tsk_need_resched(prev);
      rq->skip_clock_update = 0;
     
    if (likely(prev != next)) {//选好了,
        rq->nr_switches++;
        rq->curr = next;
        ++*switch_count;//计数加一
    ...
        context_switch(rq, prev, next); /* unlocks the rq */真正的切换堆栈帧
    ...
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
      } else
        raw_spin_unlock_irq(&rq->lock);
        
    ....
      if (need_resched())//是否需要重新调度
        goto need_resched;
    }

其实 __schedule还是比较长的,但是我们这里选择了一些主要的流程分析,流程如下:

  • 拿到当前cpu的队列rq

  • 进行一些与操作处理

  • 选取下一个进程

  • 堆栈帧context_switch 切换

  • 是否需要重新调度

关于重新调度,设计到一些内核抢占的知识,我们暂时不分析。而context_switch,是硬件堆栈的切换,我们后文会认真分析这个函数,在这个过程中我们主要关注如何选取下一个进程。

pick_next_task 函数分析

如何选取下一个任务 pick_next_task 函数,

static inline struct task_struct * pick_next_task(struct rq *rq)
    {
      const struct sched_class *class;
      struct task_struct *p;
     
      /*
       * Optimization: we know that if all tasks are in
       * the fair class we can call that function directly:
       */
      if (likely(rq->nr_running == rq->cfs.h_nr_running)) {检查当前cpu队列中是否所有的都是cfs,如果是就直接掉用cfs的调度类
        p = fair_sched_class.pick_next_task(rq);
        if (likely(p))
          return p;
      }
     
      for_each_class(class) {循环遍历每一个调度类
        p = class->pick_next_task(rq);
        if (p)
          return p;
      }
    }

pick_next_task 还是比较简单的只是有两部,

1、查看当前cpu队列是否所有的都是cfs

2、遍历所有调度类。 对于是不是全部cfs的检查是有必要的,会减少开销。我们再来看下遍历

#define sched_class_highest (&stop_sched_class)
    #define for_each_class(class) \
       for (class = sched_class_highest; class; class = class->next)
     
    extern const struct sched_class stop_sched_class;
    extern const struct sched_class rt_sched_class;
    extern const struct sched_class fair_sched_class;
    extern const struct sched_class idle_sched_class;

我们看到是从stop_sched_class开始,

const struct sched_class stop_sched_class = {
      .next      = &rt_sched_class,
    ....  
    }
     
    const struct sched_class rt_sched_class = {
      .next      = &fair_sched_class,
    ....
    }
    const struct sched_class fair_sched_class = {
      .next      = &idle_sched_class,
    ....
    }
    const struct sched_class idle_sched_class = {
      /* .next is NULL */
    ....
    }

可以看到是这么个列表。

stop_sched_class --》rt_sched_class–》fair_sched_class–》idle_sched_class–》NULL

以cfs为例看看如何选取task

static struct task_struct *pick_next_task_fair(struct rq *rq)
{
      struct task_struct *p;
      struct cfs_rq *cfs_rq = &rq->cfs;
      struct sched_entity *se;
     
      if (!cfs_rq->nr_running)
        return NULL;
     
      do {
        se = pick_next_entity(cfs_rq);
        set_next_entity(cfs_rq, se);
        cfs_rq = group_cfs_rq(se);
      } while (cfs_rq);
     
      p = task_of(se);
      if (hrtick_enabled(rq))
        hrtick_start_fair(rq, p);
     
      return p;
    }

这部分代码也是组调度流程的基础,现在看起来是比较简单的,但是接下来分析组调度的时候就会详细的展开分析。

我们看这个简单的过程:

  • 全局的rq 拿到cfs_rq

  • cfs_rq 通过pick_next_entity 拿到算法算好的调度类entity----se。

  • 进行组调度----目前先忽略

  • 将se 转换成task 这样就结束了主调的的所有过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
分析Linux内核的进程调度源码,首先需要了解Linux内核的源代码结构和相关概念。进程调度操作系统的核心功能之一,负责决定何时以及如何将CPU分配给不同的进程。 在Linux内核中,进程调度的相关源码位于kernel/sched目录下。该目录包含了与进程调度相关的各个文件和子目录,其中最重要的文件是sched.h和sched.c。 sched.h定义了与进程调度相关的数据结构、宏定义和函数原型。你可以在该文件中找到与调度算法相关的定义,如进程状态、调度类别、优先级等。 sched.c是进程调度的核心代码文件,其中实现了各种调度算法。在该文件中,你可以找到与进程调度相关的函数实现,如schedule()函数,这是Linux内核的调度函数,用于选择下一个要运行的进程。 除了sched.h和sched.c,还有一些其他的文件和子目录也与进程调度相关,如fair.c、rt.c和core.c等。fair.c中实现了CFS(完全公平调度)算法,rt.c实现了实时进程调度算法,core.c包含了通用的调度函数。 要深入理解Linux内核的进程调度源码,你可以通过阅读相关文件和函数的注释来获取更多信息。此外,还可以参考一些相关的内核文档和书籍,如《Linux内核源代码情景分析》和《深入理解Linux内核》等。 请注意,分析内核源码需要有一定的操作系统和编程经验,并且需要花费相当的时间和精力。建议在开始分析之前,先熟悉相关的概念和基本的内核知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沈万三djh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值