0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

带大家看看Linux内核如何调度进程的

B4Pb_gh_6fde77c 来源:Linux内核远航者 作者:Linux内核远航者 2021-07-26 15:14 次阅读

1.开场白

环境:

处理器架构:arm64

内核源码:linux-5.11

ubuntu版本:20.04.1

代码阅读工具:vim+ctags+cscope

本文步进到Linux内核进程管理的核心部分,打开调度器的黑匣子,来看看Linux内核如何调度进程的。实际上,进程调度器主要做两件事:选择下一个进程,然后进行上下文切换。

而何时调用主调度器调度进程那是调度时机所关注的问题,而调度时机在之前的内核抢占文章已经做了详细讲解,在此不在赘述,而本文关注的调度时机是真正调用主调度器的时机。

本文分析的内核源代码主要集中在:

kernel/sched/core.c

kernel/sched/fair.c

2.调用时机

关于调度时机,网上的文章也五花八门,之前在内核抢占文章已经做了详细讲解,而在本文我们从源码注释中给出依据(再次强调一下:本文的调度时机关注的是何时调用主调度器,不是设置重新调度标志的时机,之前讲解中我们知道他们都可以称为调度时机)。

先来说一下什么是主调度器,其实和主调度器并列的还有一个叫做周期性调度器的东西(后面有机会会讲解,主要用于时钟中断tick调来使夺取处理器的控制权),他们都是内核中的一个函数,在合适的时机被调用。

主调度器函数如下:

kernel/sched/core.c __schedule()

内核的很多路径会包装这个函数,主要分为主动调度和抢占式调度场景。

内核源码中主调度器函数也给出了调度时机的注释,下面我们就以此为依据来看下:

kernel/sched/core.c /* *__schedule()isthemainschedulerfunction. * *Themainmeansofdrivingtheschedulerandthusenteringthisfunctionare: * *1.Explicitblocking:mutex,semaphore,waitqueue,etc. * *2.TIF_NEED_RESCHEDflagischeckedoninterruptanduserspacereturn *paths.Forexample,seearch/x86/entry_64.S. * *Todrivepreemptionbetweentasks,theschedulersetstheflagintimer *interrupthandlerscheduler_tick(). * *3.Wakeupsdon'treallycauseentryintoschedule().Theyadda *tasktotherun-queueandthat'sit. * *Now,ifthenewtaskaddedtotherun-queuepreemptsthecurrent *task,thenthewakeupsetsTIF_NEED_RESCHEDandschedule()gets *calledonthenearestpossibleoccasion: * *-Ifthekernelispreemptible(CONFIG_PREEMPTION=y): * *-insyscallorexceptioncontext,atthenextoutmost *preempt_enable().(thismightbeassoonasthewake_up()'s *spin_unlock()!) * *-inIRQcontext,returnfrominterrupt-handlerto *preemptiblecontext * *-Ifthekernelisnotpreemptible(CONFIG_PREEMPTIONisnotset) *thenatthenext: *-cond_resched()call *-explicitschedule()call *-returnfromsyscallorexceptiontouser-space *-returnfrominterrupt-handlertouser-space * *WARNING:mustbecalledwithpreemptiondisabled! */ staticvoid__schednotrace__schedule(boolpreempt)

我们对注释做出解释,让大家深刻理解调度时机(基本上是原样翻译,用颜色标注)。

1.显式阻塞场景:包括互斥体、信号量、等待队列等。

这个场景主要是为了等待某些资源而主动放弃处理器,来调用主调度器,如发现互斥体被其他内核路径所持有,则睡眠等待互斥体被释放的时候来唤醒我。

2.在中断和用户空间返回路径上检查TIF_NEED_RESCHED标志。例如,arch/x86/entry_64.S。为了在任务之间驱动抢占,调度程序在计时器中断处理程序scheduler_tick()中设置标志。

解释如下:这实际上是说重新调度标志(TIF_NEED_RESCHED)的设置和检查的情形。

1)重新调度标志设置情形:如scheduler_tick周期性调度器按照特定条件设置、唤醒的路径上按照特定条件设置等。当前这样的场景并不会直接调用主调度器,而会在最近的调度点到来时调用主调度器。

2)重新调度标志检查情形:是真正的调用主调度器,下面的场景都会涉及到,在此不在赘述。

3.唤醒并不会真正导致schedule()的进入。他们添加一个任务到运行队列,仅此而已。

现在,如果添加到运行队列中的新任务抢占了当前任务,那么唤醒设置TIF_NEED_RESCHED, schedule()在最近的可能情况下被调用:

1)如果内核是可抢占的(CONFIG_PREEMPTION=y)

-在系统调用或异常上下文中,最外层的preempt_enable()。(这可能和wake_up()的spin_unlock()一样快!)

-在IRQ上下文中,从中断处理程序返回到抢占上下文

注释中很简洁的几句话,但其中的含义需要深刻去体会。

首先需要知道一点是:内核抢占说的是处于内核态的任务被其他任务所抢占的情况(无论是不是可抢占式内核,处于用户态的任务都可以被抢占,处于内核态的任务是否能被抢占由是否开启内核抢占来决定),当然内核态的任务可以是内核线程也可以是通过系统调用请求内核服务的用户任务。

情况1:这是重新开启内核抢占的情况,即是抢占计数器为0时,检查重新调度标志(TIF_NEED_RESCHED),如果设置则调用主调度器,放弃处理器(这是抢占式调度)。

情况2:中断返回内核态的时候,检查重新调度标志(TIF_NEED_RESCHED),如果设置且抢占计数器为0时则调用主调度器,放弃处理器(这是抢占式调度)。

注:关于内核抢占可以参考之前发布的文章。

2)如果内核是不可抢占的(CONFIG_PREEMPTION=y)

cond_resched()调用

显式的schedule()调用

从系统调用或异常返回到用户空间

从中断处理器返回到用户空间

解释如下:

cond_resched()是为了在不可抢占内核的一些耗时的内核处理路径中增加主动抢占点(抢占计数器是否为0且当前任务被设置了重新调度标志),则调用主调度器进行抢占式调度,所进行低延时处理。

显式的schedule()调用,这是主动放弃处理器的场景,如一些睡眠场景,像用户任务调用sleep。

系统调用或异常返回到用户空间使会判断当前进程是否设置重新调度标志(TIF_NEED_RESCHED),如果设置则调用主调度器,放弃处理器。

中断处理器返回到用户空间会判断当前进程是否设置重新调度标志(TIF_NEED_RESCHED),如果设置则调用主调度器,放弃处理器。

其实还有一种场景也会调用到主调度器让出处理器,那就是进程退出时,这里不在赘述。

下面给出总结:

1.主动调度:

睡眠场景,如sleep。

显式阻塞场景,如互斥体,信号量,等待队列,完成量等。

任务退出时,调用do_exit去释放进程资源,最后会调用一次主调度器

2.抢占调度:

不可抢占式内核

cond_resched()调用

显式的schedule()调用

从系统调用或异常返回到用户空间

从中断处理器返回到用户空间

可抢占式内核(增加一些抢占点)

重新开启内核抢占

中断返回内核态的时候

3.主调度器调用时机源码窥探

下面给出主要的一些主调度器调用时机源码分析,作为学习参考。

3.1 常规场景

中断返回用户态场景:

arch/arm64/kernel/entry.S el0_irq ->ret_to_user ->work_pending ->do_notify_resume ->if(thread_flags&_TIF_NEED_RESCHED){//arch/arm64/kernel/signal.c schedule(); ->__schedule(false);//kernel/sched/core.cfalse表示主动调度

异常返回用户态场景:

arch/arm64/kernel/entry.S el0_sync ->ret_to_user ...

任务退出场景:

kernel/exit.c do_exit ->do_task_dead ->__schedule(false);//kernel/sched/core.cfalse表示主动调度

显式阻塞场景(举例互斥体):

kernel/locking/mutex.c mutex_lock ->__mutex_lock_slowpath ->__mutex_lock ->__mutex_lock_common ->schedule_preempt_disabled ->schedule(); ->__schedule(false);//kernel/sched/core.cfalse表示主动调度

3.2 支持内核抢占场景

中断返回内核态场景

arch/arm64/kernel/entry.S el1_irq #ifdefCONFIG_PREEMPTION ->arm64_preempt_schedule_irq ->preempt_schedule_irq(); ->__schedule(true);//kernel/sched/core.ctrue表示抢占式调度 #endif

内核抢占开启场景

preempt_enable ->if(unlikely(preempt_count_dec_and_test()))//抢占计数器减一为0 __preempt_schedule(); ->preempt_schedule//kernel/sched/core.c ->__schedule(true)//调用主调度器进行抢占式调度

注:一般说异常/中断返回,返回是处理器异常状态,可能是用户态也可能是内核态,但是会看到很多资料写的都是用户空间/内核空间并不准确,但是我们认为表达一个意思,做的心中有数即可。

3.选择下一个进程

本节主要讲解主调度器是如何选择下一个进程的,这和调度策略强相关。

下面我们来看具体实现:

kernel/sched/core.c __schedule ->next=pick_next_task(rq,prev,&rf); ->if(likely(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)) gotorestart; /*Assumesfair_sched_class->next==idle_sched_class*/ if(!p){ put_prev_task(rq,prev); p=pick_next_task_idle(rq); } returnp; } for_each_class(class){ p=class->pick_next_task(rq); if(p) returnp; }

这里做了优化,当当前进程的调度类为公平调度类或者空闲调度类时,且cpu运行队列的进程个数等于cfs运行队列进程个数,说明运行队列进程都是普通进程,则直接调用公平调度类的pick_next_task_fair选择下一个进程(选择红黑树最左边的那个进程),如果没有找到说明当前进程调度类为空闲调度类,直接调用pick_next_task_idle选择idle进程。

否则,遍历调度类,从高优先级调度类开始调用其pick_next_task方法选择下一个进程。

下面以公平调度类为例来看如何选择下一个进程的:调用过程如下(这里暂不考虑组调度情况):

pick_next_task ->pick_next_task_fair//kernel/sched/fair.c ->if(prev) put_prev_task(rq,prev); se=pick_next_entity(cfs_rq,NULL); set_next_entity(cfs_rq,se);

先看put_prev_task:

put_prev_task ->prev->sched_class->put_prev_task(rq,prev); ->put_prev_task_fair ->put_prev_entity(cfs_rq,se); ->/*Put'current'backintothetree.*/ __enqueue_entity(cfs_rq,prev); cfs_rq->curr=NULL;

这里会调用__enqueue_entity将前一个进程重新加入到cfs队列的红黑树。然后将cfs_rq->curr 设置为空。

再看pick_next_entity:

pick_next_entity ->left=__pick_first_entity(cfs_rq); ->left=rb_first_cached(&cfs_rq->tasks_timeline);

将选择cfs队列红黑树最左边进程。

最后看set_next_entity:

set_next_entity ->__dequeue_entity(cfs_rq,se); ->cfs_rq->curr=se;

这里调用__dequeue_entity将下一个选择的进程从cfs队列的红黑树中删除,然后将cfs队列的curr指向进程的调度实体。

选择下一个进程总结如下:

运行队列中只有公平进程则选择公平调度类的pick_next_task_fair选择进程。

当前进程为idle进程,且没有公平进程存在情况下,调用pick_next_task_idle选择idle进程。

运行队列存在除了公平进程的其他进程,则从高优先级到低优先级调用具体调度类的pick_next_task选择进程。

对于公平调度类,选择下一个进程主要过程如下:1)调用put_prev_task方法将前一个进程重新加入cfs队列的红黑树。2)调用pick_next_entity 选择红黑树最左边的进程作为下一个进程。3)将下一个进程从红黑树中删除,cfs队列的curr指向进程的调度实体。

通用的调度类选择顺序为:

stop_sched_class ->dl_sched_class->rt_sched_class->fair_sched_class ->idle_sched_class

比如:当前运行队列都是cfs的普通进程,某一时刻发生中断唤醒了一个rt进程,那么在最近的调度点到来时就会调用主调度器选择rt进程作为next进程。

做了以上的工作之后,红黑树中选择下一个进程的时候就不会再选择到当前cpu上运行的进程了,而当前进程调度实体又被cfs队列的curr来记录着(运行队列的curr也会记录当前进程)。

下面给出公平调度类选择下一个进程图解(其中A为前一个进程,即是当前进程,即为前一个进程,B为下一个进程)

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11219

    浏览量

    208879

原文标题:深入理解Linux内核之主调度器(上)

文章出处:【微信号:gh_6fde77c41971,微信公众号:FPGA干货】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一文搞懂Linux进程的睡眠和唤醒

    ): 进程在等待某个条件满足(如I/O操作),可以被信号唤醒。 Linux通过内核提供的系统调用来控制进程的睡眠。常用的系统调用有: sleep(): 使
    发表于 11-04 15:15

    Linux用户身份与进程权限详解

    在学习 Linux 系统权限相关的主题时,我们首先关注的基本都是文件的 ugo 权限。ugo 权限信息是文件的属性,它指明了用户与文件之间的关系。但是真正操作文件的却是进程,也就是说用户所拥有的文件
    的头像 发表于 10-23 11:41 239次阅读
    <b class='flag-5'>Linux</b>用户身份与<b class='flag-5'>进程</b>权限详解

    linux驱动程序如何加载进内核

    Linux系统中,驱动程序是内核与硬件设备之间的桥梁。它们允许内核与硬件设备进行通信,从而实现对硬件设备的控制和管理。 驱动程序的编写 驱动程序的编写是Linux驱动开发的基础。在编
    的头像 发表于 08-30 15:02 369次阅读

    Linux调度器的核心scheduler_tick介绍

    scheduler_tick在Linux内核中扮演着关键角色。它不仅负责处理定时器中断和更新系统时间,还记录进程的运行时间,并决定是否需要进行任务切换。通过这些功能,scheduler_tick有效保障了系统的时间管理和任务
    的头像 发表于 08-22 14:54 400次阅读

    Linux内核测试技术

    Linux 内核Linux操作系统的核心部分,负责管理硬件资源和提供系统调用接口。随着 Linux 内核的不断发展和更新,其复杂性和代码规
    的头像 发表于 08-13 13:42 428次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>测试技术

    深入探讨Linux进程调度

    Linux操作系统作为一个开源且广泛应用的操作系统,其内核设计包含了许多核心功能,而进程调度器(Scheduler)就是其中一个至关重要的模块。进程
    的头像 发表于 08-13 13:36 890次阅读
    深入探讨<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b><b class='flag-5'>调度</b>器

    Linux内核中的页面分配机制

    Linux内核中是如何分配出页面的,如果我们站在CPU的角度去看这个问题,CPU能分配出来的页面是以物理页面为单位的。也就是我们计算机中常讲的分页机制。本文就看下Linux内核是如何管
    的头像 发表于 08-07 15:51 231次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>中的页面分配机制

    欢创播报 华为宣布鸿蒙内核已超越Linux内核

    1 华为宣布鸿蒙内核已超越Linux内核   6月21日,在华为开发者大会上, HarmonyOS NEXT(鸿蒙NEXT)——真正独立于安卓和iOS的鸿蒙操作系统,正式登场。这是HarmonyOS
    的头像 发表于 06-27 11:30 777次阅读

    linux内核主要由哪几个部分组成,作用是什么

    Linux内核主要由以下几个部分组成: 进程管理:Linux内核负责管理和调度系统中的
    的头像 发表于 01-22 14:34 2598次阅读

    Linux内核中信号的传递过程

    前面我们已经介绍了内核注意到信号的到来,调用相关函数更新进程描述符以便进程接收处理信号。但是,如果目标进程此时没有运行,内核则推迟传递信号。
    的头像 发表于 01-17 09:51 1058次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>中信号的传递过程

    兆芯正引入Linux首选内核调度技术,优化性能

    近期,兆芯工程团队亦在致力于将首选内核调度技术引进Linux系统中。他们试图通过提议的Linux内核补丁,利用已有的ACPI功能来辨别每个核
    的头像 发表于 12-29 14:30 523次阅读
    兆芯正引入<b class='flag-5'>Linux</b>首选<b class='flag-5'>内核</b><b class='flag-5'>调度</b>技术,优化性能

    获取Linux内核源码的方法

    (ELF1/ELF1S开发板及显示屏)Linux内核是操作系统中最核心的部分,它负责管理计算机硬件资源,并提供对应用程序和其他系统组件的访问接口,控制着计算机的内存、处理器、设备驱动程序和文件系统等
    的头像 发表于 12-13 09:49 615次阅读
    获取<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>源码的方法

    Linux进程、线程和协程的基础概念

    进程是计算机中运行的程序的实例,它是操作系统中最基本的执行单元之一。每个进程都有自己的独立内存空间、系统资源和代码执行流。这意味着一个进程的崩溃通常不会影响其他进程
    的头像 发表于 12-06 09:22 782次阅读

    linux查看weblogic进程

    Linux操作系统中,WebLogic是一种常用的Java应用服务器,用于部署和管理企业级Java应用程序。为了确保WebLogic服务器正常运行,有时我们需要查看WebLogic进程以了解其状态
    的头像 发表于 12-05 16:07 1796次阅读

    如何在内核中启动secondary cpu

    调度器之前,并没有实际的业务进程,而我们知道内核中cpu在空闲时会执行idle进程。因此,在其启动之前需要为每个cpu初始化一个idle进程
    的头像 发表于 12-05 15:46 541次阅读
    如何在<b class='flag-5'>内核</b>中启动secondary cpu