根据《深入Linux内核架构》和Linux-3.10.1内核源码,记一些调度过程的主体工作。

  调度器任务:CPU数目比要运行的进程数目少,需要程序之间共享CPU时间,创造并行执行的错觉。分为:调度策略和上下文切换。

  Linux调度器不考虑传统时间片,而考虑进程的等待时间,即进程在就绪队列中已经等待了多长时间(不公平程度),每次选择具有最高等待时间运行。该策略还需考虑优先级进程间切换不得太频繁(上下文切换有开销。在运行进程被新进程强占时,内核会确保被抢占者已经运行某一个最小时间限额)。Linux-2.6之后默认使用完全公平调度策略CFS

  两种方法激活调度:1、进程打算睡眠或出于其他原因放弃CPU;2、周期性检测是否有必要进行进程切换。

  内核提供两个调度器周期性调度器主调度器,合称核心调度器/通用调度器

1、数据结构

  通用调度器是一个分配器,与两个组件交互:1、调度类(模块化实现调度策略:完全公平调度、实时调度、无事可做时调度空闲进程)判断接下来运行哪个进程(每个进程都属于一个调度类。调度器自身不涉及进程管理,而将工作委托给调度类);2、上下文切换:在选中运行进程后,与CPU交互、执行底层任务切换。

  (1)task_struct:1、static_prio静态优先级是进程启动时分配的优先级(nice(-20~+19越小越高)和sched_setscheduler系统调用修改(子进程直接继承));2、normal_prio普通优先级是进程的static_prio和调度策略计算出(子进程的prio会继承);3、prio调度器考虑的优先级(因某些情况下内核需暂时提高进程优先级,不影响其余两个优先级);4、sched_class进程所属调度类;sched_entity可调度实体(调度器不限于调度进程,可处理更大实体。因此在此嵌入实体se到task_struct,内核可通过container_of取得task_struct);5、policy保存对该进程的调度策略(SCHED_NORMAL普通进程、SCHED_BATCH和SCHED_IDLE次要进程冷处理,内核将用户层进程定义的这些常量映射到fair_sched_class完全公平调度器类;SCHED_RR和SCHEDSCHED_FIFO实现软实时,映射到rt_sched_class实时调度器类);5、cpus_allowed一个位域,限制进程可在哪些CPU上运行。

  (2)调度器类:提供通用调度器和各个调度方法之间的关联。其数据结构sched_class由多个函数指针表示。成员:1、sched_class *next按照实时进程-完全公平进程-空闲进程的顺序连接不同调度类的sched_class实例。操作:1、enqueue_task向就绪队列添加一个新进程;2、dequeue_task将一个进程从就绪队列(叫队列,但完全公平调度器对此使用红黑树组织)去除;3、yield_task是使用sched_yield系统调用放弃CPU控制权;4、check_preempt_curr用wake_up_new_task唤醒新进程强占当前进程;5、pick_next_task选择下一个将运行进程;6、put_prev_task用另一个进程代替当前运行的进程之前调用;7、new_task每次新进程建立,都需调用以通知调度器,将新进程加入相应类的就绪队列。

  (3)就绪队列struct rq:每个CPU都有自身的就绪队列,每个活动进程只出现在一个就绪队列,多个CPU上不可同时运行一个进程。但进程并不由就绪队列成员直接管理,而是由调度器类管理,因此各个就绪队列中嵌入了特定于调度器类的子就绪队列cfs_rq cfs; rt_rq rt。系统所有就绪队列都在runqueues数组中,该数组每个元素分别对应系统的一个CPU。

  (4)调度实体sched_entity:on_rq该实体是否在就绪队列上接受调度;sum_exex_runtime进程运行时,记录消耗的CPU时间;exec_start每次被调用都被更新到当前时间;vruntime记录进程运行期间虚拟时间流逝;prev_exec_runtime保存进程被撤销时的sum_exec_runtime

2、周期性调度器scheduler_tick

  任务:1、管理系统和各进程与调度相关统计量;2、激活负责当前进程的调度类的周期性调度方法task_tick,若当前应被重新调度,在task_struct中设置重调度TIF_NEED_RESCHED标志,内核会在适当时机完成。

3、主调度器schedule

  将CPU分配给与当前活动进程不同的另一个进程(总是假定当前活动进程一定会被另一个进程取代)。

  流程:确定当前就绪队列-》在prev中保存指向现在仍活动进程的task_struct的指针-》更新就绪队列时钟-》清除当前运行进程task_struct中的重调度标志-》用相应调度器类方法使当前运行进程停止活动-》通知调度器类当前运行进程要被另一个进程取代-》pick_next_task以优先级从高到底依次检查每个调度类,从最高优先级的调度类中选择最高优先级的进程作为下一个应执行进程(若其余都睡眠,则只有当前进程可运行,就跳过下面了)-》context_switch分配器,执行底层上下文切换-》另一进程接管CPU,所以该函数后续代码可能运行在不同上下文,但稍后在前一进程被再次选择运行时,刚好在这一点恢复,但prev不指向正确进程,所以需要通过current和Ttest_thread_flag找到当前线程。

  context_switch:1、switch_mm更换通过task_struct->mm描述的内存管理上下文(load_cr3(next->pgd);//下一个进程的页目录基地址写入cr3寄存器,刷出TLB、向MMU提供新信息);2、switch_to切换处理器寄存器内容和内核栈(用户栈在虚拟地址空间的用户部分,已在1中更新)。

  context_swich函数中:1、注意若将运行是内核线程,其task_struct->mm是NULL,它没有自身的用户空间内存上下文,所以用惰性TLB通知底层体系结构无需切换虚拟地址空间部分,而可能在某个随机进程地址空间的上部执行,所以从prev->active_mm设置其next->active_mm即借用prev的地址空间(若prev也是内核线程,它的active_mm也会是借用再上一个);否则就switch_mm;2、若prev是内核线程,则需将prev->active_mm设置为空,以断开内核线程与借用地址空间联系;3、然后switch_to(prev, next, prev)-》barrier-》finish_task_switch针对此前的活动进程进行清理。

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;
/*
* For paravirt, this is coupled with an exit in switch_to to
* combine the page table reload and the switch backend into
* one hypercall.
*/
arch_start_context_switch(prev); if (!mm) {//内核线程的mm为NULL
next->active_mm = oldmm;//利用上一个活动进程的active_mm(若上一个也是内核线程,它的这里也是借用了再上一个,总会有值)
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);//惰性TLB,因为内核线程没有虚拟地址空间的用户空间部分,告诉底层体系结构无须切换
} else
switch_mm(oldmm, mm, next);//把虚拟内存从一个进程映射到新进程中 if (!prev->mm) {//prev是内核线程
prev->active_mm = NULL;//断开内核线程与之前借用的地址空间联系
rq->prev_mm = oldmm;
}
/*
* Since the runqueue lock will be released by the next
* task (which is an invalid locking op but in the case
* of the scheduler it's an obvious special-case), so we
* do an early lockdep release here:
*/
#ifndef __ARCH_WANT_UNLOCKED_CTXSW
spin_release(&rq->lock.dep_map, , _THIS_IP_);
#endif context_tracking_task_switch(prev, next);
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);//从上一个进程处理器状态设置为新进程的处理器状态
//包括保存、恢复栈信息和寄存器信息和其他与体系结构相关的状态信息,都必须以每个进程为对象管理和保存 barrier();
/*
* this_rq must be evaluated again because prev may have moved
* CPUs since it called schedule(), thus the 'rq' on its stack
* frame will be invalid.
*/
finish_task_switch(this_rq(), prev);
}

  switch_to:之后的代码只有当前进程下一次被选择运行时才执行。三个参数原因:若A->B->C->A,在第一次A被调出时,A的内核栈中prev是A,next是B,之后被调入时,控制权回到swich_to后的点,若恢复栈则prev是A,next是B,但实际上prev是C,所以在switch_to中通过修改最后的那个prev,即返回指向此前运行的指针C。

调度器的实现、schedule、switch_context、switch_to的更多相关文章

  1. 八、mysql视图、存储过程、函数以及时间调度器

    .create or replace view emp_view as select * from t4 ;给t4表创建一个名为emp_view的视图 .drop view emp_view 删除视图 ...

  2. RxJS——调度器(Scheduler)

    调度器 什么是调度器?调度器是当开始订阅时,控制通知推送的.它由三个部分组成. 调度是数据结构.它知道怎样在优先级或其他标准去存储和排队运行的任务 调度器是一个执行上下文.它表示任务在何时何地执行(例 ...

  3. 主调度器schedule

    中断处理完毕后,系统有三种执行流向:                                                                               1)直 ...

  4. Linux进程核心调度器之主调度器schedule--Linux进程的管理与调度(十九)

    主调度器 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_N ...

  5. Linux进程管理 (2)CFS调度器

    关键词: 目录: Linux进程管理 (1)进程的诞生 Linux进程管理 (2)CFS调度器 Linux进程管理 (3)SMP负载均衡 Linux进程管理 (4)HMP调度器 Linux进程管理 ( ...

  6. linux调度器源码分析 - 运行(四)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 之前的文章已经将调度器的数据结构.初始化.加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运 ...

  7. [Spring]支持注解的Spring调度器

    概述 如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架. 使用Spring的调度框架,优点是:支持注解(@Scheduler),可 ...

  8. Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析

    最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...

  9. 【Cocos2d-x 3.x】 调度器Scheduler类源码分析

    非个人的全部理解,部分摘自cocos官网教程,感谢cocos官网. 在<CCScheduler.h>头文件中,定义了关于调度器的五个类:Timer,TimerTargetSelector, ...

随机推荐

  1. Flutter & Dart 安装在window系统

    一.系统环境 flutter最低要求 1,windows7 SP1 64位版本以上,我的系统就是windows 7 sp1 64bit 2,git for windows ,没有安装的需要到这里下载  ...

  2. Kafka获取订阅某topic的所有consumer group【客户端版】

    之前写过如何用服务器端的API代码来获取订阅某topic的所有consumer group,参见这里.使用服务器端的API需要用到kafka.admin.AdminClient类,但是这个类在0.11 ...

  3. [原]Django-issue(1)---postgresql数据库连接密码错误

    环境: Django==1.9.13 psycopg2==2.7.5 Python 3.6.5 postgresql 1.18.1 配置django的时候出现问题 检查setting,问题点:由于安装 ...

  4. 如何判断java对象是否为String数组

    if (entry.getValue() instanceof String[]) {// ko .................... }

  5. nodejs electron 创建桌面应用

    //首先安装cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org //使用cnpm进行安装,使用方法和npm相同 cn ...

  6. ubuntu的apt-get install的默认安装路径(转)

    一.apt-get 安装 deb是debian linus的安装格式,跟red hat的rpm非常相似,最基本的安装命令是:dpkg -i file.deb或者直接双击此文件 dpkg 是Debian ...

  7. Eclipse 02: 安装SVN插件

    1.下载最新的Eclipse,我的版本是3.7.2 indigo(Eclipse IDE for Java EE Developers)版    如果没有安装的请到这里下载安装:http://ecli ...

  8. JS中的三种弹出式消息提醒(警告窗口、确认窗口、信息输入窗口)的命令是什么?

    一种: <a href="javascript:if(confirm('确实要删除该内容吗?')){location='http://www.google.com'}"> ...

  9. online ddl与pt-osc详解

    Ⅰ.背景 优化sql的过程中发现表上少一个索引,直接加一个?会不会hang住?不加?sql又跑不好,由此引出一个问题--ddl操作怎么做? Ⅱ.闲扯三两句 5.6版本之前的MySQL创建索引不支持on ...

  10. Python_环境部署及报错汇总(0)

    一.安装Anaconda Anaconda是一个开源的包.环境管理器,可以用于在同一个机器上安装不同版本的软件包及其依赖,并能够在不同的环境之间切换. Anaconda包括Conda.Python以及 ...