CFS调度算法的思想和细节

CFS----------------完全公平调度算法 1、 调度器是一个操作系统的核心部分。可以比作是CPU时间的管理员。调度器主要负责选择某些就绪的进程来执行。不同的调度器根据不同的方法挑选出最适合运行的进程。目前Linux支持的调度器就有RT scheduler、Deadline scheduler、CFS scheduler及Idle scheduler等。 普通进程的优先级 CFS是Completely Fair Scheduler简称,即完全公平调度器。CFS的设计理念是在真实硬件上实现理想的、精确的多任务CPU。CFS调度器和以往的调度器不同 阅读详情


今天在邮件列表里面有位朋友问了一个问题,问题表述如下:
在唤醒进程的时候 , 发现在 check_preempt_wakeup() 中 . 会将 cfs_rq->next 设置为唤醒的进程 ,cfs_rq->last 设置为当前的运行进程 . 然后将要唤醒的进程重新入列 , 即 enqueue_task(). 在 pick_next_task_fair() 中选择下一个调度进程的时候 , 有这样的选择 pick_next_task_fair() ---> pick_next_entity():
static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)
{
        struct sched_entity *se = __pick_next_entity(cfs_rq);
        if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, se) < 1)
                return cfs_rq->next;
        if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, se) < 1)
                return cfs_rq->last;
        return se;
}
其 中 __pick_next_entity() 用来选择 rb_tree 中最左端的 se. 然后 , 再调用 wakeup_preempt_entity() 来判断 选择出的 se 是否可以抢占 cfs_rq->next 和 cfs_rq->last. 现在我的疑问是 : cfs_rq->next 和 cfs_rq->last 是拿来做什么的呢 ? 它是为了保证唤醒时的当前进程和被唤醒的进程优先运行吗 ? 但是 , 唤醒进程的时候已经调整了它的 vruntime, 并且调用 enqueue_task() 入 列 , 这样 , 它在选择下一个进程的时候 , 为什么直接按照 vruntime 值来调度呢 ?
<-
问题描述结束
     
之所以有这样的疑问就是因为这位朋友没有从全局去考虑和理解 cfs 调度算法,而迷失在了局部的代码细节,这在读 linux 源代码的时候是一大 忌, linux 的设计思想是很好很模块化很清晰的,但是具体到代码细节就不是这么美好了,这其实是一个编程习惯问题而不是什么设计问题。解决上述的问题很 容易,其实只要找一下 check_preempt_wakeup 的调用点就会发现,并不是仅仅在唤醒进程的时候才调用的,比如在更改进程优先级或者创建新 进程或者迁移进程的时候都要调用它。要点就是,如果入队的时候没有更新 vruntime ,那么就有必要将 pick_up_next 的结果也就是红黑树最左 下的结点和新入队的做一番比较,因为入队时的情况是不确定的,如果没有更新入队进程的 vruntime 但是其权值已经改变或者绑定的运行处理器已经改变的 话,比如迁移进一个新 cpu 的运行队列,那么就不能用它保留的原来的 vruntime 来竞争 cpu 了,但是又不想破坏代码的简洁而重新每次都在入队时计算 vruntime ,那么只有先保留一个 cfsq->next 字段用来记录这个需要仲裁的新进程了,另外调度粒度也是一个很重要的参数,粒度过小的话 在 cfs 的平滑调度机制下就会发生频繁调度,系统的大部分时间都用到调度上了,对于调度器来说这样简直太精确了,但是对于整个系统来说调度仅仅是一个确保 公平的手段而已,仅是个服务,不能过多的占用处理器,相反调度粒度过大就又会回到时间片调度的那种低效状态,因此调度粒度是一个很重要的参数。判断入队时 是否更新 vruntime 的是 enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup) 的 waleup 参数,比如在 __migrate_task 中就有调用 activate_task(rq_dest, p, 0);check_preempt_curr(rq_dest, p); 另外唤醒一个新进程的情况下入队时 wakeup 参数也可能为 0 ,那么就不更新 vruntime ,这样就必须在 pick_up_next 的时候仲裁 了。
     
为何计算新入队的 vruntime 会破坏代码的简洁呢? linux 内核由很多人编写,因此代码的模块化显得很重要,最好是只在一个地方修改一个变量而不是 到处都在修改,那么常规修改 vruntime 的地方就是 update_curr 了,当然唤醒睡眠进程或者新进程的时候也要修改,但是那不是常规修改,于是 要想修改调度实体的 vruntime 就必须使其成为 curr ,然后在更新 curr 时期更新其 vruntime ,于是就只有将未决进程,也就是 cfsq 的 next ,和 pick_up_next 的结果进行比较,因为也只有 pick_up_next 的结果有资格参与比较,比较的另一方就是未决进程,它是确定 的,就是 cfsq->next 。首先看看这个神秘的 wakeup_preempt_entity 吧,待会儿再看看 place_entity-- 另一 个设置 vruntime 的地方:
static int wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
         s64 gran, vdiff = curr->vruntime - se->vruntime;  //vruntime
的差值
         if (vdiff <= 0)    //cfsq
的 vruntime 是单调递增的,也就是一个基准,各个进程的 vruntime 追赶竞争 cfsq 的 vruntime ,如果 curr 的 vruntime 比较小,说明 curr 更加需要补偿,即 se 无法抢占 curr
                 return -1;
         gran = wakeup_gran(curr);  //
计算 curr 的最小抢占期限粒度
         if (vdiff > gran)          //
当差值大于这个最小粒度的时候才抢占,这可以避免频繁抢占。
                 return 1;
         return 0;
}
static unsigned long wakeup_gran(struct sched_entity *se)
{
         unsigned long gran = sysctl_sched_wakeup_granularity;       //NICE_0_LOAD
的基准最小运行期限
         if (!sched_feat(ASYM_GRAN) || se->load.weight > NICE_0_LOAD)     //
非 NICE_0_LOAD 的进程要计算其自己的最小运行期限
                 gran = calc_delta_fair(sysctl_sched_wakeup_granularity, se); //
计算进程运行的期限,即抢占的粒度。
         return gran;
}
看 完了上面两个函数后就可以说 cfs 的设计思想了,这里不再谈 2.6.23 的内核,仅以 2.6.25 以后的为准,本文讨论 2.6.28 的内核,这些新内核的 cfs 算法和最开始的 2.6.23 的 cfs 的思想有些小不同。在 cfs 中,没有确定时间片的概念,不再像以前那样根据进程的优先值为进程分配一个确定的时 间片,在这个时间片过期后发生无条件进程切换,而未过期时则可以发生抢占。这个时间片的思想从早期的分时 unix 继承而来,已经不再适应现在抢占,特别是 内核抢占无处不在的新世界了,如今的处理器速度大大提高,时钟大大精确了,另外外设越来越智能,为 cpu 分担的工作越来越多, cpu 仍然作为计算机的中心 就不能对外设为所欲为了,外设的中断更加频繁和有效,但是如果应用这些外设的运行于 cpu 的进程如果还是延迟响应的话,事情就会显得有些不和谐。这就要求 调度器必须改进,以前的时钟不精确,中断不频繁,外设少,总线带宽低,应用不丰富等原因使得内核非抢占是可以忍受的,后来虽然有了内核抢占但是还是和硬件 格格不入,应用程序总是看起来反映迟缓或者不公平, cfs 调度器在这种情况下由运而生, cfs 的总体思想就是尽量使进程公平的被调度,这种公平不是同等对 待所有进程,而是按照进程权值百分之百履行优先级承诺。 cfs 算法意味着 cpu 的调度和硬件行为的步调更加的一致,同时也免去了复杂的行为预测算法,这比 较符合这个世界的规则。按照以前的时间片方式硬件的时间片和操作系统调度的软件时间片差好几个数量级,而且软件已经不能做的更加精确了,因此必须抛弃这种 方式, cfs 调度器看上去更像是一部无级变速器,既然跟不上硬件就别用时间片跟,到最后不但还是跟不上,而且还使得时间片调度行为丧失了世界原本的性质, 所以才有了那么多复杂的预测算法。 cfs 回归了世界的本质,就是公平的履行承诺。在 2.6.25 以后 cfs 中在每个队列设置了一个字段,就是 vruntime ,这个字段在系统运行期间单调增长,各个进程自己也有一个 vruntime ,它们相互追赶向这个 vruntime 看齐,并且可以最终将自 己的 vruntime 设置为队列的 vruntime ,处理器总是挑选 vruntime 小的运行,这其实是一种对掉队者的补偿,这就是公平,每个进程的 vruntime 相当于它自己的虚拟时钟,如果每个进程的虚拟时钟同步,各个进程就可以说是公平的,相互追赶 vruntime 并且向 cfsq 的 vruntime 看齐就是保持虚拟时钟同步。对于不同权值的进程,它们的虚拟时钟快慢不同,这才是公平的真正含义,比方说权值大的进程的虚拟时钟 10 秒走 一个字,而权值小的进程虚拟时钟 1 秒就走一个字,虚拟时钟都走一个字就同步了,但是权值大的进程运行了 10 秒而小权值的进程才运行 1 秒,这就是实质。现在 看看 place_entity :
static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
         u64 vruntime = cfs_rq->min_vruntime;
         if (initial && sched_feat(START_DEBIT))  //
如果是新进程第一次要入队,那么就要初始化它的 vruntime ,一般就把 cfsq 的 vruntime 给它就可以,但是如果当前运行的所有进程被承诺 了一个运行周期,那么则将新进程的 vruntime 后推一个他自己的 slice ,实际上新进程入队时要重新计算运行队列的总权值,总权值显然是增加了,但 是所有进程总的运行时期并不一定随之增加,则每个进程的承诺时间相当于减小了,就是减慢了进程们的虚拟时钟步伐。
                 vruntime += sched_vslice(cfs_rq, se);   //sched_vslice
计算的结果就是这个新进程
         if (!initial) {
...//
忽略一种情况
        vruntime = max_vruntime(se->vruntime, vruntime);  //
如果是唤醒已经存在的进程,则单调附值
         }
         se->vruntime = vruntime;
}
sched_vslice
很重要,它其实就是一个有意义的值,我们看一下:
static u64 sched_vslice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
         return calc_delta_fair(sched_slice(cfs_rq, se), se);
}
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)  //
返回一个理想的运行时间
{
         unsigned long nr_running = cfs_rq->nr_running;
         if (unlikely(!se->on_rq))
                 nr_running++;
         return calc_delta_weight(__sched_period(nr_running), se); //
返回一个进程 se 的应该运行的时间
}
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 除以总权值,得到一个值,该值的单位就是 vruntime 的单位。
}
static u64 __sched_period(unsigned long nr_running)  //
返回一个值,该值是一个每个进程最少运行一趟的总时间
{
         u64 period = sysctl_sched_latency;
         unsigned long nr_latency = sched_nr_latency;
         if (unlikely(nr_running > nr_latency)) {
                 period = sysctl_sched_min_granularity;
                 period *= nr_running;
         }
         return period;
}
以 上几个函数很重要,很多 cfs 中所谓的 “ 值 ” 都是上述函数计算而来的,比如在时钟中断的 tick 节拍函数中,为了测试当前进程是否需要被抢占调用了 check_preempt_tick ,该函数进一步调用了 sched_slice 获得了一个理想值,该理想值描述了这个当前进程实际上应该运行的时间, 如果这个进程实际运行的时间超过了这个理想值,那么就意味着该抢占了。
static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
         unsigned long ideal_runtime, delta_exec;
         ideal_runtime = sched_slice(cfs_rq, curr);
         delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
         if (delta_exec > ideal_runtime)
                 resched_task(rq_of(cfs_rq)->curr);
}
    
今天在一个问题的激励下,我终于写了一篇描述 cfs 的文章,呵呵

 

《Linux完全公平调度(CFS)深度解剖(安卓流畅度核心)》 完全公平调度(Completely Fair Scheduler,CFS)是Linux内核中用于进程调度的核心算法,其核心目标是实现。 阅读详情

相关推荐

CFS及RT调度整体介绍

linux内核调度子系统——CFS调度器/组调度,cfs,组调度,lazy preemption

zhaoxin1989的博客 1万+

CFS(完全公平调度算法)

CFS

qq_31098037的博客 1万+

CFS调度算法 详细解析

详细讲解了CFS调度算法相关代码,包含红黑树,Schedule_tick,task_pick等相关调度、人物选择细节

CFS调度算法

1、CFS调度算法,顾名思义就是完全公平调度策略。 比方说,调度延迟时间是10ms,存在两个进程A和B,那么两个进程分别占用CPU的时间是5ms。然而,阶级总是存在的,毕竟有些进程高贵些,需要消耗更多的时间。因此引入了nice值, 假如A进程nice值是0,对应的权重prio_to_weight是1024;B进程nice值是1,对应的权重prio_to_weight是820。因此,相对应的,A进程占用CPU的时间就变成了10 * 1024 / (1024 + 820)约5.6ms,B进程占用CPU时

sydyh43的博客 3985

基于Arduino与脑电模块的意念控制LED灯环实现

脑机接口(BCI)技术通过采集和处理脑电信号,实现人脑与外部设备的直接交互。其核心原理是利用传感器捕捉大脑活动产生的微弱电信号,经过放大、滤波和特征提取,转换为可识别的指令。这项技术在医疗康复、神经反馈训练和人机交互等领域具有重要价值。本文聚焦于一个具体的工程实践:使用消费级NeuroSky TGAM模块采集专注度与放松度数据,通过Arduino平台解析信号,并驱动WS2812B LED灯环实现实时可视化反馈。项目涵盖了硬件选型、电路搭建、数据解析和光效映射等关键环节,为脑机接口入门和嵌入式开发提供了完整的

weixin_28721917的博客 278

谈谈 FOC和BLDC 电机控制

更具体地说,通过 “Park 变换”,我们可以把三相交流电流信号转换成两相直流电流信号,从而可以把三相交流电机的控制变得和两相直流电机控制一样简单。在内环 “电流环”中,一些比较重要的模块有 “Clark 变换” 和 “Park 变换” 等数学模块、电机电流信号检测模块以及能够提供电机的转速和转角位置信息的模块。日前,Qorvo 电机控制应用高级系统/应用工程师张绍发表了一个与 BLDC 和 FOC 相关的演讲,科普了 FOC 的相关知识,还讲解了 Qorvo 用于简化 FOC 实施方案的方法。

U_YAO的博客 2118

CFS调度算法的思想与实现

CFS(Complete Fair Schedule)调度算法的思想是为每一个task维护一个虚拟的运行时间vruntime 调度程序优先选择vruntime值最小的任务执行,之所以引入vruntime的概念,是为了支持优先级调度。vruntime其实是基于task的实际运行时间及优先级权重计算出来的值,其计算公式如下:vruntime += delta_exec * NICE_0_LOAD / weightvruntime:虚拟运行时间 delta_exec: 实际的执行时间 NICE_0_LOAD:进程

aigourensheng的博客 1299

Cesium创建无人机视锥,同步视锥与鹰眼地图视角,实现视锥键盘控制。解决视锥初始化位置不准确问题等

本文描述了一个基于Cesium的3D视锥体可视化系统开发过程。系统实现了主视图与鹰眼视图的双屏联动,支持通过键盘控制视锥体的空间移动(WASD前后左右,Q/E旋转,C/Z升降),并实时同步更新两个视图。关键技术包括:1)基于主视图Scene创建Camera实现视锥体精确绘制;2)通过PerspectiveFrustum设置视锥参数;3)建立双视图的相机位置及视角同步机制。目前暂未实现视锥体far/near边界在鹰眼视图中的可视化,该系统可应用于无人机视角监控等三维场景交互需求。

coder_bin的博客 1590

进程调度源码分析之CFS

在文章开始之前想要说明一下,文中内容主要参考《奔跑吧Linux内核》入门篇及卷一部分,源码的注释部分参考了gitte上大神的源码注释(Zhe Qiao/linux 5.0注释)链接如下:https://gitee.com/residual_nozzle_on_dust/linux5-0-comments/tree/master。

abobob的博客 642

Vue3+uniapp开发必备:5分钟搞定unplugin-auto-import自动导入配置(附常见报错解决)

本文详细介绍了在Vue3+uniapp项目中配置unplugin-auto-import插件实现自动导入的方法。通过该配置,开发者无需手动导入ref、onLoad等常用API,可显著提升编码效率和团队协作规范性。文章提供了从安装、TypeScript集成到高级用法的完整指南,并附带了常见报错的解决方案。

weixin_29008805的博客 961

QT应用编程: 基于UDP协议设计的大文件传输软件

一、环境介绍 QT版本: 5.12.6 编译器: MinGW 32 传输协议: UDP 功能介绍:软件由客户端和服务器组成,客户端通过 UDP协议不断循环地向服务端发送文件,文件传输速率可以达到10MB/s以上,文件传输后支持自动删除,客户端上可以支持每分钟创建一个文件并以时间戳命名,每个生成的文件可以设置大小,默认大小为6GB; 服务端收到文件之后,将文件进行存储到本地,可以指定时间自动删除文件;服务端可以动态计算传输速率,并写入日志文件; 服务器可以支持同时接收多个客户端的文件上传。...

2803

cfs调度算法JAVA实现_CFS调度算法的思想和细节

今天在邮件列表里面有位朋友问了一个问题,问题表述如下:在唤醒进程的时候,发现在check_preempt_wakeup()中.会将 cfs_rq->next设置为唤醒的进程,cfs_rq->last设置为当前的运行进程.然后将要唤醒的进程重新入列,即 enqueue_task().在pick_next_task_fair()中选择下一个调度进程的时候,有这样的选择 pick_next_...

weixin_39937412的博客 378

YOLOv8推理程序

YOLOv8单独推理,有时候我们自定义的模块算子无法正常转换为其他框架,而且需要做成应用,因此需要一个单独推理的程序,返回的是识别后的照片还有各个类别及其对应数量。文章最后给出Flask封装为Server以及调用的实例还有Client请求代码,支持几十路多线程并发,只需加载一次模型,后边推理即可。

AIOT魔法师 698

STM32F103RCT6最小系统板全流程:原理图、PCB布局、焊接与调试

在嵌入式硬件开发中,最小系统板是MCU正常工作的核心载体,其设计质量直接影响产品稳定性与开发效率。从STM32F103RCT6的资源评估入手,需统筹Flash、SRAM、引脚数与外设映射,再依次完成时钟电路、电源拓扑、复位/BOOT配置、SWD下载接口等关键模块设计。PCB阶段,晶振靠近MCU放置、去耦电容贴近电源引脚、底层完整铺地等原则能有效规避起振失败与信号干扰。配合标准外设库和printf重定向,通过点灯与串口日志可快速验证硬件链路。以无刷电机控制器改版为应用背景,完整梳理了从原理图绘制、手工焊接、上

weixin_34297704的博客 342

PyTorch深度学习实战(7)——批大小对神经网络训练的影响

在神经网络中,批( batch )是指一次输入网络进行训练或推断的一组样本。批处理( batch processing )是指将这一组样本同时输入网络进行计算的操作。本节中首先介绍批( Batch )的基本概念,并且介绍批大小在神经网络训练过程中的影响。

盼小辉丶的博客 1万+

【红外DDE算法】一种应用于高动态范围红外可视化的算法

【红外DDE算法】一种应用于高动态范围红外可视化的算法 - 这个算法背后的动机 - 展示这个算法的流程 - 然后讨论各个可调参数的意义。

sunwindl91181的博客 1682

linux非实时任务调度CFS算法分析

CFS算法是基于一个理想的,精确的多任务CPU设计的(在这种CPU下,各个任务的运行速度是一 致的),实际上这个CPU并不存在,因此,算法模拟了硬件的实现,通过分割CPU的时间使得每个任务的 运行时间总是相等的。实现上,在每个任务控制块的调度实体上有一个变量vruntime保存了本任务的运 行时间,调度时找时间最小的任务运行,使得最后每个任务的运行时间都相等。 当然,不会当运行时间最小的任

Skylar 1924

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

第一次作业:关于Linux进程模型及CFS调度器分析

第一次作业内容   挑选一个开源的操作系统,深入源码分析其进程模型,具体包含如下内容: 操作系统是怎么组织进程的 进程状态如何转换(给出进程状态转换图) 进程是如何调度的 谈谈自己对该操作系统进程模型的看法 1. 前言   本文基于Linux Kernel 2.6.28 的源代码,分析本版本linux的进程模型和CFS调度器的基本算法。  源码浏览地址:https://e...

weixin_30725467的博客 149

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

鼠眼再看Linux调度器[2]【装】

四、CFS。 CFS现在还是非常新的调度实现,并且本人水平也十分有限,有鉴于此,这里很可能存在不当的地方甚至错误,权当抛砖引玉,不妥之处还请诸位有识之士不吝指正。 在讨论CFS之前,我们先回顾一下现有的调度器实现:这是一个巧妙的双优先级数组方案。为了尽量避免出现“过期数组”中的任务出现饥饿现象,内核使用了一些启发式的方法判断是否出现了饥饿。在绝大多数情况下,这个实现给了我们非常好...

云计算? 97

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Linux CFS 调度器 (1):概述

Linux,调度器,CFS(Completely Fair Scheduler)

JiMoKuangXiangQu的专栏 2068

linux cfs进程调度 配置,linux2.6 CFS调度算法分析

1.概述CFS(completely fair schedule)是最终被内核采纳的调度器。它从RSDL/SD中吸取了完全公平的思想,不再跟踪进程的睡眠时间,也不再企图区分交互式进程。它将所有的进程都统一对待,这就是公平的含义。CFS的算法和实现都相当简单,众多的测试表明其性能也非常优越。CFS 背后的主要想法是维护为任务提供处理器时间方面的平衡(公平性)。这意味着应给进程分配相当数量的处理器。分...

weixin_29459473的博客 301

linux cfs调度与负载均衡漫谈(一:核心基础概念)

本文相关内核代码来自Linux 6.9版本众所周知,在linux内核中,有一共有5个调度器类:大部分都是老生常谈的概念了,只是一般会有人搞混调度策略和,这两个是完全不同的概念,这里简单提两句:在linux内核的选核策略代码中,经常会看到对cpu idle的情况判断,例如: 那么其中就是用来判断target cpu是否进入了idle task, 而 则用来判断target cpu是不是正在执行的cfs任务,这两种情况对cpu来说都可以说是"空闲"的,当然前一个要更闲一点。详情见下面代码: sched_idl

nameAlreadyExist的博客 1709

linux cfs调度算法_CFS:Linux中完全公平的流程调度

linux cfs调度算法 Linux采用模块化方法进行处理器调度,因为可以使用不同的算法来调度不同的进程类型。 调度类指定哪种调度策略适用于哪种类型的过程。 完全公平调度(CFS)在2007年成为Linux 2.6.23内核的一部分,它是常规(相对于实时)进程的调度类,因此命名为SCHED_NORMAL 。 CFS适用于桌面环境中典型的交互式应用程序,但可以将其配置为SCHED_BATCH...

cumo7370的博客 602
上一篇: 至今不敢写一篇关于cfs的文章
下一篇: fork进程时资源的深拷贝和浅拷贝
linuxcode_turing
博客等级 码龄17年 0粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值