深入讲解CFS组调度!(上)
注:本文缩写说明

一、CFS组调度简介
1.1. 存在的原因
总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢,比如多用户计算机系统每个用户的所有任务划分到一个分组中,A用户90个相同任务,而B用户只有10个相同任务,在CPU完全跑满的情况下,那么A用户将占90%的CPU时间,而B用户只占到了10%的CPU时间,这对B用户显然是不公平的。再或者同一个用户,既想-j64快速编译,又不想被编译任务影响使用体验,也可将编译任务设置到对应分组中,限制其CPU资源。
1.2. 手机设备上的分组状态

/dev/cpuctl 目录使用struct task_group root_task_group 表示。其下的每一层级的子目录都抽象为一个task_group结构。有几个需要注意的点:
- 根组下的cpu.shares 文件默认值是1024,而且不支持设置。根组的负载也不会更新。
- 根组也是一个task group,根组下的任务也是被分过组的,不会再属于其它组。
- 内核线程默认在根组下,其直接从根cfs_rq上分配时间片,有非常大的优势,若是其一直跑,那在trace上看就几乎就是”一直跑”,比如常见的kswapd内核线程。
- 默认cpu.shares配置下,若全是nice=0的任务,且只考虑单核的情况下,根组下的每个任务在完全满载情况下能得到的时间片等于其它分组下所有任务能分配到的时间片的总和。
注意:task和task group都是通过权重来分配时间片的,但是task的权重来自其优先级,而task group的权重则来自与其cgroup目录下cpu.shares文件设置的值。使能组调度后,看任务分得的时间片,就不能单看其prio对应的权重了,还要看其task group分得的权重和本group中其它任务的运行情况。
二、任务的task group分组
CFS组调度功能主要是通过对任务进行分组体现出来的,一个分组由一个struct task_group来表示。
2.1. 如何设置分组
task group分组配置接口由cpu cgroup子系统通过cgroup目录层次结构导出到用户空间。

如何从task group中移除一个任务呢,没有办法直接移除的,在cgroup语义下,一个任务某一时刻必须属于一个task group,只有通过将其echo到其它分组中才能将其从当前分组中移除。
2.2. Android中如何设置分组
Process.java中向其它模块提供 setProcessGroup(int pid, int group) 将pid进程设置进group参数指定的分组中,供其它模块进行调用设置。比如OomAdjuster.java 中将任务切前/后台分别调用传参group=THREAD_GROUP_TOP_APP/THREAD_GROUP_BACKGROUND。
libprocessgroup 中提供了一个名为 task_profiles.json 的配置文件,它里面 AggregateProfiles 聚合属性字段配置了上层设置下来后的对应的行为。比如 THREAD_GROUP_TOP_APP 对应的聚合属性为 SCHED_SP_TOP_APP,其中的MaxPerformance属性对应的行为就是加入到cpu top-app分组。

”MaxPerformance”属性的配置可读性非常强,可以看出是加入到cpu子系统的top-app分组中。
2.3. Android中设置为TOP-APP分组,对cgroup设置了什么
因为有多个cgroup子系统,除了我们正在讲的CFS组调度依附的cpu cgroup子系统外,还有cpuset cgroup子系统(限制任务可运行的CPU和可使用的内存节点),blkio cgroup子系统(限制进程的块设备io),freezer cgroup子系统(提供进程冻结功能)等。上层配置分组下来可能不只切一个cgroup,具体切了哪些子系统体现在集合属性 AggregateProfiles 的数组成员上,比如上例中另外两个属性对应的行为分别是加入blkio子系统的根组和将任务的timer_slack_ns(一个平衡hrtimer定时唤醒及时性与功耗的参数)设置为50000ns。

2.4. 服务启动后就放在指定分组
在启动服务的时候使用 task_profiles进行配置,举例。

资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
三、内核实现概述
上面我们讨论了对task group分组的配置,本节开始将进入到内核中,了解其实现。
3.1. 相关功能依赖关系
内核相关CONFIG_*依赖如下:
图1:

CGROUP提供cgroup目录层次结构的功能;CGROUP_SCHED提供cpu cgroup目录层次结构(如 /dev/cpuctl/top-app),并为每个cpu cgroup目录提供task group的概念;FAIR_GROUP_SCHED基于cpu cgroup提供的task group提供CFS任务组调度功能。图1中灰色的虚线框图是Android手机内核中默认不使能的。
3.2. task group数据结构框图
如下图2所示,展示了一个task group在内核中维护的主要数据结构的框图,对照着图下面概念更容易理解:
- 由于不能确定一个分组内的任务跑在哪个CPU上,因此一个task group在每个CPU上都维护了一个group cfs_rq,由于task group也要参与调度(要先选中task group才能选中其group cfs_rq上的任务) ,因此在每个CPU上也都维护了一个group se。
- task se的my_q成员为NULL,而group se的my_q成员指向其对应的group cfs_rq,其分组在本CPU上的就绪任务就挂在这个group cfs_rq上。
- task group可以嵌套,由 parent/siblings/children 成员构成一个倒立树状层次结构,根task group的parent指向NULL。
- 所有CPU的根cfs_rq属于root task group。
图2:

注:画图比较麻烦,此图是借鉴蜗窝的。
四、相关结构体
4.1. struct task_group
一个 struct task_group 就表示一个cpu cgroup分组。在使能FAIR_GROUP_SCHED的情况下,一个 struct task_group 就表示CFS组调度中的一个任务组。

css: 该task group对应的cgroup状态信息,通过它依附到cgroup目录层次结构中。
se: group se,是个数组指针,数组大小为CPU的个数。因为一个task group中有多个任务,且可以跑在所有CPU上,因此需要每个CPU上都要有本task group的一个 se。
cfs_rq: group se的cfs_rq, 是本task group的在各个CPU上的cfs_rq,也是个数组指针,数组大小为CPU的个数。当本task group的任务就绪后就挂在这个cfs_rq上。它在各个CPU上对应的指针和task group在各个CPU上的se->my_q具有相同指向,见 init_tg_cfs_entry().
shares: task group的权重,默认为scale_up(1024)。和task se的权重类似,值越大task group能获取的CPU时间片就越多。但是和task 不同的是,task group 在每个CPU上都有一个group se,因此需要按照一定规则分配给各CPU上的group se, 下面会讲解分配规则。
load_avg: 此task group的负载,而且只是一个load_avg 变量(不像se和cfs_rq是一个结构),下面会对其进行讲解。注意它不是per-cpu的,此task group的任务在各个CPU上进行更新时都会更新它,因此需要注意对性能的影响。
parent/siblings/children: 构成task group的层次结构。
内核中有个全局变量 struct task_group root_task_group, 表示根组。其cfs_rq[]就是各个cpu的cfs_rq, 其se[]都为NULL。其权重不允许被设置,见 sched_group_set_shares()。负载也不会被更新,见 update_tg_load_avg()。
系统中的所有task group 结构都会被添加到task_groups链表上,在CFS带宽控制时使用。
初学者可能会混淆组调度和调度组这两个概念,以及 struct task_group 与struct sched_group 的区别。组调度对应struct task_group,用来描述一组任务,主要用于util uclamp(对一组任务的算力需求进行钳制)和CPU资源使用限制,也是本文中要讲解的内容。调度组对应 struct sched_group,是CPU拓扑结构sched_domain中的概念,用来描述一个CPU(MC层级)/Cluster(DIE层级)的属性,主要用于选核和负载均衡。
4.2. struct sched_entity
一个sched_entity 既可以表示一个task se, 又可以表示一个group se。下面主要介绍使能组调度后新增的一些成员。

load: 表示se的权重,对于gse, 新建时初始化为NICE_0_LOAD, 见init_tg_cfs_entry()。
depth: 表示task group的嵌套深度,根组下的se的深度为0,每嵌套深一层就加1。比如/dev/cpuctl目录下有个tg1目录,tg1目录下又有一个tg2目录,tg1对应的group se的深度为0,tg1下的task se的深度是1,tg2下的task se的深度是2。更新位置见 init_tg_cfs_entry()/attach_entity_cfs_rq()。
parent: 指向父se节点, 父子se节点都是对应同一cpu的。根组下任务的指向为NULL。
cfs_rq: 该se挂载到的cfs_rq。对于根组下的任务指向rq的cfs_rq,非根组的任务指向其parent->my_rq,见init_tg_cfs_entry()。
my_q: 该se的cfs_rq, 只有group se才有cfs_rq,task se的为NULL,entity_is_task()宏通过这个成员来判断是task se还是group se。
runnable_weight: 缓存 gse->my_q->h_nr_running 的值,在计算gse的runnable负载时使用。
avg: se的负载,对于tse会初始化为其权重(创建时假设其负载很高),而gse则会初始化为0,见init_entity_runnable_average()。task se的和group se的有一定区别,下面第五章会进行讲解。
4.3. struct cfs_rq
struct cfs_rq 既可以表示per-cpu的CFS就绪队列,又可以用来表示gse的my_q队列。下面列出对组调度比较关键的一些成员进行讲解。

load: 表示cfs_rq的权重,无论是根cfs_rq还是grq,这里的权重都等于其队列上挂的所有任务的权重之和。
nr_running: 当前层级下 task se 和 group se的个数和。
h_nr_running: 当前层级以及所有子层级下task se的个数和,不包括group se。
avg: cfs_rq的负载。下面将对比task se、group se、cfs_rq讲解负载。
removed: 当一个任务退出或者唤醒后迁移到到其他cpu上的时候,原来CPU的cfs rq上需要移除该任务带来的负载。这个移除动作会先把移除的负载记录在这个removed成员中,在下次调用update_cfs_rq_load_avg()更新cfs_rq负载时再移除。nr表示要移除的se的个数,*_avg则表示要移除的各类负载之和。
tg_load_avg_contrib: 是对 grq->avg.load_avg 的一个缓存,表示当前grq的load负载对tg的贡献值。用于在更新 tg->load_avg 的同时降低对 tg->load_avg 的访问次数。在计算gse从tg分得的权重配额时的近似算法中也有用到,见 calc_group_shares()/update_tg_load_avg()。
propagate: 标记是否有负载需要向上层传播。下面7.3节会进行讲解。
prop_runnable_sum: 在负载沿着task group层级结构向上层传播的时候,表示要上传的tse/gse的load_sum值。
h_load: 层次负载hierarchy load,表示本层cfs_rq的load_avg对CPU的load_avg的贡献值,主要在负载均衡路径中使用。下面会对其进行讲解。
last_h_load_update: 表示上一次更新h_load的时间点(单位jiffies)。
h_load_next: 指向子gse,为了获取任务的hierarchy load(task_h_load函数),需要从顶层cfs向下,依次更新各个level的cfs rq的h_load。因此,这里的h_load_next就是为了形成一个从顶层cfs rq到底层cfs rq的cfs rq--se--cfs rq--se的关系链。
rq: 使能组调度后才加的这个成员,若没有使能组调度的话,cfs_rq就是rq的一个成员,使用 container_of进行路由,使能组调度后,增加了一个rq成员进行cfs_rq到rq的路由。
on_list/leaf_cfs_rq_list: 尝试将叶cfs_rq串联起来,在CFS负载均衡和带宽控制相关逻辑中使用。
tg: 该cfs_rq隶属的task group。
五、task group权重
task group的权重使用struct task_group 的 shares 成员来表示,默认值是scale_load(1024)。可以通过cgroup目录下的cpu.shares文件进行读写,echo weight > cpu.shares 就是将task group权重配置为weight,保存到shares 成员变量中的值是scale_load(weight)。root_task_group不支持设置权重。
不同task group的权重大小表示系统CPU跑满后,哪个task group组可以跑多一些,哪个task group组要跑的少一些。
5.1. gse的权重
task group在每个CPU上都有一个group se,那么就需要将task group的权重 tg->shares 按照一定的规则分配到各个gse上。规则就是公式(1):
* tg->weight * grq->load.weight* ge->load.weight = ----------------------------------------- (1)* \Sum grq->load.weight
其中 tg->weight 就是tg->shares, grq->load.weight 表示tg在各个CPU上的grq的权重。也就是每个gse根据其cfs_rq的权重比例来分配tg的权重。cfs_rq的权重等于其上挂载的任务的权重之和。假设tg的权重是1024,系统中只有2个CPU,因此有两个gse, 若其grq上任务状态如下如下图3,则gse[0]分得的权重为 1024 * (1024+2048+3072)/(1024+2048+3072+1024+1024) = 768;gse[1]分得的权重为 1024 * (1024+1024)/(1024+2048+3072+1024+1024) = 256。
图3:

gse的权重更新函数为 update_cfs_group(),下面看其具体实现:

tg的权重向gse[X]的分配动作是在 calc_group_shares() 中完成的。
公式(1)中使用到 \Sum grq->load.weight,也就是说一个gse权重的更新需要访问各个CPU上的grq,锁竞争代价比较高,因此进行了一系列的近似计算。
首先进行替换:
* grq->load.weight --> grq->avg.load_avg (2)
然后得到:
* tg->weight * grq->avg.load_avg* ge->load.weight = ---------------------------------------- (3)* tg->load_avg** Where: tg->load_avg ~= \Sum grq->avg.load_avg
由于cfs_rq->avg.load_avg = cfs_rq->avg.load_sum/divider。而 cfs_rq->avg.load_sum 等于 cfs_rq->load.weight 乘以非idle状态下的几何级数。这个近似是在tg的每个CPU上的grq的非idle状态的时间级数是相同的前提下才严格相等的。也就是说tg的任务在各个CPU上的运行状态越一致,越接近这个近似值。
task group 空闲的情况下,启动一个任务。grq->avg.load_avg 需要时间来建立,在建立时间这种特殊情况下公式1简化为:
* tg->weight * grq->load.weight* ge->load.weight = --------------------------------------- = tg->weight (4)* grp->load.weight
相当于一个单核系统下的状态了。为了让公式(3)在这种特殊情况下更贴近与公式(4),又做了一次近似,得到:
* tg->weight * grq->load.weight* ge->load.weight = -------------------------------------------------------------------- (5)* tg->load_avg - grq->avg.load_avg + grq->load.weight
但是因为grq上没有任务时,grq->load.weight 可以下降到 0,导致除以零,需要使用 grq->avg.load_avg作为它的下限,然后给出:
* tg->weight * grq->load.weight* ge->load.weight = ------------------------------------------ (6)* tg_load_avg'** 其中:* tg_load_avg' = tg->load_avg - grq->avg.load_avg + max(grq->load.weight, grq->avg.load_avg)
max(grq->load.weight, grq->avg.load_avg) 一般都是取grq->load.weight,因为只有grq上一直有任务running+runnable才会趋近于grq->load.weight。
calc_group_shares() 函数是通过公式(6)近似计算各个gse分得的权重:

由于tg中的每个任务都对gse的权重有贡献,因此grq上任务个数变更时都要更新gse的权重,近似过程中使用到了se的负载,在entity_tick()中也进行了一次更新。调用路径:

5.2. gse上每个tse分到的权重
任务组中的任务也是按其权重比例分配gse的权重。如上图2中gse[0]的grq上挂的3个任务,tse1分得的权重就是7681024/(1024+2048+3072)=128, tse2分得的权重就是7682048/(1024+2048+3072)=256, tse3分得的权重就是768*3072/(1024+2048+3072)=384。
在tg中的任务分配tg分得的时间片的时候,会使用到这个按比例分得的权重。分组嵌套的越深,能按比例分得的权重就越小,由此可见,在高负载时task group中的任务是不利于分配时间片的。
文章篇幅过长,下文继续讲解!

相关文章:
深入讲解CFS组调度!(上)
注:本文缩写说明 一、CFS组调度简介 1.1. 存在的原因 总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢,比如多用户计算机系统每个用户的所有任务划分到一个分组中,A用户90个相同任务,而B…...
大数据实操项目分享:餐饮智能推荐服务在线实习项目
项目背景:在“互联网"背景下,餐饮企业的经营方式发生了很大的变革:团购和020拓宽了销售 渠道,电子点餐、店内WIFI等信息技术提升了服务水平,大数据、私人定制更好地满足了细分市场的需求等。但是与此同时…...
代码随想录day38
动态规划五部曲 确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 https://leetcode.cn/problems/fibonacci-number/ class Solution {public int fib(int n) {if(n0) return 0;if(n<3) return 1;int[] dp new int[n]…...
《计算机网络:自顶向下方法》实验5:TCP
Q1 包含HTTP POST消息的TCP报文段的序号是多少?注意:为了发现POST 命令, 你需要在wireshark底部的报文内容域窗口中去查找,查找数据中包含 “POST”的段。 如图所示,由报文中的POST 和 HTTP/1.1可知,其包含HTTP POST消息; TCP报文段的序号可见TCP报文: Sequence Number:…...
【踩坑指南】Stable Diffusion 服务器端部署笔记
文章目录下载github文件配置环境ckpt文件权重下载生成图像NSFW检查(瑟图过滤)下载github文件 https://github.com/CompVis/stable-diffusion 这个网址,下载压缩包解压,也可以用git clone下载 配置环境 这一步坑最多,…...
[qiankun]-多页签缓存
[qiankun]-多页签缓存环境功能需求多页签缓存方案方案1.主服务进行html替换方案2.微服务vnode 替换方案3.每个微服务都不卸载微服务加载方式的选择微服务的路由路径选择微服务的缓存工具微服务的容器使用tab作为微服务的挂载容器使用微服务路由作为微服务的挂载容器场景描述微服…...
2|电子技术|数字电子技术基础|雨课堂习题|考前回顾
A/DD/A转化横向与阵列 相乘,竖向为或阵列 相加!功率放大电路克服交越失真,是在乙类的基础上增加两个二极管,使微导通,使三极管导通时间大于半个周期,小于一个周期,构成甲乙类工作状态。选择填空…...
vue+echarts:圆形柱状图设置角度和最大值
第020个点击查看专栏目录本示例是显示圆形的柱状图,angleAxis设置一个max, angleAxis上startAngle:90 , 将0点设置为最顶点。 文章目录示例效果示例源代码(共100行)相关资料参考专栏介绍示例效果 示例源代码…...
Linux系统安装Nginx常见报错问题
安装Nginx从nginx官网下载所需版本的nginx,http://nginx.org/下载之后,将安装包上传到linux系统指定路径解压文件,tar -zxvf nginx-1.22.1.tar.gz (此处用1.22.1版本为例)进入安装包目录,cd nginx-1.22.1执…...
按下按键之后,打印一句话------>三个按键需要实现
main.c: #include "key.h" extern void printf(const char *fmt, ...); void delay_ms(int ms){ int i,j; for(i 0; i < ms;i) for (j 0; j < 1800; j);} int main(){ //key1键盘 //EXIT控制器初始化 void PF9_exti_init(); //GICD控…...
Mac配置VScode
Mac配置VScode 常用技巧 命令调色板 根据您当前的上下文访问所有可用的命令。 键盘快捷键:⇧⌘P 快速打开 快速打开文件。 键盘快捷键:⌘P **提示:**类型?查看命令建议。 在最近打开的文件夹和工作区之间导航 最近打开 键盘快捷…...
MAC地址IP地址 端口
网络结构: 服务器-客户机(C/S)Client-Server结构,如QQ,LOL都拥有客户端 优点:响应速度快,形式多样,安全新较高缺点:安装软件和维护,不能跨平台LINUX/windows/MAC浏览器-…...
关于虚拟数字人你想知道的都在这里
2022年底,微软旗下的人工智能实验室Open AI发布的对话式大型语言模型ChatGPT聊天机器人一夜蹿红,5天用户量超百万,在各大中外媒体平台掀起了一阵热潮。也带火了人工智能相关产业,AI虚拟数字人就是其中之一,一个随着元宇…...
分布式任务调度处理方案(无代码)
业务涉及到,需要向数据库、redis、elasticsearch、MinIO写四份数据,这里存在分布式事务问题。如何解决问题,先分析cap,是要保证可用性,还是保证一致性。如何选择是CP还是AP?分析业务场景CP的场景࿱…...
2023年博管办香江学者计划、澳门青年学者开始申报
2023年2月20日,全国博士后管委会办公室官方网站发出了2023年香江学者计划、澳门青年学者计划和博士后国(境)外学术交流项目申报指南,以下知识人网小编仅转载香江学者计划和澳门青年学者计划申报指南并做重点解读。知识人网整理香江…...
(二十一)、实现评论功能(1)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】
1,评论回复模块的样式布局 1.1 在detail页面添加uview中的 Empty 内容为空组件 <!-- 评论区 --><view class"comment"><u-empty mode"comment" icon"http://cdn.uviewui.com/uview/empty/comment.png"></u-emp…...
【Docker】初识Dcoker以及镜像操作(一)
目录 1.初识Docker 1.1.什么是Docker 1.1.1.应用部署的环境问题 1.1.2.Docker解决依赖兼容问题 1.1.3.Docker解决操作系统环境差异 1.1.4.小结 1.2.Docker和虚拟机的区别 1.3.Docker架构 1.3.1.镜像和容器 1.3.2.DockerHub 1.3.3.Docker架构 1.3.4.小结 1.4.安装D…...
(1)C#传智:在vs2022中基本了解(第一天)
开始vs2022中C#入门,就是一笔记,算不上原创,没办法得选啊。 一、vs中卸载项目和移除项目有什么区别? 1、卸载、移除都不会移除物理文件,只会删除关联 2、卸载删除关联的程度低,卸载后项目只是“变灰色…...
【数据结构与算法】算法的时间复杂度和空间复杂度
文章目录前言1.算法效率1.1.如何衡量一个算法的好坏1.2.算法的复杂度2.时间复杂度2.1.时间复杂度的概念2.2.大O的渐进表示法2.3.常见时间复杂度计算举例2.4.常见时间复杂度3.空间复杂度4.复杂度oj练习Practice.1 消失的数字Practice.2 旋转数组写在最后前言 关于时空复杂度的分…...
不使用contab -e的方式,添加计划任务
不使用contab -e的方式,添加计划任务 crond 服务的周期任务的文件存放位置在:/var/spool/cron/ 如果你是root用户的话那么你的周期任务文件名就叫root 如果你使用其他用户创建的周期任务,任务文件名就叫它本身 1、 使用root用户创建周期任…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
