根据《深入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. String 源码探究

    起因:忽然想到平时用的HashMap 当key是字符串的时候为什么总可以覆盖,然后看了String的源码发现: private final char value[]; private int hash ...

  2. JNDI数据源的配置

    一.数据源的由来 在Java开发中,使用JDBC操作数据库的四个步骤如下:   ①加载数据库驱动程序(Class.forName("数据库驱动类");)   ②连接数据库(Conn ...

  3. Hystrix入门与分析(二):依赖隔离之线程池隔离

    1.依赖隔离概述 依赖隔离是Hystrix的核心目的.依赖隔离其实就是资源隔离,把对依赖使用的资源隔离起来,统一控制和调度.那为什么需要把资源隔离起来呢?主要有以下几点: 1.合理分配资源,把给资源分 ...

  4. HBase多条件及分页查询的一些方法

    HBase是Apache Hadoop生态系统中的重要一员,它的海量数据存储能力,超高的数据读写性能,以及优秀的可扩展性使之成为最受欢迎的NoSQL数据库之一.它超强的插入和读取性能与它的数据组织方式 ...

  5. jenkins之 Throttle Concurrent Builds使用

    Jenkins控制并发插件 Throttle Concurrent Builds介绍,管网见:https://github.com/jenkinsci/throttle-concurrent-buil ...

  6. D - F(x)

    For a decimal number x with n digits (A nA n-1A n-2 ... A 2A 1), we define its weight as F(x) = A n ...

  7. RxJava2-后台执行耗时操作,实时通知 UI 更新(一)

    一.前言 接触RxJava2已经很久了,也看了网上的很多文章,发现基本都是在对RxJava的基本思想介绍之后,再去对各个操作符进行分析,但是看了之后感觉过了不久就忘了. 偶然的机会看到了开源项目 Rx ...

  8. Promise及Async/Await

      一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪 ...

  9. oracle ORA-01991错误--重建密码文件问题

    问题现象描述: 统计服务器测试没问题,刚好上次配置系统的时候有点问题,故重装一次,配置好安全策略(最近在研究如何新配置一台服务器的时候,第一时间配置好相关的安全设置,有空再写下来). 为了省事,直接冷 ...

  10. Python3.6连接mysql(一)

    初次学习python,因为python连接mysql的时候,需要安装mysql驱动模块 之前按照廖雪峰网站上的方法安装mysql驱动的方法: MySQL官方提供了mysql-connector-pyt ...