你的新进程是如何被内核调度执行到的?(下)

news2024/10/6 22:30:58

接上文你的新进程是如何被内核调度执行到的?(上)

四、新进程加入调度

进程在 copy_process 创建完毕后,通过调用 wake_up_new_task 将新进程加入到就绪队列中,等待调度器调度。

//file:kernel/fork.c
long do_fork(...)
{
 //复制一个 task_struct 出来
 struct task_struct *p;
 p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace);

 //子任务加入到就绪队列中去,等待调度器调度
 wake_up_new_task(p);
 ...
}

今天我们可以来展开看看 wake_up_new_task 执行时具体都发生了什么。新进程是如何加入到 CPU 运行队列 (struct rq)中的,我们来展开详细看看。

//file:kernel/sched/core.c
void wake_up_new_task(struct task_struct *p)
{
 //4.1 为进程选择一个合适的CPU
 set_task_cpu(p, select_task_rq(p, SD_BALANCE_FORK, 0));

 //4.2 将进程添加到活动进程集合
 rq = __task_rq_lock(p);
 activate_task(rq, p, 0);
 ...
}

wake_up_new_task 主要做了两件事,一是选择一个合适 CPU,二是将进程添加到所选的 CPU 的任务队列中。

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

4.1 选择合适的 CPU 运行队列

前面我们讲到,每个 CPU 核都有一个对应的运行队列 runqueue (struct rq)。所以新进程在加入调度前的第一件事就是选择一个合适的运行队列。然后使用该任务队列中,并等待调度。

要稍微注意的是,在 set_task_cpu(p, select_task_rq(p, SD_BALANCE_FORK, 0)) 这一行代码中包含对两个函数的调用。

  • select_task_rq 是选择一个合适的 CPU(运行队列)

  • set_task_cpu 是使用选择好的 CPU

在讲选择运行队列之前,我们先简单回顾一下 CPU 里的缓存。

CPU 同一个物理核上的两个逻辑核是共享一组 L1 和 L2 缓存的。整颗物理 CPU 上所有的核心共享同一组 L3。每一级 Cache 的访问耗时都差别非常大,L1 大约是 1 ns 多一些,L2 大约是 2 ns 多,L3 大约 4 - 8 ns。而内存耗时在最坏的随机 IO 情况下可以达到 30 多 ns。

了解了 CPU 的物理结构以及各级缓存的性能差异,你就大概能弄明白选择 CPU 的核心目的了。CPU 调度是在缓存性能和空闲核心两个点之间做权衡。同等条件下会尽量优先考虑缓存命中率,选择同 L1/L2 的核,其次会选择同一个物理 CPU 上的(共享 L3),最坏情况下去选择另外一个物理 CPU 上的核心。

选择运行队列 select_task_rq 这个函数有点复杂。但是理解了上面这个逻辑后,相信你理解起来就会容易很多。

//file:kernel/sched/core.c
static inline
int select_task_rq(struct task_struct *p, int sd_flags, int wake_flags)
{
 int cpu = p->sched_class->select_task_rq(p, sd_flags, wake_flags);
 ...
 return cpu;
}

在本文的第三节中我们提到了 fork 出来的新进程的 sched_class 使用的是公平调度器 fair_sched_class,回忆一下这个结构体的定义。

//file:kernel/sched/fair.c
const struct sched_class fair_sched_class = {
 ...
 .select_task_rq  = select_task_rq_fair,
 .migrate_task_rq = migrate_task_rq_fair,
 ...
}

所以上面的 p->sched_class->select_task_rq 这一句实际是进入到了 fair_sched_class 的 select_task_rq_fair 方法里,通过公平调度器实现的选择任务队列的来选择的。

//file:kernel/sched/fair.c
static int
select_task_rq_fair(struct task_struct *p, int sd_flag, int wake_flags)
{
 // 当前 CPU
 int cpu = smp_processor_id();

 // 上一次执行的 CPU
 int prev_cpu = task_cpu(p);

 // 快速路径选择
 new_cpu = select_idle_sibling(p, prev_cpu);
 goto unlock;

 // 慢速路径选择
 group = find_idlest_group(sd, p, cpu, load_idx);
 new_cpu = find_idlest_cpu(group, p, cpu);
 .......
}

为了方便你理解,我把 select_task_rq_fair 源码精简处理后,只留下了关键逻辑。这个函数一开始就获得了当前 CPU(创建新进程的进程所使用的 CPU)和上一次运行的 CPU(新进程暂时还没有)。接下来就是两个关键逻辑:一是快速路径选择,二是慢速路径选择。

在快速路径选择中,主要的策略就是考虑共享 cache 且 idle 的 CPU。优先选择任务上一次运行的CPU,其次是唤醒任务的 CPU。总之就是尽量考虑 cache 性能。

如果快速路径没选到,那就进入慢速路径。首选选出负载最小的组(find_idlest_group),然后再从该组中选出最空闲的 CPU(find_idlest_cpu)。

当进入到慢速路径以后,会导致进程下一次执行的时候跑的别的核、甚至是别的物理 CPU 上,这样以前跑热的 L1、L2、L3 就都失效了。用户进程过多地发生这种漂移会对性能造成影响。当然内核在极力地避免。如果你想强行干掉漂移,可以试试 taskset 命令。

至于 set_task_cpu 的逻辑比较简单,主要就是把选到的 CPU 设置到新创建出来的进程 task_struct 上。

 

4.2 将进程添加到活动进程集合

在选择完 CPU 后,下一步就是将新创建出来的进程添加到该 CPU 对应的运行队列 (struct rq) 中。

//file:kernel/sched/core.c
void wake_up_new_task(struct task_struct *p)
{
 //4.1 为进程选择一个合适的CPU
 set_task_cpu(p, select_task_rq(p, SD_BALANCE_FORK, 0));

 //4.2 将进程添加到活动进程集合
 rq = __task_rq_lock(p);
 activate_task(rq, p, 0);
 ...
}

经过 set_task_cpu 设置后,新进程taskstruct 指针 p 上已经记录了下一次要使用的 CPU 号。调用 __task_rq_lock 函数的作用就是将新进程 p 要使用的 CPU 的运行队列 struct rq 给找出来,并给它加个锁防止冲突。

接着调用 activate_task 将新进程添加到该 CPU 运行队列中去。

来查看其源码。

//file: kernel/sched/core.c
void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
 ......
 enqueue_task(rq, p, flags);
}

static void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
 ......
 p->sched_class->enqueue_task(rq, p, flags);
}

回忆完全公平调度器 fair_sched_class 对象。

//file:kernel/sched/fair.c
const struct sched_class fair_sched_class = {
 .next   = &idle_sched_class,
 .enqueue_task  = enqueue_task_fair,
 .dequeue_task  = dequeue_task_fair,
 ...
 .select_task_rq  = select_task_rq_fair,
 .migrate_task_rq = migrate_task_rq_fair,
 ...
}

可见 p->sched_class->enqueue_task 实际调用的是 enqueue_task_fair。经过 enqueue_task_fair => enqueue_entity ==> __enqueue_entity,最终插入到红黑树中等待调度。

//file:kernel/sched/fair.c
static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
 struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
 struct rb_node *parent = NULL;
 ...
 //插入到红黑树中
 rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}

五、调度时机

前面我们讲述的过程全部是新进程创建发生的事情,新进程还没有真正被调度。触发调度器开始选择进程并上 CPU 开始运行的时机有很多。我们就以咱们前面文章讲过的同步阻塞时机为例。

在同步阻塞网络编程模型下,如果 socket 上没有收到数据,或者收到不足够多,则调用 sk_wait_data 把当前进程阻塞掉,让出 CPU 并调度运行队列中的其它进程进行。

我们现在假设就有某一个进程发生了这样的阻塞。sk_wait_data 依次会调用 sk_wait_event、schedule_timeout,然后到达调度的核心函数 schedule。我们来看看它的核心实现 __schedule。

//file: kernel/sched/core.c
static void __sched __schedule(void)
{
 ......

 //取出当前 CPU 及其任务队列
 cpu = smp_processor_id();
 rq = cpu_rq(cpu);

 //5.1 获取下一个待执行任务
 next = pick_next_task(rq);

 //5.2 执行上下文切换到新进程上
 context_switch(rq, prev, next);
 ......
}

在这个函数中把当前 CPU 的任务队列取了出来,接着获取下一个待运行的任务,再执行上下文切换到新进程上来运行。接下来我们分两个小节单独来看下。

5.1 获取下一个待执行任务

是如何获取下一个待执行任务的呢?我们来看下 pick_next_task 的实现。

//file: kernel/sched/core.c
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
 p = fair_sched_class.pick_next_task(rq);
 ...
}

因为大部分都是普通进程,所以大概率会执行到 fair_sched_class.pick_next_task 函数中,也就是 pick_next_task_fair 函数中。该函数其实就是从当前任务队列的红黑树节点将运行虚拟时间最小的节点(最左侧的节点)选出来而已。

//file: kernel/sched/fair.c
static struct task_struct *pick_next_task_fair(struct rq *rq)
{
 //获取完全公平调取器
 struct cfs_rq *cfs_rq = &rq->cfs;

 //从完全公平调取器红黑树中选择一个进程
 se = pick_next_entity(cfs_rq);
 ...
}

这样,下一个待运行的进程就被选出来了。

5.2 执行上下文切换到新进程上

选出来待运行的新进程以后,接着就需要执行进程上下文切换,把新进程的运行状态给切换上来。

//file:kernel/sched/core.c
static inline void
context_switch(struct rq *rq, struct task_struct *prev,
     struct task_struct *next)
{
 //准备新旧两个进程的地址空间
 struct mm_struct *mm, *oldmm;
 mm = next->mm;
 oldmm = prev->active_mm;

 //执行地址空间切换
 switch_mm(oldmm, mm, next);

 //执行栈和寄存器切换
 switch_to(prev, next, prev);
 ......
}

当前进程上下文切换完成的时候,新进程终于可以得以运行了!

六、总结

好了,我们把今天的文章的内容总结一下。

一个进程从 fork 创建出来到最后真正能获得 CPU 并进行运行,中间有很多的内核逻辑需要处理,我把它分成了这么几个步骤供你更容易地理解。

第一,每个 CPU 核都有一个运行队列。为了支持不同的调度需求,运行队列是由实时调度器、完全公平调取器等多种调度器组成。

第一,是进程在 fork 的时候会选择自己的调取器,用户进程一般都是用完全公平调度器(fair_sched_class)。
第二,进程创建完前会综合考虑缓存友好性以及空闲状况,选择一个 CPU 运行队列出来,并将新进程添加到该队列中。

第三,内核有很多的时机来触发调度。我们文中举了同步阻塞网络 IO 放弃 CPU 时调取新进程运行的例子。在放弃 CPU 前会从当前 CPU 的运行队列获取一个进程出来,上下文切换后运行之。

我们再回到开篇的问题:

问题一:进程不主动释放 CPU 的话,每次调度最少能运行多久?
在完全公平调度器中,出于减少频繁切换进程所带来的成本考虑,一个进程一旦被分配到 CPU 就会持续运行相对较长的一段时间,避免频繁的进程上下文切换导致的性能损耗。这段时间的最小值由 sched_min_granularity_ns 这个内核参数来控制,单位是 ns (纳秒) 。例如下面这个配置的最短运行时间是 10 ms。

# sysctl -a | grep min_granularity
kernel.sched_min_granularity_ns = 10000000

当然了,如果进程因为等待网络、磁盘等资源时主动放弃 CPU 那另算。

问题二:进程的 nice 值代表的是优先级吗,高优先级是否能抢占低优先级的 CPU ?
在实时任务如 migration 内核线程中,是按优先级调度的。优先级强调的是抢占,高优先级比低优先级有优先获得 CPU 的权利。

但是对于用户进程来讲,一般都采用的完全公平调度器来进行 CPU 资源的分配。在这种调取器中,其 nice 其实是一个权重的概念。权重高的进程获得的 CPU 比例会相对高一些。但不是实时抢占。

 

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

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

相关文章

表白墙服务器版【交互接口、服务器端代码、前端代码、数据存入文件/数据库】

文章目录 一、准备工作二、约定前后端交互接口三、实现服务器端代码 四、调整前端页面代码五、数据存入文件六、数据存入数据库一、准备工作 1) 创建 maven 项目2) 创建必要的目录 webapp, WEB-INF, web.xml&#xff1b;web.xml如下&#xff1a;<!DOCTYPE web-app PUBLIC&qu…

家居行业如何实现智能化?快解析来助力

什么是智能家居&#xff1f;主要是指利用先进的电子通信技术&#xff0c;将居家生活有关的各个子系统有机结合在一起&#xff0c;通过网络化便可以对这些系统进行智能控制与管理。智能家居概念之所以逐渐普及&#xff0c;得益于物联网、大数据、人工智能等新兴技术的进步。智能…

科学计算模型 Numpy 详解

本文主要介绍Numpy&#xff0c;并试图对其进行一个详尽的介绍。 通过阅读本文&#xff0c;你可以&#xff1a; 了解什么是 Numpy掌握如何使 Numpy 操作数组&#xff0c;如创建数组、改变数组的维度、拼接和分隔数组等掌握 Numpy 的常用函数&#xff0c;如数组存取函数、加权平均…

表关联查询

表关联查询 1.表别名 当表的名字很长或者执行一些特殊查询时&#xff0c;为了方便操作或者需要多次使用相同的表时&#xff0c;可以为表指定别名&#xff0c;以替代表原来的名称。 在为表取别名时&#xff0c;要保证不能与数据库中的其他表的名称冲突。 对单表做简单的别名查询…

能否通过手机号查询他人位置及技术实现(省流:不能)

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;红中 &#x1fad2;每日emo&#xff1a;纪念我死去的爱情 &#x1f342;灵感来源&#xff1a;艺术源于生活&#xff0c…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.2 Spring 缓存使用方式

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.2 Spring 缓存使用方式5.2.1 Spring 缓存使用5.2.…

数字集成电路设计(五、仿真验证与 Testbench 编写)(二)

文章目录4. 信号时间赋值语句4.1 时间延迟的语法说明4.2 时间延迟的描述形式4.3 边沿触发事件4.3.1 事件表达式4.3.2 边沿触发语法格式4.4 电平敏感事件4. 信号时间赋值语句 &#xff01;&#xff01;信号赋值语句是硬件描述语言非常重要的一条语句&#xff0c;是对于任意信号…

Zookeeper:Zookeeper的主从选举机制

ZAB 协议&#xff0c;全称 Zookeeper Atomic Broadcast&#xff08;Zookeeper 原子广播协议&#xff09;&#xff0c;是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的一致性协议。基于该协议&#xff0c;ZooKeeper 实现了一种主从模式的系统架构来保持集群中各个副…

业务定制型异地多活架构设计

1个原理 大道至简-异地多活核心原理 异地多活本质上是 CAP 中的AP 大道至深-CAP 粒度 CAP 关注的粒度是数据&#xff0c;而不是系统&#xff0c;需要根据不同业务的数据特点来设计异地多活 延迟 CAP 是忽略网络延迟的 &#xff0c;但工程落地不可能做到零延迟 分区容忍…

【Linux】linux中你不得不爱的命令集(上)

Linux命令集 我们将要介绍的命令并不是linux中所有的命令&#xff0c;是我们常见的和经常要使用的命令。 我们所用的linux版本是centos7&#xff0c;我们的linux搭建是在腾讯云服务器上搭建的&#xff0c;借助Xshell登录服务器&#xff0c;在root下进行命令行的操作。 目录 L…

[附源码]java毕业设计社区生鲜仓库管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

异地多活架构的3种模式

业务定制型异地多活 按照业务的优先级进行排序&#xff0c;优先保证核心业务异地多活 基于核心业务的流程和数据&#xff0c;设计定制化的异地多活架构 优点 对基础设施无强要求&#xff0c;例如机房部署、存储系统、时延等&#xff0c;一般部署在远距离的两个城市&#xff…

经济师报考专业选择及难度分析!这三个专业每年报考人数超10万!

经济师 经济师考试报考专业设有10个专业&#xff0c;含工商管理、农业经济、财政税收、金融、保险、运输经济、人力资源管理、旅游经济、建筑与房地产经济、知识产权。那么&#xff0c;哪些专业是经济师报考的热门专业&#xff1f;哪些专业前景较好&#xff1f;哪个又更好考呢…

SAP 物料分类账配置详解Part 2( 基于SAP S/4HANA1909 版本)

1.12 检查物料会计科目的结算 1.13 激活在制品实际成本计算 1.14 定义并分配评估策略 1.15 定义实际成本核算/物料分类帐的访问 1.16 分配成本核算码到物料类型 1.17 将评估范围设置为生产 1.12 检查物料会计科目的结算 1.12.1 概念说明 为物料分类账的结账配置自动…

C++模拟OpenGL库——图片处理及纹理系统(二):图片Alpha值混合操作

目录 Alpha值混合操作 更改一些类接口设置&#xff0c;实现Alpha值设定 Alpha值混合操作 先上图&#xff0c;其实原理和ColorLerp的原理一样&#xff0c;一种线性插值的方法来实现Alpha通道的混合。 Alpha通道就是对RGB三个值的一种表现约束&#xff0c;比如Alpha0.5&#x…

使用keytool生成Tomcat证书

一、HTTPS原理 1、HTTP、HTTPS、SSL、TLS介绍与相互关系 &#xff08;1&#xff09;HTTP&#xff1a;平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的&#xff08;明文&#xff09;&#xff0c;因此使用HTTP协议传输隐私信息非常不安全。 &#xff08;2&am…

人工智能学习相关笔记

文章目录留出法(hold-out)Artifact (error)理解交叉熵损失函数(CrossEntropy Loss)信息量信息熵相对熵(KL散度)交叉熵交叉熵在单分类问题中的应用回顾知识蒸馏公式对抗学习随机投影(Random Projection)概述基本实现sklearn中的随机投影独立成分分析(ICA)ICA算法ICA 应用sklearn…

tslib库编译与移植

tslib库编译与移植 1.tslib库简介 tslib 是电阻式触摸屏用于校准的一个软件库&#xff0c;是一个开源的程序&#xff0c;能够为触摸屏驱动获得的采样提供诸如滤波、去抖、校准等功能&#xff0c;通常作为触摸屏驱动的适配层&#xff0c;为上层的应用提供了一个统一的接口。 2…

数据结构——顺序表

目录 一.简介 线性表 顺序表 二.结构体与初始化 1.创建 2.初始化 三.功能实现 1.打印 2.销毁 3.扩容 4.尾插 5.尾删 6.头插 7.头删 8.查找元素 9.下标位置的插入与某一数据前的插入 10.下标位置的删除与某一数据的删除 11.头插、头删、尾插、尾删的常态化 一.简…

模块电路选型(1)----电源模块

系列文章目录 1.电源模块 2.主控模块 3.传感器模块 4.通信模块 5.电机驱动模块 6.存储模块 7.人机交互模块 文章目录前言一、DCDC电源模块1、LM2596 DCDC降压模块设计二、LDO电源模块1、1117芯片前言 送给大学毕业后找不到奋斗方向的你&#xff08;每周不定时更新&#x…