Linux进程调度器-基础

背景

  • Read the fucking source code!  --By 鲁迅

  • A picture is worth a thousand words. --By 高尔基

说明:

  1. Kernel版本:4.14

  2. ARM64处理器,Contex-A53,双核

  3. 使用工具:Source Insight 3.5, Visio

1. 概述

从这篇文章开始,将开始Linux调度器的系列研究了。本文也会从一些基础的概念及数据结构入手,先打造一个粗略的轮廓,后续的文章将逐渐深入。

2. 概念

2.1 进程


  • 从教科书上,我们都能知道:进程是资源分配的最小单位,而线程是CPU调度的的最小单位。

  • 进程不仅包括可执行程序的代码段,还包括一系列的资源,比如:打开的文件、内存、CPU时间、信号量、多个执行线程流等等。而线程可以共享进程内的资源空间。

  • 在Linux内核中,进程和线程都使用struct task_struct结构来进行抽象描述。

  • 进程的虚拟地址空间分为用户虚拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间,没有用户虚拟地址空间的进程称为内核线程。

Linux内核使用task_struct结构来抽象,该结构包含了进程的各类信息及所拥有的资源,比如进程的状态、打开的文件、地址空间信息、信号资源等等。task_struct结构很复杂,下边只针对与调度相关的某些字段进行介绍。

struct task_struct {/* ... *//* 进程状态 */volatile long      state;/* 调度优先级相关,策略相关 */int        prio;int        static_prio;int        normal_prio;unsigned int      rt_priority;unsigned int      policy;/* 调度类,调度实体相关,任务组相关等 */const struct sched_class  *sched_class;struct sched_entity    se;struct sched_rt_entity    rt;
#ifdef CONFIG_CGROUP_SCHEDstruct task_group    *sched_task_group;
#endifstruct sched_dl_entity    dl;/* 进程之间的关系相关 *//* Real parent process: */struct task_struct __rcu  *real_parent;/* Recipient of SIGCHLD, wait4() reports: */struct task_struct __rcu  *parent;/** Children/sibling form the list of natural children:*/struct list_head    children;struct list_head    sibling;struct task_struct    *group_leader;/* ... */
}

2.2 进程状态


  • 上图中左侧为操作系统中通俗的进程三状态模型,右侧为Linux对应的进程状态切换。每一个标志描述了进程的当前状态,这些状态都是互斥的;

  • Linux中的就绪态运行态对应的都是TASK_RUNNING标志位,就绪态表示进程正处在队列中,尚未被调度;运行态则表示进程正在CPU上运行;

内核中主要的状态字段定义如下

/* Used in tsk->state: */
#define TASK_RUNNING			0x0000
#define TASK_INTERRUPTIBLE		0x0001
#define TASK_UNINTERRUPTIBLE		0x0002/* Used in tsk->exit_state: */
#define EXIT_DEAD			0x0010
#define EXIT_ZOMBIE			0x0020
#define EXIT_TRACE			(EXIT_ZOMBIE | EXIT_DEAD)/* Used in tsk->state again: */
#define TASK_PARKED			0x0040
#define TASK_DEAD			0x0080
#define TASK_WAKEKILL			0x0100
#define TASK_WAKING			0x0200
#define TASK_NOLOAD			0x0400
#define TASK_NEW			0x0800
#define TASK_STATE_MAX			0x1000/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE			(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED			(TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED			(TASK_WAKEKILL | __TASK_TRACED)#define TASK_IDLE			(TASK_UNINTERRUPTIBLE | TASK_NOLOAD)

2.3 scheduler 调度器


  • 所谓调度,就是按照某种调度的算法,从进程的就绪队列中选取进程分配CPU,主要是协调对CPU等的资源使用。进程调度的目标是最大限度利用CPU时间。

内核默认提供了5个调度器,Linux内核使用struct sched_class来对调度器进行抽象:

  1. Stop调度器, stop_sched_class:优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;

  2. Deadline调度器, dl_sched_class:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;

  3. RT调度器, rt_sched_class:实时调度器,为每个优先级维护一个队列;

  4. CFS调度器, cfs_sched_class:完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;

  5. IDLE-Task调度器, idle_sched_class:空闲调度器,每个CPU都会有一个idle线程,当没有其他进程可以调度时,调度运行idle线程;

Linux内核提供了一些调度策略供用户程序来选择调度器,其中Stop调度器IDLE-Task调度器,仅由内核使用,用户无法进行选择:

  • SCHED_DEADLINE:限期进程调度策略,使task选择Deadline调度器来调度运行;

  • SCHED_RR:实时进程调度策略,时间片轮转,进程用完时间片后加入优先级对应运行队列的尾部,把CPU让给同优先级的其他进程;

  • SCHED_FIFO:实时进程调度策略,先进先出调度没有时间片,没有更高优先级的情况下,只能等待主动让出CPU;

  • SCHED_NORMAL:普通进程调度策略,使task选择CFS调度器来调度运行;

  • SCHED_BATCH:普通进程调度策略,批量处理,使task选择CFS调度器来调度运行;

  • SCHED_IDLE:普通进程调度策略,使task以最低优先级选择CFS调度器来调度运行;

2.4 runqueue 运行队列


  • 每个CPU都有一个运行队列,每个调度器都作用于运行队列;

  • 分配给CPU的task,作为调度实体加入到运行队列中;

  • task首次运行时,如果可能,尽量将它加入到父task所在的运行队列中(分配给相同的CPU,缓存affinity会更高,性能会有改善);

Linux内核使用struct rq结构来描述运行队列,关键字段如下:

/** This is the main, per-CPU runqueue data structure.** Locking rule: those places that want to lock multiple runqueues* (such as the load balancing or the thread migration code), lock* acquire operations must be ordered by ascending &runqueue.*/
struct rq {/* runqueue lock: */raw_spinlock_t lock;/** nr_running and cpu_load should be in the same cacheline because* remote CPUs use both these fields when doing load calculation.*/unsigned int nr_running;/* 三个调度队列:CFS调度,RT调度,DL调度 */struct cfs_rq cfs;struct rt_rq rt;struct dl_rq dl;/* stop指向迁移内核线程, idle指向空闲内核线程 */struct task_struct *curr, *idle, *stop;/* ... */
}

2.5 task_group 任务分组


  • 利用任务分组的机制,可以设置或限制任务组对CPU的利用率,比如将某些任务限制在某个区间内,从而不去影响其他任务的执行效率;

  • 引入task_group后,调度器的调度对象不仅仅是进程了,Linux内核抽象出了sched_entity/sched_rt_entity/sched_dl_entity描述调度实体,调度实体可以是进程或task_group

  • 使用数据结构struct task_group来描述任务组,任务组在每个CPU上都会维护一个CFS调度实体、CFS运行队列,RT调度实体,RT运行队列

Linux内核使用struct task_group来描述任务组,关键的字段如下:

/* task group related information */
struct task_group {/* ... *//* 为每个CPU都分配一个CFS调度实体和CFS运行队列 */
#ifdef CONFIG_FAIR_GROUP_SCHED/* schedulable entities of this group on each cpu */struct sched_entity **se;/* runqueue "owned" by this group on each cpu */struct cfs_rq **cfs_rq;unsigned long shares;
#endif/* 为每个CPU都分配一个RT调度实体和RT运行队列 */
#ifdef CONFIG_RT_GROUP_SCHEDstruct sched_rt_entity **rt_se;struct rt_rq **rt_rq;struct rt_bandwidth rt_bandwidth;
#endif/* task_group之间的组织关系 */struct rcu_head rcu;struct list_head list;struct task_group *parent;struct list_head siblings;struct list_head children;/* ... */
};

3. 调度程序

调度程序依靠几个函数来完成调度工作的,下边将介绍几个关键的函数。

  1. 主动调度 - schedule()

  • schedule()函数,是进程调度的核心函数,大体的流程如上图所示。

  • 核心的逻辑:选择另外一个进程来替换掉当前运行的进程。进程的选择是通过进程所使用的调度器中的pick_next_task函数来实现的,不同的调度器实现的方法不一样;进程的替换是通过context_switch()来完成切换的,具体的细节后续的文章再深入分析。

  1. 周期调度 - schedule_tick()

  • 时钟中断处理程序中,调用schedule_tick()函数;

  • 时钟中断是调度器的脉搏,内核依靠周期性的时钟来处理器CPU的控制权;

  • 时钟中断处理程序,检查当前进程的执行时间是否超额,如果超额则设置重新调度标志(_TIF_NEED_RESCHED);

  • 时钟中断处理函数返回时,被中断的进程如果在用户模式下运行,需要检查是否有重新调度标志,设置了则调用schedule()调度;

  1. 高精度时钟调度 - hrtick()

  • 高精度时钟调度,与周期性调度类似,不同点在于周期调度的精度为ms级别,而高精度调度的精度为ns级别;

  • 高精度时钟调度,需要有对应的硬件支持;

  1. 进程唤醒时调度 - wake_up_process()

  • 唤醒进程时调用wake_up_process()函数,被唤醒的进程可能抢占当前的进程;

上述讲到的几个函数都是常用于调度时调用。此外,在创建新进程时,或是在内核抢占时,也会出现一些调度点。

本文只是粗略的介绍了一个大概,后续将针对某些模块进行更加深入的分析,敬请期待。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

嵌入式Linux

微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每天学习点--------第五天(2017-10-9) 摘要: 常用的集合

今天学习 java.util下面的软件包 包含 collection框架、遗留的coolection类、事件模型、日期和时间设施、国际化和各种工具类&#xff08;字符串标记生成器、随机数生成器和位数组&#xff09; 一、Collenction<.E> 接口 转载于:https://www.cnblogs.com/hanxue112253/p/…

Linux内存,先看这篇文章

内存大小计算我们拿32位系统来举个栗子2^32 ‭4,294,967,296‬ bytes‭4,294,967,296‬ bytes / 1024 ‭4,194,304‬ kbytes4,194,304‬ kbytes / 1024 ‭4,096‬ M‭4,096‬ M /1024 4G物理内存如何分页&#xff1f;分段和分页计算机内存管理的两种方式&#xff0c;这里我…

cloudstack java api_CloudStack API编程指引

前言本文阐述为CloudStack编写新API或者更新已存在API时应遵循的约定和编程指引。参考文档(暂略)介绍当你需要为CS添加新的API时&#xff0c;需要创建一个Request类和Response类(或者在扩展CS API功能时它的API Responese已经定义的情况下重用已经存在的API Response类)。编写C…

在ODM公司要不要跳槽到创业公司

读者朋友提问&#xff1a; 发哥&#xff0c;我现在在手机odm公司做指纹模块做了两三个月&#xff0c;基本天天加班到十点以后&#xff0c;后面要被调到camera团队&#xff0c;但是从这几个月的经历来看&#xff0c;感觉学到的不多&#xff0c;代码都是供应商写的&#xff0c;很…

安卓系统应用启动流程分析

随着移动开发的兴起&#xff0c;安卓系统的重要性愈加突显。本文简要介绍安卓系统上应用启动流程&#xff0c;对于应用开发、系统定制以及性能优化人员来说&#xff0c;熟悉应用启动流程会使得在今后的工作中更加得心应手&#xff0c;做到知其然&#xff0c;知其所以然。本文主…

物联网通信协议全解析

随着物联网设备数量的持续增加&#xff0c;这些设备之间的通信或连接已成为一个重要的思考课题。通信对物联网来说十分常用且关键&#xff0c;无论是近距离无线传输技术还是移动通信技术&#xff0c;都影响着物联网的发展。而在通信中&#xff0c;通信协议尤其重要&#xff0c;…

【长沙集训】2017.10.10

Adore 1.1 问题描述 小 w 偶然间遇到了一个 DAG。 这个 DAG 有 m 层&#xff0c;第一层只有1个源点&#xff0c;最后一层只有1个汇点&#xff0c;剩下的每一层都有 k 个 节点。 现在小 w 每次可以取反第 i(1 < i < n − 1) 层和第 i 1 层之间的连边。也就是把原本从 (i,…

Linux中断子系统之Workqueue

背景说明Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 概述Workqueue工作队列是利用内核线程来异步执行工作任务的通用机制&#xff1b;Workqueue工作队列可以用作中断处理的Bott…

给你准备的Linux启动流程

读者朋友提问&#xff1a; 昨天在后台看到一个读者朋友跟我说&#xff0c;发哥&#xff0c;你能不能讲一下嵌入式Linux的开机流程&#xff0c;然后我看了下&#xff0c;我是没有写过这方面的文章&#xff0c;所以&#xff0c;就有了这篇文章。回答&#xff1a;我们都知道pc指针…

Linus Torvalds:我们都老了,但Linux维护者真的很难找

Linux 之父Linus Torvalds非常担忧没人继续维护内核「真的很难找到维护者&#xff01;」在本周召开的Linux开源峰会与嵌入式大会上&#xff0c; VMware的首席开放源代码官Dirk Hohndel和Linux的创建者Linus Torvalds再次就Linux开发展开了远程对话讨论。左&#xff1a;Dirk Ho…

看printk引发的一点思考

在源码位置kernel/printk/函数原型asmlinkage __visible int printk(const char *fmt, ...) {printk_func_t vprintk_func;va_list args;int r;va_start(args, fmt);/** If a caller overrides the per_cpu printk_func, then it needs* to disable preemption when calling pr…

我毕业时候写的简历

写简历这个事情&#xff0c;一直是一个非常让人头疼的&#xff0c;在我看来&#xff0c;写简历并不是一件简单的事情&#xff0c;所以&#xff0c;现在是晚上一点钟&#xff0c;我倒腾了一个晚上&#xff0c;才有了这篇文章。我认为写简历有几个需要注意的地方&#xff0c;不啰…

idea java 非法字符_解决IDEA显示非法字符 \ufeff 的问题

一、问题在txt文本中复制代码进入IDEA报错Error:(1, 1) java: 非法字符: ‘\ufeffError:(1, 10) java: 需要class, interface或enum二、解决办法用IDEA转换&#xff0c;先转换为GBK&#xff0c;再转回UTF-8()补充知识&#xff1a;Eurake问题Failed to bind properties under eu…

Linux 内核完成接口

Linux 内核里面有一个函数wait_for_completion&#xff0c;这是一个内核同步机制的函数&#xff0c;同步机制如果是早期的读者应该看过我发的文章&#xff0c;如果没有看过的可以看看Linux 专辑文章里面找找。既然是同步机制&#xff0c;主要的工作就是调用了这个函数&#xff…

关于测试

这是5月份和公司同仁做的分享&#xff0c;分享主题是关于测试&#xff0c;是我自己对于测试的一些认知&#xff0c;以及态度的转变。 目录 以怎样的心态面对测试 安全测试贯穿整个软件生命周期 总结 以怎样的心态面对测试 提问大家几个小问题&#xff1a; 你喜欢测试吗&#…

涨疯了,历史总是如此相似

2015年的股市&#xff0c;如果你经历过&#xff0c;那你一定会记忆犹新。最近的股市太猛了&#xff0c;写一篇文章纪念一下~mark上周五&#xff0c;在一个炎热的晚上&#xff0c;我跟4个股神在深圳坪洲的某个火锅店相遇&#xff0c;虽然他们都是富甲一方的富豪&#xff0c;虽然…

java基础语句_【Java基础-Java语言基础】

知识点&#xff1a;1.关键字  2.标识符  3.变量和常量一、关键字1.Java中的关键字有很多一共53个关键字有两个是保留字(java的关键字都是小写的&#xff01;&#xff01;)2.Java中的保留字(1)const  常量&#xff0c;数量(2)goto   转到3.Java的关键字(1)访问修饰符的关…

安卓9.0Sensor框架

前言本来如果只是给传感器写个驱动并提供能读取温湿度数据的节点&#xff0c;是一件比较轻松的事情&#xff0c;但是最近上层应用的同事要求我们按照安卓标准的流程来&#xff0c;这样他们就能通过注册一个服务直接读取传感器事件数据了。这样做的好处就是第三方的应用也能正常…

Theano 更多示例

Logistic函数 logistic函数的图&#xff0c;其中x在x轴上&#xff0c;s(x)在y轴上。 如果你想对双精度矩阵上的每个元素计算这个函数&#xff0c;这表示你想将这个函数应用到矩阵的每个元素上。 嗯&#xff0c;你是这样做的&#xff1a; xT.dmatrix(x) s1/(1T.exp(-x)) logisti…

SensorKernel层框架分析

接上文安卓9.0Sensor框架前言前面我们已经讲解了sensor框架中的framework到vendor层&#xff0c;这篇文章我们将会讲解kernel层的内容。不过不同的芯片平台&#xff0c;kernel层中的sensor框架是不同的&#xff0c;这里针对的是mt8167s平台。不过这里提醒一下&#xff0c;MTK平…