您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > linux百科

Linux 内核调度器源码解析:从调度入口到挑选下一个进程

时间:2023-11-27 15:09:09  来源:微信公众号  作者:囧囧妹

linux内核中,调度器(scheduler)扮演着至关重要的角色,决定了哪个进程将获得CPU的执行时间。本文将深入剖析内核中调度器的代码实现,从入口函数开始,一步步分析如何选择下一个要执行的进程。让我们一同揭开这个内核之谜。

Linux 内核调度器源码解析:从调度入口到挑选下一个进程

调度器入口

Linux调度器入口函数定义在kernel/sched/core.c中:

asmlinkage __visible void __sched schedule(void)
{
    // 获取当前任务结构体的指针
    struct task_struct *tsk = current;

    // 将任务提交到调度工作队列中
    sched_submit_work(tsk);

    // 进入调度循环,直到没有需要被调度的任务
    do {
        // 禁用抢占
        preempt_disable();
        // 调用实际的调度函数 __schedule,并传入调度策略参数 SM_NONE
        __schedule(SM_NONE);
        // 启用抢占,但不进行重新调度
        sched_preempt_enable_no_resched();
    } while (need_resched()); // 循环直到没有需要重新调度的任务

    // 更新工作队列中的任务状态
    sched_update_worker(tsk);
}
EXPORT_SYMBOL(schedule);

调度器的入口函数是schedule,首先获取当前任务结构体的指针,然后将任务提交到调度工作队列中,接着进入一个循环,该循环会禁用抢占,调用实际的调度函数__schedule,并在循环结束后启用抢占。循环会一直执行,直到没有需要重新调度的任务为止。最后,函数会更新工作队列中任务的状态。函数最后export导出schedule函数以供其他部分使用。

static void __sched __schedule(bool preempt)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;

    prev = current;
    rq = this_rq();
    switch_count = &prev->nivcsw;

    // 获取下一个要运行的进程
    next = pick_next_task(rq);

    // 切换到下一个进程
    context_switch(rq, prev, next, switch_count);

    // 如果需要抢占,启用抢占
    if (preempt)
        need_resched();
}

} 这里,__schedule函数负责实际的调度操作。首先,它获取了当前任务结构体的指针(prev)、运行队列(rq)以及切换计数器(switch_count)。然后,通过调用pick_next_task函数,它选择下一个要运行的进程(next)。最后,通过context_switch函数,它进行进程切换,将CPU控制权移交给下一个进程。

具体如何挑选下一个需要运行的进程,就要扒开pick_next_task函数。

pick_next_task

/*
 * 选择下一个要运行的任务。
 */
static inline struct task_struct *
__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
    const struct sched_class *class; // 定义调度类指针
    struct task_struct *p; // 定义任务结构体指针

    // 优化:如果前一个任务是公平调度类中的任务,且运行队列中的任务数与CFS队列中的任务数相等,
    // 则可以直接选择下一个公平类任务,因为其他调度类的任务无法抢占CPU。
    if (likely(!sched_class_above(prev->sched_class, &fAIr_sched_class) &&
               rq->nr_running == rq->cfs.h_nr_running)) {

        p = pick_next_task_fair(rq, prev, rf); // 选择下一个公平调度类任务
        if (unlikely(p == RETRY_TASK)) // 如果选择任务失败,需要重新尝试
            goto restart;

        if (!p) {
            put_prev_task(rq, prev);
            p = pick_next_task_idle(rq); // 如果没有可运行任务,则选择下一个空转调度类任务
        }

        return p;
    }

restart:
    put_prev_task_balance(rq, prev, rf); // 将前一个任务放回队列,进行重新平衡

    // 遍历所有调度类
    for_each_class(class) {
        p = class->pick_next_task(rq); // 选择下一个任务
        if (p)
            return p;
    }

    BUG(); // 如果没有可运行任务,引发BUG。空转类应该始终有可运行的任务。
}

这段代码是用于选择下一个要运行的任务的函数。首先,它检查是否可以优化选择下一个任务,如果前一个任务是公平调度类中的任务,并且运行队列中的任务数与CFS队列中的任务数相等,就可以直接选择下一个公平调度类任务。如果选择任务失败,会重新尝试,然后如果没有可运行任务,将选择下一个空转调度类任务。如果不满足优化条件,将会重新平衡队列,然后遍历所有的调度类,选择下一个任务。如果没有可运行任务,将引发BUG,因为空转类应该始终有可运行的任务。


struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
  struct cfs_rq *cfs_rq = &rq->cfs; // 获取CFS队列
  struct sched_entity *se; // 定义调度实体指针
  struct task_struct *p; // 定义任务结构体指针
  int new_tasks;

again:
  // 如果没有可运行的公平调度任务,跳转到idle标签
  if (!sched_fair_runnable(rq))
    goto idle;

#ifdef CONFIG_FAIR_GROUP_SCHED
  // 如果没有前一个任务,或者前一个任务不属于公平调度类,跳转到simple标签
  if (!prev || prev->sched_class != &fair_sched_class)
    goto simple;

  do {
    struct sched_entity *curr = cfs_rq->curr;

    // 如果当前任务存在
    if (curr) {
      // 如果当前任务在队列上,则更新其运行时间
      if (curr->on_rq)
        update_curr(cfs_rq);
      else
        curr = NULL;

      // 如果CFS队列的运行时间不正常,跳转到idle标签
      if (unlikely(check_cfs_rq_runtime(cfs_rq))) {
        cfs_rq = &rq->cfs;

        // 如果没有可运行任务,跳转到idle标签
        if (!cfs_rq->nr_running)
          goto idle;

        goto simple;
      }
    }

    // 选择下一个调度实体,并切换到相应的CFS队列
    se = pick_next_entity(cfs_rq, curr);
    cfs_rq = group_cfs_rq(se);
  } while (cfs_rq);

  // 获取与选定实体关联的任务结构体
  p = task_of(se);

  // 如果前一个任务不等于选定任务,进行任务切换
  if (prev != p) {
    struct sched_entity *pse = &prev->se;

    while (!(cfs_rq = is_same_group(se, pse))) {
      int se_depth = se->depth;
      int pse_depth = pse->depth;

      if (se_depth <= pse_depth) {
        put_prev_entity(cfs_rq_of(pse), pse);
        pse = parent_entity(pse);
      }
      if (se_depth >= pse_depth) {
        set_next_entity(cfs_rq_of(se), se);
        se = parent_entity(se);
      }
    }

    put_prev_entity(cfs_rq, pse);
    set_next_entity(cfs_rq, se);
  }

  goto done;
simple:
#endif
  // 如果有前一个任务,将其放回队列
  if (prev)
    put_prev_task(rq, prev);

  do {
    // 选择下一个调度实体,并切换到相应的CFS队列
    se = pick_next_entity(cfs_rq, NULL);
    set_next_entity(cfs_rq, se);
    cfs_rq = group_cfs_rq(se);
  } while (cfs_rq);

  // 获取与选定实体关联的任务结构体
  p = task_of(se);

done: __maybe_unused;

#ifdef CONFIG_SMP
  // 将下一个正在运行的任务移动到队列的前面
  list_move(&p->se.group_node, &rq->cfs_tasks);
#endif

  // 如果启用高精度定时器,开始高精度定时
  if (hrtick_enabled_fair(rq))
    hrtick_start_fair(rq, p);

  // 更新不适合运行的任务状态
  update_misfit_status(p, rq);

  return p;

idle:
  // 如果没有rf标志,返回NULL
  if (!rf)
    return NULL;

  // 尝试进行新的空闲平衡操作
  new_tasks = newidle_balance(rq, rf);

  // 如果新的平衡操作失败,返回RETRY_TASK标志
  if (new_tasks < 0)
    return RETRY_TASK;

  // 如果有新的可运行任务,回到again标签重新选择
  if (new_tasks > 0)
    goto again;

  // 如果队列即将变为空闲状态,检查是否需要更新时钟pelt的lost_idle_time
  update_idle_rq_clock_pelt(rq);

  return NULL;
}

这个函数用于选择下一个要在公平调度类中运行的任务。函数中包含了条件判断和循环,以确保选择最适合的任务。


/*
 * 选择下一个调度实体,考虑以下因素,按照顺序:
 * 1) 在进程/任务组之间保持公平性
 * 2) 选择“下一个”进程,因为某个进程确实希望运行
 * 3) 选择“上一个”进程,以提高缓存局部性
 * 4) 如果其他任务可用,则不运行“跳过”的进程
 */
static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
  struct sched_entity *left = __pick_first_entity(cfs_rq);  // 获取最左边的实体
  struct sched_entity *se;

  /*
   * 如果 curr 被设置,我们必须查看它是否位于树中最左边的实体的左侧,
   * 前提是树中确实有实体存在。
   */
  if (!left || (curr && entity_before(curr, left)))
    left = curr;

  se = left; /* 理想情况下,我们运行最左边的实体 */

  /*
   * 避免运行跳过的实体,如果可以不运行其他实体而不会太不公平。
   */
  if (cfs_rq->skip && cfs_rq->skip == se) {
    struct sched_entity *second;

    if (se == curr) {
      second = __pick_first_entity(cfs_rq);  // 获取最左边的实体
    } else {
      second = __pick_next_entity(se);  // 获取下一个实体
      if (!second || (curr && entity_before(curr, second)))
        second = curr;
    }

    if (second && wakeup_preempt_entity(second, left) < 1)
      se = second;
  }

  if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) {
    /*
     * 有人确实希望运行这个实体。如果不不公平,就运行它。
     */
    se = cfs_rq->next;
  } else if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1) {
    /*
     * 更倾向于运行最后一个实体,尝试将 CPU 返回到一个被抢占的任务。
     */
    se = cfs_rq->last;
  }

  return se;
}
 
if (se == curr) {
  second = __pick_first_entity(cfs_rq);  // 获取最左边的实体
} else {
  second = __pick_next_entity(se);  // 获取下一个实体
  if (!second || (curr && entity_before(curr, second)))
    second = curr;
}

if (second && wakeup_preempt_entity(second, left) < 1)
  se = second;

return se; } 函数pick_next_entity的作用是选择下一个要运行的调度实体,它根据一系列因素来决定选择哪个实体,以确保公平性、满足任务需求,并尽量提高缓存局部性。

总结

通过深入分析Linux内核调度器的代码实现,我们了解了调度器的入口函数和选择下一个执行进程的过程。这个过程是内核多任务处理的核心,确保了系统资源的合理分配。深入理解调度器的工作原理将有助于我们更好地优化系统性能,提高响应速度。



Tags:Linux   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
微软 Win11 Linux 子系统(WSL)发布 2.2.2 版本
IT之家 4 月 8 日消息,微软近日更新 Windows Subsystem for Linux(WSL),最新 2.2.2 版本中带来了诸多改进,重点更新了 nft 规则,可以让 IPv6 流量通过 Linux 容器。图源: dev.to,AI...【详细内容】
2024-04-08  Search: Linux  点击:(3)  评论:(0)  加入收藏
从原理到实践:深入探索Linux安全机制
Linux 是一种开源的类Unix操作系统内核,由Linus Torvalds在1991年首次发布,其后又衍生出许多不同的发行版(如Ubuntu、Debian、CentOS等)。前言本文将从用户和权限管理、文件系统...【详细内容】
2024-03-27  Search: Linux  点击:(13)  评论:(0)  加入收藏
在Linux系统中,如何处理内存管理和优化的问题?
本文对 Linux 内存管理和优化的一些高级技巧的详细介绍,通过高级的内存管理技巧,可以帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。在...【详细内容】
2024-03-26  Search: Linux  点击:(6)  评论:(0)  加入收藏
Linux 6.9-rc1 内核发布:AMD P-State 首选核心、BH 工作队列
IT之家 3 月 25 日消息,Linus Torvalds 宣布,Linux 6.9 内核的首个 RC(候选发布)版 Linux 6.9-rc1 发布。▲ Linux 6.9-rc1Linus 表示,Linux 内核 6.9 看起来是一个“相当正常”...【详细内容】
2024-03-25  Search: Linux  点击:(9)  评论:(0)  加入收藏
Linux发行版 Ubuntu 迎更新 界面设计灵感来自 Windows 11
近日,一位第三方开发者推出了一款名为“Wubuntu”的特殊Linux发行版。这款系统源自主流的Ubuntu版本,但在界面设计上却借鉴了微软最新的Windows 11风格,甚至在其中融入了微软标...【详细内容】
2024-02-27  Search: Linux  点击:(41)  评论:(0)  加入收藏
Win + Ubuntu 缝合怪:第三方开发者推出“Wubuntu”Linux 发行版
IT之家 2 月 26 日消息,一位第三方开发者推出了一款名为“Wubuntu”的缝合怪 Linux 发行版,系统本身基于 Ubuntu,但界面为微软 Windows 11 风格,甚至存在微软 Windows 徽标。据...【详细内容】
2024-02-27  Search: Linux  点击:(47)  评论:(0)  加入收藏
Linux中磁盘和文件系统工作原理解析
在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展...【详细内容】
2024-02-20  Search: Linux  点击:(49)  评论:(0)  加入收藏
Linux子系统概览
inux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。以下是Linux...【详细内容】
2024-02-01  Search: Linux  点击:(74)  评论:(0)  加入收藏
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  Search: Linux  点击:(65)  评论:(0)  加入收藏
如何使用PHP SSH2模块执行远程Linux命令
PHP SSH2扩展是用于在PHP程序中使用SSH(安全壳协议)的一种扩展。它允许建立加密连接和执行远程命令、上传和下载文件等操作,十分方便实用。下面我将为大家详细介绍一下该扩展的...【详细内容】
2024-01-26  Search: Linux  点击:(98)  评论:(0)  加入收藏
▌简易百科推荐
微软 Win11 Linux 子系统(WSL)发布 2.2.2 版本
IT之家 4 月 8 日消息,微软近日更新 Windows Subsystem for Linux(WSL),最新 2.2.2 版本中带来了诸多改进,重点更新了 nft 规则,可以让 IPv6 流量通过 Linux 容器。图源: dev.to,AI...【详细内容】
2024-04-08    IT之家  Tags:Linux   点击:(3)  评论:(0)  加入收藏
从原理到实践:深入探索Linux安全机制
Linux 是一种开源的类Unix操作系统内核,由Linus Torvalds在1991年首次发布,其后又衍生出许多不同的发行版(如Ubuntu、Debian、CentOS等)。前言本文将从用户和权限管理、文件系统...【详细内容】
2024-03-27  凡夫编程  微信公众号  Tags:Linux安全   点击:(13)  评论:(0)  加入收藏
在Linux系统中,如何处理内存管理和优化的问题?
本文对 Linux 内存管理和优化的一些高级技巧的详细介绍,通过高级的内存管理技巧,可以帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。在...【详细内容】
2024-03-26  编程技术汇  微信公众号  Tags:Linux   点击:(6)  评论:(0)  加入收藏
Linux 6.9-rc1 内核发布:AMD P-State 首选核心、BH 工作队列
IT之家 3 月 25 日消息,Linus Torvalds 宣布,Linux 6.9 内核的首个 RC(候选发布)版 Linux 6.9-rc1 发布。▲ Linux 6.9-rc1Linus 表示,Linux 内核 6.9 看起来是一个“相当正常”...【详细内容】
2024-03-25    IT之家  Tags:Linux   点击:(9)  评论:(0)  加入收藏
轻松实现Centos系统的软件包安装管理:yum指令实战详解
yum 是一种用于在 CentOS、Red Hat Enterprise Linux (RHEL) 等基于 RPM 的 Linux 发行版上安装、更新和管理软件包的命令行工具。它可以自动解决软件包依赖关系,自动下载并...【详细内容】
2024-02-27  凡夫贬夫  微信公众号  Tags:Centos   点击:(51)  评论:(0)  加入收藏
Win + Ubuntu 缝合怪:第三方开发者推出“Wubuntu”Linux 发行版
IT之家 2 月 26 日消息,一位第三方开发者推出了一款名为“Wubuntu”的缝合怪 Linux 发行版,系统本身基于 Ubuntu,但界面为微软 Windows 11 风格,甚至存在微软 Windows 徽标。据...【详细内容】
2024-02-27    IT之家  Tags:Ubuntu   点击:(47)  评论:(0)  加入收藏
Linux中磁盘和文件系统工作原理解析
在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展...【详细内容】
2024-02-20  王建立    Tags:Linux   点击:(49)  评论:(0)  加入收藏
Linux子系统概览
inux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。以下是Linux...【详细内容】
2024-02-01    简易百科  Tags:Linux   点击:(74)  评论:(0)  加入收藏
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  松鼠宝贝    Tags:Linux内核   点击:(65)  评论:(0)  加入收藏
如何确保Linux进程稳定与持久
在Linux系统中,进程的稳定性与持久性对于维持系统的持续运行至关重要。然而,由于各种原因,进程可能会面临崩溃或系统重启的情况。为了确保关键进程能够持续运行,我们必须采取一...【详细内容】
2024-01-19  松鼠宝贝    Tags:Linux进程   点击:(84)  评论:(0)  加入收藏
站内最新
站内热门
站内头条