Linux进程管理:(六)SMP负载均衡

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

  • 参考资料及图片来源:《奔跑吧Linux内核》

  • Linux 5.0内核源码注释仓库地址:

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 前置知识

1.1 CPU管理位图

内核对CPU的管理是通过位图(bitmap)变量来管理的,并且定义了possible、present、online和active这4种状态:

/* 位图类型变量 */
// 表示系统中有多少个可以运行(现在运行或者将来某个时间点运行)的 CPU 内核
#define cpu_possible_mask ((const struct cpumask *)&__cpu_possible_mask)
// 表示系统中有多少个正处于运行(online)状态的 CPU 内核
#define cpu_online_mask   ((const struct cpumask *)&__cpu_online_mask)
// 表示系统中有多少个可处于运行状态的CPU内核,它们不一定都处于运行状态,有的CPU内核可能被热插拔了
#define cpu_present_mask  ((const struct cpumask *)&__cpu_present_mask)
// 表示系统中有多少个活跃的 CPU 内核
#define cpu_active_mask   ((const struct cpumask *)&__cpu_active_mask)
  • #define DECLARE_BITMAP(name,bits) \unsigned long name[BITS_TO_LONGS(bits)]typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
    
    • name[]:位图类型变量使用一个长整数类型数组name[],每位代表一个CPU。对于64位处理器来说,一个长整数类型数组成员只能表示64个CPU内核。内核配置中有一个宏CONFIG_NR_CPUS为8,那么只需要一个长整数类型数组成员可。
    • struct cpumask:cpumask数据结构本质上是位图,内核通常使用cpumask的相关接口函数来管理CPU内核数量。

CPU位图的初始化流程如下图所示:

在这里插入图片描述

  • cpu_possible_mask通过查询系统DTS表或者ACPI表获取系统CPU内核数量
  • cpu_present_mask等同于cpu_possible_mask
  • cpu_active_mask是经过使能后的CPU内核数量

1.2 CPU调度域

根据系统的内存和高速缓存的布局,CPU域可分为如下几类:

在这里插入图片描述

Linux内核通过数据结构sched_domain_topology_level来描述CPU的层次关系,简称为SDTL:

// 用于描述 CPU 的层级关系
struct sched_domain_topology_level {sched_domain_mask_f mask;			// 函数指针,用于指定某个 SDTL 的 cpumask 位图// sd_flags 函数指针里指定了该调度层级的标志位sched_domain_flags_f sd_flags;		// 函数指针,用于指定某个 SDTL 的标志位int		    flags;int		    numa_level;struct sd_data      data;
#ifdef CONFIG_SCHED_DEBUGchar                *name;
#endif
};

另外,内核默认定义了一个数组default_topology[]来概括CPU物理域的层次结构

// CPU 拓扑关系,从下往上
static struct sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT		// 超线程// cpu_smt_mask() 函数描述了 SMT 层级的 CPU 位图组成方式{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) },
#endif
#ifdef CONFIG_SCHED_MC		// 多核// cpu_coregroup_mask() 函数描述了 MC 层级的 CPU 位图组成方式{ cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
#endif						// 处理器// cpu_cpu_mask() 函数描述了 DIE 层级的 CPU 位图组成方式{ cpu_cpu_mask, SD_INIT_NAME(DIE) },{ NULL, },
};

default_topology[]数组可知,系统默认支持DIE层级、SMT层级以及MC层级。另外,在调度域里划分调度组,使用sched_group来描述调度组,调度组是负载均衡调度的最小单位,在最低层级的调度域中,通常一个调度组描述一个CPU。

在一个支持NUMA架构的处理器中,假设它支持SMT技术,那么整个系统的调度域和调度组的关系如下图所示(它在默认调度层级中新增一个NUMA层级的调度域):

在这里插入图片描述

  • DIE表示处理器封装,它是CPU拓扑结构中的一个层级,在多核处理器中,通常有多个物理处理器核心,组成一个封装

在Linux内核中使用sched_domain数据结构来描述调度层级。在超大系统中系统会频繁访问调度域数据结构,为了提升系统的性能和可扩展性,调度域sched_domain数据结构采用Per-CPU变量来构建:

// 描述调度层级
struct sched_domain {// 父调度域指针,顶层调度域必须为 null 终止struct sched_domain *parent;// 子调度域指针,底层调度域必须为 null 终止struct sched_domain *child;// groups 指向该调度域的平衡组链表struct sched_group *groups;// 最小平衡间隔(毫秒)unsigned long min_interval;// 最大平衡间隔(毫秒)unsigned long max_interval;...
};

下面举例来说明CPU调度域的拓扑关系,如下图所示,假设在一个4核处理器中,每个CPU拥有独立L1高速缓存且不支持超线程技术,4个物理CPU被分成两个簇Cluster0和Cluster1,每个簇包含两个物理CPU,簇中的CPU共享L2高速缓存

在这里插入图片描述

因为每个CPU内核只有一个运行线程,所以4核处理器没有SMT层级。簇由两个CPU组成,这两个CPU处于MC层级且是兄弟关系。整个处理器可以看作处于DIE层级,因此该处理器只有两个层级,即MC和DIE。根据上述原则,4核处理器的调度域和调度组的拓扑关系如下图所示:

在这里插入图片描述

在每个SDTL都为每个CPU分配了对应的调度域和调度组,以CPU0为例:

  • 对于DIE层级,CPU0对应的调度域是domain_die_0,该调度域管辖着4个CPU并包含两个调度组,分别为group_die_0和group_die_1
    • 调度组group_die_0管辖CPU0和CPU1
    • 调度组group_die_1管辖CPU2和CPU3
  • 对于MC层级,CPU0对应的调度域是domain_mc_0,该调度域中管辖着CPU0和CPU1并包含两个调度组,分别为group_mc_0和group_mc_1
    • 调度组group_mc_0管辖CPU0
    • 调度组group_mc_1管辖CPU1

除此以外,4核处理器的调度域和调度组还有两层关系,如下图所示:

在这里插入图片描述

综上所述,为了提升系统的性能和可扩展性,sched_domain数据结构采用Per-CPU变量来构建,这样可以减少CPU之间的访问竞争。而sched_group数据结构则是调度域内共享的。

2. LLC调度域

在负载均衡算法中,我们常常需要快速找到系统中最高层级的并且具有高速缓存共享属性的调度域。通常在—个系统,我们把最后一级高速缓存(Last Level Cache,LLC)称为LLC。在调度域标志位中,SD_SHARE_PKG_RESOURCE标志位用于描述高速缓存的共享属性。因此,在调度域层级中,包含LLC的最高一级调度域称为LLC调度域。在Linux内核中实现一组特殊用途的指针,用来指向LLC调度域,这些指针是Per-CPU变量。查找和设置LLC调度域是在update_top_cache_domain()函数中实现的。在select_idle_cpu()等函数中会使用到LLC 调度域。

// 指向 LLC 调度域
DEFINE_PER_CPU(struct sched_domain *, sd_llc);
// LLC 调度域包含多少个 CPU
DEFINE_PER_CPU(int, sd_llc_size);
// LLC 调度域第一个 CPU 的编号
DEFINE_PER_CPU(int, sd_llc_id);
DEFINE_PER_CPU(struct sched_domain_shared *, sd_llc_shared);
DEFINE_PER_CPU(struct sched_domain *, sd_numa);
DEFINE_PER_CPU(struct sched_domain *, sd_asym_packing);
// 指向第一个包含不同 CPU 架构的调度域,主要用于大/小核架构
DEFINE_PER_CPU(struct sched_domain *, sd_asym_cpucapacity);
DEFINE_STATIC_KEY_FALSE(sched_asym_cpucapacity);// 查找和设置 LLC 调度域
static void update_top_cache_domain(int cpu)
{...
}

3. 负载均衡

负载均衡机制从注册软中断开始,每次系统处理调度节拍时会检查当前是否需要处理负载均衡。负载均衡的流程如图所示:

在这里插入图片描述

为了使读者有更真切的理解,下文将根据流程图围绕源代码进行讲解这个过程:

load_balance()

// this_cpu 指当前正在运行的 CPU
// this_rq 表示当前就绪队列
// sd 表示当前正在做负载均衡的调度域
// idle 表示当前 CPU 是否处于 IDLE 状态
// continue_balancing 表示当前调度域是否还需要继续做负载均衡
static int load_balance(int this_cpu, struct rq *this_rq,struct sched_domain *sd, enum cpu_idle_type idle,int *continue_balancing)
{...struct lb_env env = {// 当前调度域.sd		= sd,// 当前的 CPU,后面可能要把一些繁忙的进程迁移到该 CPU 上.dst_cpu	= this_cpu,// 当前 CPU 对应的就绪队列.dst_rq		= this_rq,// 当前调度域里的第一个调度组的 CPU 位图.dst_grpmask    = sched_group_span(sd->groups),.idle		= idle,// 本次最多迁移 32 个进程(sched_nr_migrate_break全局变量的默认值为 32).loop_break	= sched_nr_migrate_break,// load_balance_mask 位图.cpus		= cpus,.fbq_type	= all,.tasks		= LIST_HEAD_INIT(env.tasks),};// 把 sd 调度域管辖的 CPU 位图复制到 load_balance_mask 位图里cpumask_and(cpus, sched_domain_span(sd), cpu_active_mask);schedstat_inc(sd->lb_count[idle]);redo:// should_we_balance() 函数判断当前 CPU 是否需要做负载均衡if (!should_we_balance(&env)) {*continue_balancing = 0;goto out_balanced;}// 查找该调度域中最繁忙的调度组group = find_busiest_group(&env);if (!group) {schedstat_inc(sd->lb_nobusyg[idle]);goto out_balanced;}// 在刚才找到的最繁忙的调度组中查找最繁忙的就绪队列busiest = find_busiest_queue(&env, group);if (!busiest) {schedstat_inc(sd->lb_nobusyq[idle]);goto out_balanced;}...// env.src_cpu 指向最繁忙的调度组中最繁忙的就绪队列中的 CPUenv.src_cpu = busiest->cpu;// env.src_rq 指向最繁忙的就绪队列env.src_rq = busiest;ld_moved = 0;if (busiest->nr_running > 1) {...// detach_tasks() 函数遍历最繁忙的就绪队列中所有的进程,找出适合被迁移的进程,然后让这些// 进程退出就绪队列。cur_ld_moved 变量表示已经迁出了多少个进程cur_ld_moved = detach_tasks(&env);...if (cur_ld_moved) {// attach_tasks() 函数把刚才从最繁忙就绪队列中迁出的进程都迁入当前 CPU 的就绪队列中attach_tasks(&env);ld_moved += cur_ld_moved;}...// 由于进程设置了 CPU 亲和性,不能迁移进程到 dst_cpu,因此还有负载没迁移完成,需要换一个新的// dst_cpu 继续做负载迁移if ((env.flags & LBF_DST_PINNED) && env.imbalance > 0) {...goto more_balance;}...}...
}

load_balance()->should_we_balance()

// 判断当前 CPU 是否需要做负载均衡
static int should_we_balance(struct lb_env *env)
{// sg 指向调度域中的第一个调度组struct sched_group *sg = env->sd->groups;...// 查找当前调度组是否有空闲 CPU,如果有空闲 CPU,那么使用变量 balance_cpu 记录该 CPUfor_each_cpu_and(cpu, group_balance_mask(sg), env->cpus) {if (!idle_cpu(cpu))continue;balance_cpu = cpu;break;}// 查找该调度组有哪些 CPU 适合做负载均衡// 	若调度组里有空闲 CPU,则优先选择空闲 CPU// 	调度组里没有空闲 CPU,则选择调度组的第一个 CPUif (balance_cpu == -1)balance_cpu = group_balance_cpu(sg);// 然后判断适合做负载均衡的 CPU 是否为当前 CPU,若为当前 CPU,则 should_we_balance()函数返回true,表示可以做负载均衡return balance_cpu == env->dst_cpu;
}

load_balance()->find_busiest_group()

// 查找该调度域中最繁忙的调度组
static struct sched_group *find_busiest_group(struct lb_env *env)
{...// 初始化 sd_lb_stats 结构体init_sd_lb_stats(&sds);// 更新该调度域中负载的相关统计信息update_sd_lb_stats(env, &sds);...// 如果没有找到最繁忙的调度组或者最繁忙的组中没有正在运行的进程,那么跳过该调度域if (!sds.busiest || busiest->sum_nr_running == 0)goto out_balanced;// 计算该调度域的平均负载sds.avg_load = (SCHED_CAPACITY_SCALE * sds.total_load)/ sds.total_capacity;// 如果最繁忙的调度组的组类型是 group_imbalanced,那么跳转到 force_balance 标签处if (busiest->group_type == group_imbalanced)goto force_balance;...// 若本地调度组的平均负载比刚才计算出来的最繁忙调度组的平均负载还要大,那么不需要做负载均衡if (local->avg_load >= busiest->avg_load)goto out_balanced;// 若本地调度组的平均负载比整个调度域的平均负载还要大,那么不需要做负载均衡if (local->avg_load >= sds.avg_load)goto out_balanced;// 如果当前 CPU 处于空闲状态,最繁忙的调度组里的空闲 CPU 数量大于本地调度组里的空闲 CPU 数量,说明不需要做负载均衡if (env->idle == CPU_IDLE) {if ((busiest->group_type != group_overloaded) &&(local->idle_cpus <= (busiest->idle_cpus + 1)))goto out_balanced;// 如果当前 CPU 不处于空闲状态,那么比较本地调度组的平均量化负载和最繁忙调度组的平均量化负载,这里使用了 imbalance_pct// 系数,它在 sd_init() 函数中初始化,默认值为125。若本地调度组的平均量化负载大于最繁忙组的平均量化负载,说明该调度组不// 忙,不需要做负载均衡。} else {if (100 * busiest->avg_load <=env->sd->imbalance_pct * local->avg_load)goto out_balanced;}force_balance:env->src_grp_type = busiest->group_type;// 根据最繁忙调度组的平均量化负载、调度域的平均量化负载和本地调度组的平均量化负载来计算该调度域需要迁移的负载不均衡值calculate_imbalance(env, &sds);// 返回最忙的调度组return env->imbalance ? sds.busiest : NULL;out_balanced:env->imbalance = 0;return NULL;
}

load_balance()->find_busiest_queue()

  1. for_each_cpu_and()遍历该调度组里所有的CPU
  2. 通过就绪队列的量化负载(runnable_load_avg)与CFS额定算力(cpu_capacity)的乘积来进行比较。使用weighted_cpuload()来获取cfs_rq->avg.runnable_load_avg的值,通过capacity_of()来获取cpu_capacity的值。这里要考虑不同处理器架构计算能力的不同对处理器负载的影响。

注意,数据结构rq中的cpu_capacity_orig成员指的是CPU的额定算力,而cpu_capacity成员指的是CFS就绪队列的额定算力,通常CFS额定算力最大能达到CPU额定算力的80%。

load_balance()->detach_tasks()

// detach_tasks() 函数遍历最繁忙的就绪队列中所有的进程,找出适合被迁移的进程,
// 然后让这些进程退出就绪队列。返回值表示已经迁出了多少个进程
static int detach_tasks(struct lb_env *env)
{...// env->imbalance 表示还有多少不均衡的负载需要迁移if (env->imbalance <= 0)return 0;// while 循环遍历最繁忙的就绪队列中所有的进程while (!list_empty(tasks)) {...// 在can_migrate_task()函数中判断哪些进程可以迁移,哪些进程不能迁移。// 不适合迁移的原因://	一是进程允许运行的CPU位图的限制(cpus_allowed)//	二是当前进程正在运行//	三是热高速缓存(cache-hot)的问题//	四如果进程负载的一半大于要迁移负载总量(env->imbalance),则该进程也不适合迁移if (!can_migrate_task(p, env))goto next;load = task_h_load(p);if (sched_feat(LB_MIN) && load < 16 && !env->sd->nr_balance_failed)goto next;if ((load / 2) > env->imbalance)goto next;// detach_task()函数让进程退出就绪队列,然后设置进程的运行CPU为迁移目的地CPU,并设置p->on_rq为// TASK_ON_RQ_MIGRARINGdetach_task(p, env);// 把要迁移的进程添加到env->tasks链表中list_add(&p->se.group_node, &env->tasks);detached++;// 递减不均衡负载总量 env->imbalanceenv->imbalance -= load;...}return detached;
}

load_balance()->attach_tasks()

// 把detach_tasks()分离出来的进程,重新添加到目标就绪队列中
static void attach_tasks(struct lb_env *env)
{...// 遍历env->tasks链表while (!list_empty(tasks)) {...// 把进程添加到目标CPU的就绪队列中attach_task(env->dst_rq, p);}...
}

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

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

相关文章

windows文档格式转换的实用工具

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

CSDN 编辑器设置图片缩放和居中

CSDN 编辑器设置图片缩放和居中 文章目录 CSDN 编辑器设置图片缩放和居中对齐方式比例缩放 对齐方式 Markdown 编辑器插入图片的代码格式为 ![图片描述](图片路径)CSDN 的 Markdown 编辑器中插入图片&#xff0c;默认都是左对齐&#xff0c;需要设置居中对齐的话&#xff0c;…

如何写好Stable Diffusion的prompt

Stable Diffusion是一种强大的文本到图像生成模型&#xff0c;其效果在很大程度上取决于输入的提示词&#xff08;Prompt&#xff09;。以下是一些关于如何编写有效的Stable Diffusion Prompt的秘诀&#xff1a; 明确描述&#xff1a;尽量清晰地描述你想要的图像内容。使用具体…

论文阅读——SpectralGPT

SpectralGPT: Spectral Foundation Model SpectralGPT的通用RS基础模型&#xff0c;该模型专门用于使用新型3D生成预训练Transformer&#xff08;GPT&#xff09;处理光谱RS图像。 重建损失由两个部分组成&#xff1a;令牌到令牌和频谱到频谱 下游任务&#xff1a;

SQL日期函数

文章目录 1.获取日期时间函数1.1 获取当前日期时间1.2 获取当前日期1.3 获取当前时间 2.日期格式化★★★2.1 日期转指定格式字符串2.2 字符串转日期 3.日期间隔3.1 增加日期间隔 ★★★3.2 减去一个时间间隔★★★3.3 日期相差天数&#xff08;天&#xff09;3.4 相差时间&…

FFmpeg-aac、h264封装flv及时间转换

文章目录 时间概念流程api核心代码 时间概念 dts: 解码时间戳, 表示压缩帧的解码时间 pts: 显示时间戳, 表示将压缩帧解码后得到的原始帧的显示时间 时间基: time_base &#xff0c; 通常以ms为单位 时间戳: timestamp , 多少个时间基 真实时间&#xff1a;time_base * timest…

谷歌(edge)浏览器过滤,只查看后端发送的请求

打开F12 调试工具 选择Network 这是我们会发现 什么图片 文件 接口的请求很多很多&#xff0c;我们只需要查看我们后端发送的请求是否成功就好了 正常情况我们需要的都是只看接口 先点击这里这个 过滤 我们只需要点击 Fetch/XHR 即可过滤掉其他请求信息的展示 这样烦恼的问题就…

vulhub中GitLab 远程命令执行漏洞复现(CVE-2021-22205)

GitLab是一款Ruby开发的Git项目管理平台。在11.9以后的GitLab中&#xff0c;因为使用了图片处理工具ExifTool而受到漏洞CVE-2021-22204的影响&#xff0c;攻击者可以通过一个未授权的接口上传一张恶意构造的图片&#xff0c;进而在GitLab服务器上执行任意命令。 环境启动后&am…

ChatGPT提示词方法的原理

关于提示词&#xff0c;我之前的一些文章可以参考&#xff1a; 【AIGC】AI作图最全提示词prompt集合&#xff08;收藏级&#xff09;https://giszz.blog.csdn.net/article/details/134815245?ydrefereraHR0cHM6Ly9tcC5jc2RuLm5ldC9tcF9ibG9nL21hbmFnZS9hcnRpY2xlP3NwbT0xMDExL…

Tomcat Seeion 集群

部署&#xff1a;nginx服务器&#xff1a;11-11&#xff1b;tomcat1:11-3; tomcat2:11-6 nginx服务器11-11做搭建&#xff1a; [rootmcb-11 ~]# systemctl stop firewalld [rootmcb-11 ~]# setenforce 0 [rootmcb-11 ~]# yum install epel-release.noarch -y [rootmcb…

华为三层交换机:ACL的基本实验

实验要求&#xff1a; PC1不允许访问PC3&#xff0c;PC3可以访问PC1 分析问题&#xff1a; PC1不允许访问PC3&#xff0c;问题中含有“目标地址”则我们需要设置目标地址&#xff0c;这样基本ACL是不行的&#xff0c;必须使用高级ACL [sw1]acl ? INTEGER<2000-2999>…

Flutter 多语言自动化本地化生成器

Flutter 多语言自动化本地化生成器 这是一个为Flutter设计的插件&#xff0c;通过从Excel表格提取的CSV文件自动生成Dart本地化文件&#xff0c;以简化应用程序本地化的流程。这个工具通过自动化创建多语言资源文件&#xff0c;简化了开发人员和翻译人员的工作流程。 录屏2024…

网络学习:邻居发现协议NDP

目录 前言&#xff1a; 一、报文内容 二、地址解析----NS/NA 目标的被请求组播IP地址 邻居不可达性检测&#xff1a; 重复地址检测 路由器发现 地址自动配置 默认路由器优先级和路由信息发现 重定向 前言&#xff1a; 邻居发现协议NDP&#xff08;Neighbor Discovery…

论文阅读——Rein

Stronger, Fewer, & Superior: Harnessing Vision Foundation Models for Domain Generalized Semantic Segmentation 一、引言 是一个对Domain Generalized Semantic Segmentation (DGSS)任务的视觉大模型的微调方法&#xff0c;即Rein。 Rein 专为 DGSS 任务量身定制&a…

数据结构——lesson8二叉树的实现

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

【玩转pandas系列】pandas数据结构—DataFrame

文章目录 前言一、DataFrame创建1.1 字典创建1.2 NumPy二维数组创建 二、DataFrame切片2.1 行切片2.2 列切片2.3 行列切片 三、DataFrame运算3.1 DataFrame和标量的运算3.2 DataFrame之间的运算3.3 Series和DataFrame之间的运算 四、DataFrame多层次索引4.1 多层次索引构造1.隐…

软考高级:架构描述语言 ADL 概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

【Linux】Ubuntu使用Netplan配置静态/动态IP

1、说明 Ubuntu 18.04开始,Ubuntu和Debian移除了以前的ifup/ifdown命令和/etc/network/interfaces配置文件,转而使用ip link set或者/etc/netplan/01-netcfg.yaml模板和sudo netplan apply命令实现网络管理。 Netplan 是抽象网络配置描述器,用于配置Linux网络。 通过netpla…

旧华硕电脑开机非常慢 电脑开机黑屏很久才显示品牌logo导致整体开机速度非常的慢怎么办

前提条件 电池需要20&#xff05;&#xff08;就是电池没有报废&#xff09;且电脑接好电源&#xff0c;千万别断电&#xff0c;电脑会变成砖头的 解决办法 更新bios即可解决&#xff0c;去对应品牌官网下载最新的bios版本就行了 网上都是一些更新驱动啊

【Godot4.2】颜色完全使用手册

概述 本篇简单汇总Godot中的颜色的构造和使用&#xff0c;内容包括了&#xff1a; RGB、RGBA&#xff0c;HSV以及HTML16进制颜色值、颜色常量等形式构造颜色颜色的运算以及取反、插值用类型化数组、紧缩数组或PNG图片形式存储多个颜色 构造颜色 因为颜色是一种视觉元素&…