Linux的进程调度实现

        经常被问到进程的调度算法有哪些,什么先进先出、短进程优先、时间片轮转、多级反馈多列等等算法能说一大堆?那具体的,linux内核使用了什么样的算法,且来探究一下。

        本文所引用源码基于linux内核2.6.34版本。

目录

调度器类

从 schedule() 开始

pick_next_task()

三种调度器类 

完全公平调度算法

时间记账

公平的pick_next_task()

总结


调度器类

        首先要明确的是,linux并不仅仅使用一种调度算法,而是多种。具体的,内核中以调度器类的方式提供多种调度算法。这样做的目的是为了让不同类型的进程有选择性的使用不同的调度算法。

从 schedule() 开始

        schedule() 是内核调度的入口函数。例如当一个进程调用read之类的阻塞方法而数据没有就绪时,内核就会通过 schedule() 函数触发调度,将自己挂到等待队列中,寻找一个新进程去运行。schedule() 的实现在 kernel/sched.c 文件中,它会停止当前正在运行的进程,并找到下一个待运行的进程去调度执行。

void __sched schedule(void)
{// 清除当前进程的重调度标识clear_tsk_need_resched(prev);// 将旧进程从运行队列中移除if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {if (unlikely(signal_pending_state(prev->state, prev)))prev->state = TASK_RUNNING;elsedeactivate_task(rq, prev, 1);switch_count = &prev->nvcsw;}// 调度前的处理钩子pre_schedule(rq, prev);// 将当前进程放回运行or等待队列,并选择下一个要运行的进程put_prev_task(rq, prev);next = pick_next_task(rq);// 执行上下文切换及更新统计信息if (likely(prev != next)) {sched_info_switch(prev, next);perf_event_task_sched_out(prev, next);rq->nr_switches++;rq->curr = next;++*switch_count;context_switch(rq, prev, next); /* unlocks the rq */} elseraw_spin_unlock_irq(&rq->lock);// 调度后的处理钩子post_schedule(rq);
}

        schedule() 虽然函数体比较长,但其做的事情比较简单,最重要的事情就是 pick_next_task() 和 context_switch() 啦。我们需要具体看一下它是怎么pick next的~

pick_next_task()

        pick_next_task() 会根据优先级,从高到低依次检查每一个调度器类,并且从最高优先级的一个调度器类中,选择最高优先级的一个进程出来。

/** 选择优先级最高的task*/
static inline struct task_struct *
pick_next_task(struct rq *rq)
{const struct sched_class *class;struct task_struct *p;/** 优化:如果所有的运行态的任务都属于cfs调度器的运行队列* 那么这里就可以直接调用cfs调度器的方法*/if (likely(rq->nr_running == rq->cfs.nr_running)) {// fair_sched_class:cfs调度器p = fair_sched_class.pick_next_task(rq);if (likely(p))return p;}// sched_class_highest:取最高优先级的调度器类class = sched_class_highest;for ( ; ; ) {p = class->pick_next_task(rq);if (p)return p;/** 永远不会走到这里,因为idle调度器总会返回一个task*/returns a non-NULL p:class = class->next;}
}

        调度器类 sched_class 的定义如下,其中定义了一个指向下一优先级调度器类的一个指针,以及多个函数指针,分别用以执行不同的操作。这也是kernel中使用面向对象思想的体现。

struct sched_class {// 指向下一个sched_class的指针,用于支持调度类的层次结构const struct sched_class *next;// 将任务(进程或线程)添加到运行队列void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup,bool head);// 从运行队列中移除任务void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);// 使当前任务放弃CPU,让其他任务有机会运行void (*yield_task) (struct rq *rq);// 检查当前任务是否应该被抢占(即被其他任务中断)void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);// 从运行队列中选择下一个要运行的任务struct task_struct * (*pick_next_task) (struct rq *rq);// 将前一个任务(即刚刚被抢占或完成的任务)放回运行队列void (*put_prev_task) (struct rq *rq, struct task_struct *p);// 设置当前任务为运行队列的当前任务void (*set_curr_task) (struct rq *rq);...
};

三种调度器类 

     根据 next 指针的指向,很容易找到 kernel 支持的几种调度器类:

  1. rt_sched_class:实时调度器类,优先级最高:分为RR算法、FIFO算法。RR为一种基于时间片的FIFO算法,按入队顺序先进先出的执行。FIFO即为简单的先进先出算法。
  2. fair_sched_class:完全公平(cfs)调度器类,完全公平调度算法。
  3. idle_sched_class:空闲调度器类,优先级最低,只有当系统中没有其它优先级更高的任务时,才会调度到。

        根据调度器器的种类,也就能看出,从调度的角度来讲,kernel 将进程分为三种类型,分别为实时进程、普通进程以及空闲进程。通过 ps 命令可以查看 linux 环境中不同进程的优先级及采用的调度算法:

LC0:~$ ps -eo pid,cls,sched,pri,comm | grep "FF\|RR\|IDL"16  FF   1 139 migration/017  FF   1  90 idle_inject/039  FF   1  90 watchdogd1776 IDL   5   0 tracker-miner-f
LC0:~$ ps -eo pid,cls,sched,pri,comm | grep "TS" | head -n11  TS   0  19 systemd
LC0:~$ 

        其中 FF 为实时调度算法中的FIFO;TS 表示 "Time Sharing",即为时间共享调度策略,也即 CFS 完全公平调度算法,旨在公平地分配CPU时间给所有进程;IDL 即空闲调度策略。由于 IDL 调度器只是用来在无可运行进程调度时使用,所以其只管理一个 idle 进程,并没有使用到运行队列。

完全公平调度算法

        CFS 调度算法的设计基于一个简单的理念而来:进程调度的效果应该如同系统具备一个理想中的完美多任务处理器。在这种系统中,每个task均能公平的获得 (1/可运行进程数) 的处理器时间比。

        理论上,如果能把时间片分隔为无限小,那么就可以做到平均分配给每个进程相同的运行时间,也就能做到完全公平。然而实际上,一方面cpu的时钟周期不是无限小的,并且任务的切换是有代价的,时间片粒度细到一定程度,CPU 只能忙于切换进程的上下文,而无暇执行实际的任务了。

        现实如何?CFS 充分考虑了这种切换带来的开销,允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程。这里的运行最少也不是简单的根据时间片来计算了,而是一个时间片的加权值:处理器时间比。nice 值在 CFS 中被作为进程获得处理器运行比的权重:nice值越高,进程可获得的处理器时间比也就越小,反之。

时间记账

        即使 CFS 不是按时间片,而是按时间比来调度进程,但其仍然必需维护每个进程运行的时间记账,并据此确保每个进程只在分配给它的处理器时间内运行。

        在 linux/sched.h 中定义了时间记账的结构体——调度实体:

struct sched_entity {// 实体的负载权重,用于调度决策struct load_weight	load;// 红黑树节点,用于快速查找和插入调度实体struct rb_node		run_node;// 链表节点,将实体组织成链表struct list_head	group_node;// 指示实体是否在调度队列上unsigned int		on_rq;// 记录实体开始执行的时间u64			exec_start;// 记录实体执行的总时间u64			sum_exec_runtime;// 虚拟运行时间,即加权后的执行时间u64			vruntime;// 上一次计算时的 sum_exec_runtime 值u64			prev_sum_exec_runtime;// 实体最后一次被唤醒的时间u64			last_wakeup;...
};

        调度实体 sched_entity 这个结构的指针,存放在 task_strcut 中。sched_entity比较重要的变量则是 vruntime,它表示进程执行的加权后的时间,CFS 用这个 vruntime 帮助实现逼近理想多任务处理器的完美公平调度。

        记账功能通过定时器定时调用 update_curr() 实现:

static void update_curr(struct cfs_rq *cfs_rq)
{// 获得最后一次修改load后当前任务所占用的运行总时间delta_exec = (unsigned long)(now - curr->exec_start);__update_curr(cfs_rq, curr, delta_exec);curr->exec_start = now;if (entity_is_task(curr)) {struct task_struct *curtask = task_of(curr);trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);cpuacct_charge(curtask, delta_exec);account_group_exec_runtime(curtask, delta_exec);}
}

        update_curr() 计算出当前进程的运行时间,保存到了 delta_exec 变量中。然后调用了 __update_curr() 。__update_curr() 根据当前可运行进程总数对运行时间进行加权计算,结果累加到 vruntime 中。

static inline void
__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,unsigned long delta_exec)
{curr->sum_exec_runtime += delta_exec;delta_exec_weighted = calc_delta_fair(delta_exec, curr);curr->vruntime += delta_exec_weighted;...
}static inline unsigned long
calc_delta_fair(unsigned long delta, struct sched_entity *se)
{if (unlikely(se->load.weight != NICE_0_LOAD))delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);return delta;
}// delta *= weight / lw
static unsigned long
calc_delta_mine(unsigned long delta_exec, unsigned long weight,struct load_weight *lw)
{// 近似计算出 lw->weight的倒数 + 1if (!lw->inv_weight) {if (BITS_PER_LONG > 32 && unlikely(lw->weight >= WMULT_CONST))lw->inv_weight = 1;elselw->inv_weight = 1 + (WMULT_CONST-lw->weight/2)/ (lw->weight+1);}tmp = (u64)delta_exec * weight;// SRR为对tmp低32位四舍五入并只保留tmp的高32位tmp = SRR(tmp * lw->inv_weight, WMULT_SHIFT);return (unsigned long)min(tmp, (u64)(unsigned long)LONG_MAX);
}

公平的pick_next_task()

        CFS 会挑选一个 vruntime 最小的进程来作为下一个运行的进程,它通过红黑树来组织可运行进程队列,并利用其快速查找最小 vruntime 的节点。pick_next_task_fair() 是 CFS 调度器注册到调度器类中的 pick_next_task() 钩子函数。

static struct task_struct *pick_next_task_fair(struct rq *rq)
{do {se = pick_next_entity(cfs_rq);set_next_entity(cfs_rq, se);cfs_rq = group_cfs_rq(se);} while (cfs_rq);p = task_of(se);return p;
}// pick_next_entity中调用__pick_next_entity
static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)
{struct rb_node *left = cfs_rq->rb_leftmost;if (!left)return NULL;return rb_entry(left, struct sched_entity, run_node);
}

        最小 vruntime 的节点,其实就是对应红黑树中最左侧的那个叶子节点。__pick_next_entity找到这个最左节点并返回了调度实体,pick_next_task_fair() 中通过 task_of() 找到来调度实体所属的 task_struct。

总结

        linux实现了三种不同的调度器类:实时调度器、完全公平调度器、空闲调度器,分别对应实时进程、普通进程、空闲进程。实际环境中大多数进程都是普通进程,即采用完全公平调度算法。完全公平调度算法调度程序取决于运行程序消耗了多少处理器使用比,如果消耗的使用比比当前进程小,那么新进程会立刻投入运行,抢占当前进程。

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

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

相关文章

探索 PostgreSQL 的高级数据类型 - 第 1 部分

数组和枚举 PostgreSQL 因其可扩展性和多功能性而备受欢迎,除了传统的整数和字符串之外,它还提供了多种数据类型。其中,包括数组和枚举,其为开发者提供了高级的数据建模能力。本文中,我们将深入研究这些复杂的数据类型…

Unity中PICO实现 隔空取物 和 接触抓取物体

文章目录 前言一、隔空取物1、XR Grab Interactable2、调节扔出去时的相关系数3、用手柄射线指向需要抓取的物体后,按下侧边扳机键即可抓取 二、接触抓取物体1、替换手柄上抓取物体的脚本2、在手柄上添加 接触抓取物体的脚本3、在手柄上添加碰撞盒触发器4、在需要抓…

PHAMB: 病毒数据分箱

Genome binning of viral entities from bulk metagenomics data | Nature Communications 安装 ### New dependencies *Recommended* conda install -c conda-forge mamba mamba create -n phamb python3.9 conda activate phamb mamba install -c conda-forge -c biocond…

IOS降级后从高版本到低版本恢复备份

IOS降级后从高版本到低版本恢复备份 此方法只适用于小版本还原,比如17.4->17.3,未验证大版本恢复可行性手机型号:iphone 13pro 系统版本:17.4 降级版本:17.3.1 步骤 通过itunes或者MacOS系统下对当前版本进行备份…

基于ThinkPHP框架的校园一卡通系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 框架技术 3 1.1.1 Bootstrap 3 1.1.2 ThinkPHP框架 3 1.2 前端技术 4 1.2.1 JavaScript 4 1.2.2 ECharts 4 1.3 B/S架构 4 1.4 数据库技术 5 1.4.1 MySQL 5 1.5 本章小结 6 2 系统分析 7 2.1 功能需求分析 7 2.2 非功能需…

202441读书笔记|《笠翁对韵》—— 金菡萏,玉芙蓉,酒晕微酡琼杏颊,香尘浅印玉莲双

202441读书笔记|《笠翁对韵》——金菡萏,玉芙蓉,酒晕微酡琼杏颊,香尘浅印玉莲双 《作家榜名著:笠翁对韵》作者李渔,霍俊明。是所有词句都有注音的一本书,轻松学不认识的字,非常朗朗上口的对偶词…

PromptBreeder---针对特定领域演化和发展提示词的方法

原文地址:promptbreeder-evolves-adapts-prompts-for-a-given-domain 论文地址:https://arxiv.org/pdf/2309.16797.pdf 2023 年 10 月 6 日 提示方法分为两大类 硬提示是由人工精心设计的文本提示,包含离散的输入令牌;其缺点…

【Linux】gcc与make、makefile

文章目录 1 gcc/g1.1 预处理1.2 编译1.3 汇编1.4 链接1.4.1 静态链接1.4.2 动态链接 2 make和makefile2.1 依赖关系2.2 依赖方法2.3 伪目标 3 总结 1 gcc/g 当我们创建一个文件,并向里面写入代码,此时,我们该如何使我们的代码能够运行起来呢&…

html--心花怒放

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Canvas 绘制一个❤</title><link rel"shortcut icon" href"../../assets/images/icon/favicon.ico" type"ima…

C#实现快速排序算法

C#实现快速排序算法 以下是C#中的快速排序算法实现示例&#xff1a; using System;class QuickSort {// 快速排序入口函数public static void Sort(int[] array){QuickSortRecursive(array, 0, array.Length - 1);}// 递归函数实现快速排序private static void QuickSortRecu…

ubuntu自带屏幕截图功能

目录 简介开始截屏步骤1.打开截屏软件2.选择区域3.截图 快捷键 录屏方法11.开始录屏2.停止录屏 方法2 补充说明 简介 试了好多开源跨平台截图软件&#xff0c;但是在ubuntu上都或多或少存在问题。ubuntu有自带的截图软件。打算把ubuntu自带的截图软件用起来。 顺便说一下我使…

B端系统升级,登录页必在升级之列,不容置疑。

进行B端界面升级时&#xff0c;首先升级登录页有以下几个原因&#xff1a; 用户体验&#xff1a;登录页是用户进入系统的第一个页面&#xff0c;用户首先接触到的界面。通过升级登录页&#xff0c;可以提升用户的第一印象&#xff0c;增强用户对系统的信任感和好感度&#xff…

Android Studio编译及调试知识

文章目录 Android Studio编译kotlin项目Android Studio编译Java和kotlin混合项目的过程gradle打印详细错误信息&#xff0c;类似这种工具的使用Android apk 从你的代码到APK打包的过程&#xff0c;APK安装到你的Android手机上的过程&#xff0c;最后安装好的形态&#xff0c;以…

简单形状点云轮廓点排序(旋转角)

1、背景介绍 很多边缘提取算法提取的边缘点为无序点云&#xff0c;如下图所示&#xff0c;无序点云不利于后续各种应用&#xff0c;比如根据边缘计算点云面积、点云轮廓线规则化等。若对点云进行排序&#xff0c;则可以进行上述引用。但实际上&#xff0c;点云形状错综复杂&…

Dubbo 和 Zookeeper 的关系

Dubbo 和 Zookeeper 的关系 Zookeeper的作用 zookeeper用来注册服务和进行负载均衡&#xff0c;哪一个服务由哪一个机器来提供必需让调用者知道&#xff0c;简 单来说就是ip地址和服务名称的对应关系。当然也可以通过硬编码的方式把这种对应关系在调用方 业务代码中实现&#…

AVL树讲解

AVL树 1. 概念2. AVL节点的定义3. AVL树插入3.1 旋转 4.AVL树的验证 1. 概念 AVL树是一种自平衡二叉搜索树。它的每个节点的左子树和右子树的高度差&#xff08;平衡因子&#xff0c;我们这里按右子树高度减左子树高度&#xff09;的绝对值不超过1。AVL的左子树和右子树都是AV…

MIT6.5840(6.824)Lab2总结(Raft)

MIT6.5840&#xff08;原MIT6.824&#xff09;Lab2总结&#xff08;Raft&#xff09; 资源分享&#xff1a; 官网地址&#xff1a;http://nil.csail.mit.edu/6.5840/2023/ Raft论文地址&#xff1a;http://nil.csail.mit.edu/6.5840/2023/papers/raft-extended.pdf 官方学生…

Web Servlet

目录 1 简介2 创建Servlet项目并成功发布运行3 新加Servlet步骤4 Servlet项目练习5 Servlet运行原理6 操作 HTTP Request头的方法(部分方法示例)7 操作 HTTP Response头的方法(部分方法示例)8 两种重定向(页面跳转)方法9 Cookie9.1 Cookie工作原理9.2 cookie构成9.3 Servlet 操…

axios的详细使用

目录 axios&#xff1a;现代前端开发的HTTP客户端王者 一、axios简介 二、axios的基本用法 1. 安装axios 2. 发起GET请求 3. 发起POST请求 三、axios的高级特性 1. 拦截器 2. 取消请求 3. 自动转换JSON数据 四、axios在前端开发中的应用 五、总结 axios&#xff1a…

【JS】判断是否安装了某个Chrome插件

前提 manifest.json 清单 下文均以manifest.json v3介绍。 因为Chrome官方文档中明确说明&#xff0c;v2已经弃用了。 ID 由于浏览器的安全策略&#xff0c;以下方法均在「已知扩展程序 ID」 的前提下才可实现。 获取扩展程序ID 进入扩展程序管理页&#xff0c;找到对应插…