技术标签: 内核 linux内核 linux 任务 调度
4) 检查新任务是否需要抢占当前任务,如果条件具备则将当前任务(rq->curr)调度出去;
5) 调用task_woken_rt()对当前cpu进行均衡。
这些事情是在 wake_up_new_task()中来完成的。因此,出于亲和性以及负载均衡等等原因需要检查这个默认cpu是否适合新任务运行;如果不适合,就需要为其重新找一个运行cpu,这是通过函数select_task_rq(struct task_struct *p, int cpu, int sd_flags, int wake_flags)来实现的。
调用的流程为:
wake_up_new_task(p)
| set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
如果2.1中4个条件都依次满足,则需要通过find_lowest_rq(struct task_struct *task)函数为新任务task选择一个新的合适的运行cpu,调用链为:
wake_up_new_task(p)
| set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
| find_lowest_rq(struct task_struct *task)
这个查找选择的流程如下所示:
1) 先从task_rq(task)->rd->cpupri中寻找优先级最低的cpu集合,并求出此 最低优先级cpu集合 与task->cpus_allowed交集的集合,放到lowest_mask中,后续都优先在此集合中进行选择;在通过select_task_rq()为新任务选择了cpu后,就可以通过set_task_cpu(struct task_struct *p, unsigned int new_cpu)函数重新为新任务设置运行cpu和就绪队列rq,调用流程为:
wake_up_new_task(p)
| set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
实际上指定cpu的意义并不仅仅在于设置任务的thread_info->cpu,更重要的是:为任务指定到此cpu的运行队列。 在引入组调度之后,调度器的组织呈现层级结构,每一个任务组在各个cpu上都有对应的调度队列。而且调度队列上挂着的不再只是任务,而是抽象出来的调度实体:它既可以表示一个任务也可以表示一个组。对于实时任务,对应的调度实体为:struct sched_rt_entity。
一个实时任务用 task_struct.rt 成员来表示对应的调度实体sched_rt_entity;每个实时调度实体又根据优先级挂到优先级队列rt_rq.queue[prio]上。
其中调度实体挂接到所属的运行队列rt_rq是sched_rt_entity.rt_rq来结构类型;同时,由于加入了组调度,调度实体还可能有自己所属的上层实体parent (注意:这个parent与我们将进程创建时的父进程不是一个领域也不是一个概念),这个是用sched_rt_entity.parent来表示的。
关于实时任务中的调度实体、运行队列、优先级数组等等相关组织结构可以参考《RT任务调度概览》中的示意图。
而set_task_cpu()的主要任务就是为新任务p指定自己的运行队列和上层调度实体parent。
在2.2节找到新任务的运行cpu后,就需要设置新任务所属的运行队列和所属的parent实体等等信息了。 wake_up_new_task(p)
| set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
| activate_task(rq, p, 0);
新任务p加入到运行队列以等待调度就是由activate_task()来实现的。在探讨activate_task()函数如何把任务加入运行队列之前,我们来看下一个rt_rq的组织结构,以明确我们的新任务该往哪里放,该怎么放,放进去了会有什么影响。
/* Real-Time classes' related field in a runqueue: */
struct rt_rq {
struct rt_prio_array active; /* 我们的调度实体最终是放到优先级数组active中 */
unsigned int rt_nr_running;
#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
struct {
int curr; /* highest queued rt task prio */
#ifdef CONFIG_SMP
int next; /* next highest */
#endif
} highest_prio;
#endif
......
struct rq *rq;
struct task_group *tg;
#endif
};
这里展示了新任务相关的rt_rq结构成员,新任务最终的归属在优先级数组rt_rq.active成员中,其结构如下所示:
/*
* This is the priority-queue data structure of the RT scheduling class:
*/
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
struct list_head queue[MAX_RT_PRIO];
};
可以看到优先级数组结构有两个成员,一个是位图bitmap,一个是list_head数组。
activate_task(rq, p, 0)
| enqueue_task(rq, p, flags)
| update_rq_clock(rq);
| enqueue_task_rt(rq, p, flags)
| enqueue_rt_entity(rt_se, flags & ENQUEUE_HEAD)
| enqueue_pushable_task(rq, p)
我们已经知道现在的调度器中实际上是将调度组和任务抽象成了调度实体,所以任务入队,也就是调度实体rt_se入队的主要工作是由函数 enqueue_rt_entity(rt_se, flags & ENQUEUE_HEAD) 来完成。
同时,由于组调度的层级关系,一个调度实体的优先级和调度信息都依赖于其子层级的调度实体和task。例如一个组的优先级就是其下属的优先级最高的那个task。因此实时调度中一个调度实体的“入队”、“出队”可以说是牵一发而动全身,其parent,parent的parent.....其所有的祖先都需要随之发生动荡。
因此新任务加入到运行队列这个过程是相当复杂的。
整个流程如下所示:
1) 将rt_se及其所有祖先的各个调度实体从上到下层(top-rt_se到该rt_se)依次出队;
1.1) 将rt_se的各个层级祖先从上往下组织起来;
1.2) 更新rq->nr_running -= rt_rq->rt_nr_running;
1.3) 从rt_se的顶层祖先开始,从上到下调用__dequeue_rt_entity(rt_se)将各个rt_se出队。
1.3.1) 将rt_se从其所在的优先级队列出队并更新优先级位图;
1.3.2) rt_se出队后需要更新相关信息
1.3.2.1) 更新rt_se->rt_rq的rt_nr_running,即减去rt_se中的rt_nr_running;
1.3.2.2) dec_rt_prio(rt_rq, rt_se_prio(rt_se)) 更新rt_rq的优先级
1.3.2.2.1) 更新rt_rq->highest_prio.curr;
1.3.2.2.2) 如果需要出队的rt_se所在的rt_rq是top rt_rq且top rt_rq的优先级已经改变(可能是在2.1得到更新),则更新rq所在cpu的 cpu priority。
1.3.2.3) 调用dec_rt_migration()更新过载和迁移信息;
1.3.2.3.1) 如果rt_se是一个task,则更新rt_rq->rt_nr_total 、rt_rq->rt_nr_migratory;
1.3.2.3.2) 根据前面更新的rt_nr_total和rt_nr_migratory再更新rt_rq->overload 和 rq->rd->rto_mask;
1.3.2.4) 如果rt_se是boosted的(rt_se或者rt_se->my_q上有优先级翻转的任务),则需要更新rt_rq->rt_nr_boosted。
2) 将rt_se及其所有祖先的各个调度实体从下到上层(从该rt_se到top-rt_se)依次入队;
2.1) 如果rt_se不是任务而是一个组且 (调度受到限制或者组内没有可运行的实体)则返回;
2.2) 否则将rt_se挂到对应优先级队列queue的尾部,并设置rt_se优先级对应的位图;
2.3) rt_se入队后需要更新相关信息,如所属rt_rq的rt_nr_running、rt_rq的优先级、cpu优先级、过载和迁移信息以及rt_nr_boosted等等,参考__dequeue_rt_entity。
3) 调用enqueue_top_rt_rq(&rq->rt)将top rt_rq入队
3.1) 如果top rt_rq调度受限(rt_throttled标志置位 且 rt_rq中没有优先级翻转的task) 或者 top rt_rq没有就绪的实体则直接返回;
3.2) 否则将top rt_rq的rt_nr_running统计到rq->nr_running中,并置rt_rq->rt_queued。
最后,如果新加入到运行队列的任务不是正在运行的,即不是rq->curr且新任务没有亲和性限制,则将新任务加入到pushable链表中并更新就绪队列上的highest prio pushable task。
一旦新任务加入到了运行队列就有可能比当前任务更有运行资格,这个时候就需要检查是否需要将当前任务rq->curr调度出去,这是调用check_preempt_curr(rq, p, WF_FORK)来实现的。
wake_up_new_task(p)
| set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
| activate_task(rq, p, 0);
| check_preempt_curr(rq, p, WF_FORK);
实际上它完成的主要工作是检查:
1) 如果新任务p->sched_class与当前任务rq->curr->sched_class相等,则调用sched_class->check_preempt_curr(rq, p, flags)检查rq->curr是否需要调度出去;
2) 否则判断新任务p的调度类是否比rq->curr调度类优先级“高”,如果高则将rq上的当前任务调度出去。
由于我们这里讨论的是实时任务,而实时调度类比系统中的大部分任务优先级要“高”,因而当前任务很有可能被调度出去。
然而如果rq->curr任务也是实时调度类,则需要通过check_preempt_curr_rt()函数来进行检查:
1) 如果rq->curr优先级小于新任务p的优先级,则当前任务rq->curr被调度出去;
2) 如果rq->curr优先级与新任务p的优先级相等:
2.1) 若rq->curr不可迁移只能期望将p迁移出去而不将rq->curr调度出去;
2.2) 否则,若新任务p可以迁移那么就期望将p迁移出去,而不将rq->curr调度出去;
2.3) 否则,就是rq->curr可迁移而新任务不可迁移的情况,此时就将rq->curr调度出去,使其有机会到其他cpu的rq上运行。
这里的调度类优先级“高”、“低”是什么意思呢?我们知道现在的调度器有许多不同的调度类,如我们熟悉的实时调度rt_sched_class、公平调度fair_sched_class等等。这些调度类通过一个next指针组织成一个链表,链表越靠前面的调度类越容易被先被cpu"垂青",这就是我这里所说的优先级“高”、“低”。系统中所有的调度类简单组织情况如下:
【stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class】
其中stop_sched_class没有真正参与调度;dl_sched_class属于dead_line调度类,这类任务没有优先级概念,是系统中最紧迫的任务(这个还没有研究过)。而我们平时关注任务一般都是是rt和fair调度类。
由于我们先加了一个rt任务到当前cpu上,这个很可能引发此cpu上的rt任务过载而打乱了cpu上的负载均衡。这个时候就需要调用task_woken_rt(rq, p)来进行检查,以确定是否需要对当前的cpu的rq就绪队列执行push操作,即将当前cpu上的实时任务“推”到其他空闲的cpu上,以达到cpu负载均衡。关于实时任务负载均衡的问题后续再讨论,这里我们重点关注task_woken_rt(rq,p)在满足什么条件才会执行push。
1) 新任务p的p->on_cpu为0 &&
2) 当前任务rq->curr没有设置need reschedule &&
3) 新任务p没有cpu允许迁移 &&
4) rq->curr是dead_line或者rt类型的任务 &&
5) rq->curr不许迁移或者rq->curr优先级高于等于p的优先级。
只有同时满足上面5个条件才会执行push_rt_tasks(rq),以尝试将本cpu的rq中的任务“推”给其他cpu。
_do_fork()
| copy_process()
| wake_up_new_task()
| select_task_rq()
| set_task_cpu()
| activate_task()
| enqueue_task_rt()
| check_preempt_curr()
| task_woken_rt()
其中工作量最大的就是activate_task(),因为实时任务的入队流程是“牵一发而动全身”。当然,由于水平有限,许多小细节都没有分析的明白,还有待加强。有任何建议或者意见都欢迎交流、指正和讨论。
文章浏览阅读1.2w次,点赞7次,收藏76次。MQ全称是MessageQueue(消息队列),是保存消息在传输过程中的一种容器,既是存储消息的一种中间件。多是应用在分布式系统中进行通信的第三方中间件,如下图所示,发送方成为生产者,接收方称为消费者。............_mq
文章浏览阅读1.5k次,点赞47次,收藏18次。Bug分析是QA的一项主要技能,需要针对项目中遇到的经典问题进行分类分析, 直达问题本质。 并且能够给团队其他项目或者成员起到典型的借鉴作用。 当然也有一些非常经典的问题可以进行技术深挖, 以供参考。 个人认为比较典型的「Bug分析」是stackoverflow, 当然, 一个完善的bug分析库, 可以进行问题分类总结。 对于测试新人是有很大的帮助的。本质上, 在测试领域很多问题是可重现可整理可规避的。另外, bug分析本身是为了拓宽每个人的认知边界, 缩小团队间的乔哈里窗以达到最佳的合作状态。一个「好的B
文章浏览阅读800次。HQST导读:PULSE普思是网络通讯行业中龙头企业之一,其中网络变压器产品大都由国内代工厂代为生产,H5020NLHX5020NL千兆四口网络变压器是普思公司经典老牌产品,相对整个市场用量不是很大,集中生产约一月20万颗左右……PULSE普思是网络通讯行业中龙头企业之一,其中网络变压器产品大都由国内代工厂代为生产,H5020NLHX5020NL千兆四口网络变压器是普思公司经典老牌产品,相对整个市场用量不是很大,集中生产约一月20万颗左右,……PULSE H5020NL千兆网络变压器对应HQS._4口网络变压器
文章浏览阅读242次,点赞3次,收藏9次。交换机,壳体采用镀锌钢板,结构紧凑,支持八个百兆端口,可配置一至四个百兆光纤端口。两路冗余电源设计,支持4pin可插拔端子,交直流通用,同时提供电源防接保护及过压、欠压保护,极大提升产品工作的稳定性。2.支持两路冗余电源设计,4pin可插拔端子,支持12~36V宽电压输入,交直流通用,同时提供电源防反接保护及过压、欠压保护,极大提升产品工作的稳定性。4.-40℃~75℃工作温度,-40~85℃存储温度,在极端气象条件下也能安全运行。8.支持IEEE802.3,IEEE802.3u,IEEE802.3x。
文章浏览阅读946次。Hi,我是阿昌,今天教你如何使用通义灵码。_通义灵码
文章浏览阅读2.3w次。我们在开发或编译旧版本NDK项目时,需要使用一些老版本的NDK,在这里提供了旧版NDK的列表及下载链接_ndk 老颁布
文章浏览阅读640次,点赞6次,收藏18次。网关是一个大的概念,没有特指是什么设备,很多设备都可以做网关,普通的PC机也能做,常用的网关设备是路由器。网关的作用主要是用来连接两个不同的网络,比如可以连接两个IP地址不相同的网络,或连接两个操作系统不同的网络,如WINDOWS与LINUX互连,或连接两个网络协议不同的网络,如TCP/IP与IPX.或拓扑结构不同的网络,如以太网和令牌环网。总之网关是一种中间媒介。而防火墙也可以做网关,但它的主要做用只是用来防病毒或防黑客,网关只算是防火墙的一个功能。网关与防火墙的区别。
文章浏览阅读4.1k次,点赞42次,收藏34次。背景在使用之前的代码时,报错: Traceback (most recent call last): File "xxx", line xx, in import pymysql ModuleNotFoundError: No module named 'pymysql'翻译:```追溯(最近一次通话):文件“xxx”,第xx行,在导入pymysqlModuleNotFoundError:没有名为“pymysql”的模块```原因 ......_modulenotfounderror: no module named 'pymysql
文章浏览阅读275次。1 import java.io.File;23 import java.io.IOException;45 import java.util.Locale;6789 import jxl.CellView;1011 import jxl.Workbook;1213 import jxl.WorkbookSettings;1415 import jxl.format.UnderlineStyle;..._android excel生成读取类
文章浏览阅读4.3w次,点赞16次,收藏126次。1、去微软官网下载完成ISO镜像,最好不要在线安装,打开官方链接 https://www.visualstudio.com/zh-cn/downloads/download-visual-studio-vs.aspx按下图操作:2、用虚拟光驱加载,或者直接右键解压。在安装前,先安装两个证书。亲测,安装后,减少了很多“安装包损坏或丢失”的现象。两证书下载地址链接: https:/..._vs2015离线版csdn
文章浏览阅读2k次,点赞4次,收藏3次。目前 postcss-pxtorem 版本最高6.0.0,报这个错是因为插件版本太高,降成5.1.1可解决这个报错解决方法:分两步1.执行npm uninstall post-pxtorem2.执行npm i [email protected]_error: postcss plugin postcss-import requires postcss 8.
文章浏览阅读787次。Linux-ARM开发_linux arm开发