现在的 Linux 内核和 Linux 2.6 的内核有多大区别?

origin:

http://www.zhihu.com/question/35484429/answer/62964898?from=timeline&isappinstalled=0
这个问题挺大的。

2.6 时代跨度非常大,从2.6.1 (2003年12月发布) 到 2.6.39(2011年5月发布), 跨越了 39 个大版本。
3.0(原计划的 2.6.40, 2011年7月发布) 到 3.19(2015年2月发布)。
4.0(2015年4月发布)到4.2(2015年8月底发布)。

总的来说,从进入2.6之后,每个大版本跨度开发时间大概是 2 - 3 个月。2.6.x , 3.x, 4.x,数字的递进并没有非常根本性,非常非常非常引人注目的大变化,但每个大版本中都有一些或大或小的功能改变。主版本号只是一个数字而已。不过要直接从 2.6.x 升级 到 3.x, 乃至 4.x,随着时间间隔增大,出问题的机率当然大很多。

个人觉得 Linux 真正走入严肃级别的高稳定性,高可用性,高可伸缩性的工业级别内核大概是在 2003 年后吧。一是随着互联网的更迅速普及,更多的人使用、参与开发。二也是社区经过11年发展,已经慢慢摸索出一套很稳定的协同开发模式,一个重要的特点是 社区开始使用版本管理工具进入管理,脱离了之前纯粹手工(或一些辅助的简陋工具)处理代码邮件的方式,大大加快了开发的速度和力度。

因此,我汇总分析一下从 2.6.12 (2005年6月发布,也就是社区开始使用 git 进行管理后的第一个大版本),到 4.2 (2015年8月发布)这中间共 51个大版本,时间跨度 10年的主要大模块的一些重要的变革。
个人时间,精力,能力有限, 有问题欢迎指出,交流。

慢慢更新,保证填完所有坑,借此机会做个整理 ;-)


  • 调度子系统(scheduling)
概述:Linux 是一个遵循 POSIX 标准的类 Unix 操作系统(然而它并不是 Unix 系统[1]),POSIX 1003.1b 定义了调度相关的一个功能集合和 API 接口[2]。调度器的任务是分配 CPU 运算资源,并以协调效率和公平为目的。 效率可从两方面考虑: 1) 吞吐量(throughput) 2)延时(latency)。不做精确定义,这两个有相互矛盾的衡量标准主要体现为两大类进程:一是 CPU 密集型,少量 IO 操作,少量或无与用户交互操作的任务(强调吞吐量,对延时不敏感,如高性能计算任务 HPC), 另一则是 IO 密集型, 大量与用户交互操作的任务(强调低延时,对吞吐量无要求,如桌面程序)。 公平在于有区分度的公平,多媒体任务和数值计算任务对延时和限定性的完成时间的敏感度显然是不同的。
为此, POSIX 规定了操作系统必须实现以下 调度策略(scheduling policies), 以针对上述任务进行区分调度:
- SCHED_FIFO
- SCHED_RR

这两个调度策略定义了对实时任务,即对延时和限定性的完成时间的高敏感度的任务。前者提
供 FIFO 语义,相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务;后 者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾
部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可
以根据需要用 sched_setscheduler() API 设置策略。
- SCHED_OTHER
此调度策略包含除上述实时进程之外的其他进程,亦称普通进程。采用分时策略,根据动态优
先级(可用 nice() API设置),分配 CPU 运算资源。 注意:这类进程比上述两类实时进程优先
级低,换言之,在有实时进程存在时,实时进程优先调度。


Linux 除了实现上述策略,还额外支持以下策略:
- SCHED_IDLE 优先级 最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者)
- SCHED_BATCH 是 SCHED_OTHER 策略的分化,与 SCHED_OTHER 策略一样,但针对吞吐量优化
- SCHED_DEADLINE 是新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。

除了完成以上基本任务外,Linux 调度器还应提供高性能保障,对吞吐量和延时的均衡要有好的优化;要提供高可扩展性(scalability)保障,保障上千节点的性能稳定;对于广泛作为服务器领域操作系统来说,它还提供丰富的组策略调度和节能调度的支持。

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 重要功能和时间点 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

- 抢占支持(preemption): 2.6 时代开始支持
(具体时间难考,是在 2.5 这个奇数版本中引入,可看此文章[3], 关于 Linux 版本规则,可看我文章[4]).

可抢占性,对一个系统的调度延时具有重要意义。2.6 之前,一个进程进入内核态后,别的进程无法抢占,只能等其完成或退出内核态时才能抢占, 这带来严重的延时问题,2.6 开始支持内核态抢占。

- 普通进程调度器(SCHED_OTHER)之纠极进化史:
Linux 一开始,普通进程和实时进程都是基于优先级的一个调度器, 实时进程支持 100 个优先级,普通进程是优先级小于实时进程的一个静态优先级,所有普通进程创建时都是默认此优先级,但可通过 nice() 接口调整动态优先级(共40个). 实时进程的调度器比较简单,而普通进程的调度器,则历经变迁[5]:

  1) O(1) 调度器: 2.6 时代开始支持(2002年引入)。顾名思义,此调度器为O(1)时间复杂度。该调度器以修正之间的O(n) 时间复杂度调度器,以解决扩展性问题。为每一个动态优先级维护队列,从而能在常数时间内选举下一个进程来执行。

2)) 夭折的 RSDL(The Rotating Staircase Deadline Scheduler)调度器, 2007 年 4 月提出,预期进入 2.6.22, 后夭折。

O(1) 调度器存在一个比较严重的问题: 复杂的交互进程识别启发式算法- 为了识别交互性的和批处理型的两大类进程,该启发式算法融入了睡眠时间作为考量的标准,但对于一些特殊的情况,经常判断不准,而且是改完一种情况又发现一种情况。

Con Kolivas (八卦:这家伙白天是个麻醉医生)为解决这个问题提出 RSDL(The Rotating Staircase Deadline Scheduler)算法。该算法的亮点是对公平概念的重新思考: 交互式(A)和批量式(B)进程应该是被完全公平对待的,对于两个动态优先级完全一样的 A, B 进程, 它们应该被同等地对待,至于它们是交互式与否(交互式的应该被更快调度), 应该从他们对分配给他们的时间片的使用自然地表现出来,而不是应该由调度器自作高明地根据他们的睡眠时间去猜测。这个算法的核心是Rotating Staircase, 是一种衰减式的优先级调整,不同进程的时间片使用方式不同,会让它们以不同的速率衰减(在优先级队列数组中一级一级下降,这是下楼梯这名字的由来), 从而自然地区分开来进程是交互式的(间歇性的少量使用时间片)和批量式的(密集的使用时间片)。具体算法细节可看这篇文章: The Rotating Staircase Deadline Scheduler [LWN.net]

3) 完全公平的调度器(CFS), 2.6.23(2007年10月发布)

Con Kolivas 的完全公平的想法启发了原 O(1)调度器作者 Ingo Molnar, 他重新实现了一个新的调度器,叫 CFS。新调度器的核心同样是 完全公平性, 即平等地看待所有普通进程,让它们自身行为彼此区分开来,从而指导调度器进行下一个执行进程的选举。

具体说来,此算法基于一个理想模型。想像你有一台无限个 相同计算力的 CPU, 那么完全公平很容易,每个 CPU 上跑一个进程即可。但是,现实的机器 CPU 个数是有限的,超过 CPU 个数的进程数不可能完全同时运行。因此,算法为每个进程维护一个理想的运行时间,及实际的运行时间,这两个时间差值大的,说明受到了不公平待遇,更应得到执行。

至于这种算法如何区分交互式进程和批量式进程,很简单。交互式的进程大部分时间在睡眠,因此它的实际运行时间很小,而理想运行时间是随着时间的前进而增加的,所以这两个时间的差值会变大。与之相反,批量式进程大部分时间在运行,它的实际运行时间和理想运行时间的差距就较小。因此,这两种进程被区分开来。

CFS 的测试性能比 RSDS 好,并得到更多的开发者支持,所以它最终替代了 RSDL 在 2.6.23 进入内核,一直使用到现在。可以八卦的是,Con Kolivas 因此离开了社区,不过他本人否认是因为此事,心生龃龉。后来,2009 年,他对越来越庞杂的 CFS 不满意,认为 CFS 过分注重对大规模机器,而大部分人都是使用少 CPU 的小机器,开发了 BFS 调度器[6], 这个在 Android 中有使用,没进入 Linux 内核。

- 有空时再跑 SCHED_IDLE, 2.6.23(2007年10月发布)

此调度策略和 CFS 调度器在同一版本引入。系统在空闲时,每个 CPU 都有一个 idle 线程在跑,它什么也不做,就是把 CPU 放入硬件睡眠状态以节能(需要特定CPU的driver支持), 并等待新的任务到来,以把 CPU 从睡眠状态中唤醒。如果你有任务想在 CPU 完全 idle 时才执行,就可以用 sched_setscheduler() API 设置此策略。


-吭哧吭哧跑计算 SCHED_BATCH, 2.6.16(2006年3月发布)


概述中讲到 SCHED_BATCH 并非 POSIX 标准要求的调度策略,而是 Linux 自己额外支持的。
它是从 SCHED_OTHER 中分化出来的, 和 SCHED_OTHER 一样,不过该调度策略会让采用策略的进程比 SCHED_OTHER 更少受到 调度器的重视。因此,它适合非交互性的,CPU 密集运算型的任务。如果你事先知道你的任务属于该类型,可以用 sched_setscheduler() API 设置此策略。

在引入该策略后,原来的 SCHED_OTHER 被改名为 SCHED_NORMAL, 不过它的值不变,因此保持 API 兼容,之前的 SCHED_OTHER 自动成为 SCHED_NORMAL, 除非你设置 SCHED_BATCH。

- 十万火急,限期完成 SCHED_DEADLINE, 3.14(2014年3月发布)


此策略支持的是一种实时任务。对于某些实时任务,具有阵发性(sporadic), 它们阵发性地醒来执行任务,且任务有 deadline 要求,因此要保证在 deadline 时间到来前完成。为了完成此目标,采用该 SCHED_DEADLINE 的任务是系统中最高优先级的,它们醒来时可以抢占任何进程。

如果你有任务属于该类型,可以用 sched_setscheduler()sched_setattr() API 设置此策略。

更多可参看此文章: Deadline scheduling: coming soon? [LWN.net]

- 普通进程的组调度支持(Fair Group Scheduling), 2.6.24(2008年1月发布)


2.6.23 引入的 CFS 调度器对所有进程完全公平对待。但这有个问题,设想当前机器有2个用户,有一个用户跑着 9个进程,还都是 CPU 密集型进程;另一个用户只跑着一个 X 进程,这是交互性进程。从 CFS 的角度看,它将平等对待这 10 个进程,结果导致的是跑 X 进程的用户受到不公平对待,他只能得到约 10% 的 CPU 时间,让他的体验相当差。

基于此,组调度的概念被引入[6]。CFS 处理的不再是一个进程的概念,而是调度实体(sched entity), 一个调度实体可以只包含一个进程,也可以包含多个进程。因此,上述例子的困境可以这么解决:分别为每个用户建立一个组,组里放该用户所有进程,从而保证用户间的公平性。

该功能是基于控制组(control group, cgroup)的概念,需要内核开启 CGROUP 的支持才可使用。关于 CGROUP ,以后可能会写。

- 实时进程的组调度支持(RT Group Scheduling), 2.6.25(2008年4月发布)

该功能同普通进程的组调度功能一样,只不过是针对实时进程的。

- 组调度带宽控制((CFS bandwidth control) , 3.2(2012年1月发布)

组调度的支持,对实现多租户系统的管理是十分方便的,在一台机器上,可以方便对多用户进行 CPU 均分.然后,这还不足够,组调度只能保证用户间的公平,但若管理员想控制一个用户使用的最大 CPU 资源,则需要带宽控制.3.2 针对 CFS组调度,引入了此功能[8], 该功能可以让管理员控制在一段时间内一个组可以使用 CPU 的最长时间.

- 极大提高体验的自动组调度(Auto Group Scheduling), 2.6.38(2011年3月发布)

试想,你在终端里熟练地敲击命令,编译一个大型项目的代码,如 Linux内核,然后在编译的同时悠闲地看着电影等待,结果电脑却非常卡,体验一定很不爽.

2.6.38 引入了一个针对桌面用户体验的改进,叫做自动组调度.短短400多行代码[9], 就很大地提高了上述情形中桌面使用者体验,引起不小轰动.

其实原理不复杂,它是基于之前支持的组调度的一个延伸.Unix 世界里,有一个 会话(session) 的概念,即跟某一项任务相关的所有进程,可以放在一个会话里,统一管理.比如你登录一个系统,在终端里敲入用户名,密码,然后执行各种操作,这所有进程,就被规划在一个会话里.

因此,在上述例子里,编译代码和终端进程在一个会话里,你的浏览器则在另一个会话里.自动组调度的工作就是,把这些不同会话自动分成不同的调度组,从而利用组调度的优势,使浏览器会话不会过多地受到终端会话的影响,从而提高体验.

该功能可以手动关闭.

- 基于调度域的负载均衡, 2.6.7(2004年6月发布)


计算机依靠并行度来突破性能瓶颈,CPU个数也是与日俱增。最早的是 SMP(对称多处理), 所以 CPU共享内存,并访问速度一致。随着 CPU 个数的增加,这种做法不适应了,因为 CPU 个数的增多,增加了总线访问冲突,这样 CPU 增加的并行度被访问内存总线的瓶颈给抵消了,于是引入了 NUMA(非一致性内存访问)的概念。机器分为若干个node, 每个node(其实一般就是一个 socket)有本地可访问的内存,也可以通过 interconnect 中介机构访问别的 node 的内存,但是访问速度降低了,所以叫非一致性内存访问。Linux 2.5版本时就开始了对 NUMA 的支持[7]。

而在调度器领域,调度器有一个重要任务就是做负载均衡。当某个 CPU 出现空闲,就要从别的 CPU 上调整任务过来执行; 当创建新进程时,调度器也会根据当前负载状况分配一个最适合的 CPU 来执行。然后,这些概念是大大简化了实际情形。

在一个 NUMA 机器上,存在下列层级:
 1.每一个 NUMA node 是一个 CPU socket(你看主板上CPU位置上那一块东西就是一个 socket).
2.每一个socket上,可能存在两个核,甚至四个核。
 3.每一个核上,可以打开硬件多纯程(HyperThread)。

如果一个机器上同时存在这三人层级,则对调度器来说,它所见的一个逻辑 CPU其实是一人 HyperThread.处理同一个core 中的CPU , 可以共享L1, 乃至 L2 缓存,不同的 core 间,可以共享 L3 缓存(如果存在的话).

基于此,负载均衡不能简单看不同 CPU 上的任务个数,还要考虑缓存,内存访问速度.所以,2.6.7 引入了 调度域(sched domain) 的概念,把 CPU 按上述层级划分为不同的层级,构建成一棵树,叶子节点是每个逻辑 CPU, 往上一层,是属于 core 这个域,再往上是属于 socket 这个域,再往上是 NUMA 这个域,包含所有 CPU.

当进行负载均衡时,将从最低一级域往上看,如果能在 core 这个层级进行均衡,那最好;否则往上一级,能在socket 一级进行均衡也还凑合;最后是在 NUMA node 之间进行均衡,这是代价非常大的,因为跨 node 的内存访问速度会降低,也许会得不偿失,很少在这一层进行均衡.

这种分层的做法不仅保证了均衡与性能的平衡,还提高了负载均衡的效率.

关于这方面,可以看这篇文章: Scheduling domains [LWN.net]

- 更精确的调度时钟(HRTICK), 2.6.25(2008年4月发布)

CPU的周期性调度,和基于时间片的调度,是要基于时钟中断来触发的.一个典型的 1000 HZ 机器,每秒钟产生 1000 次时间中断,每次中断到来后,调度器会看看是否需要调度.

然而,对于调度时间粒度为微秒(10^-6)级别的精度来说,这每秒 1000 次的粒度就显得太粗糙了.

2.6.25引入了所谓的 高清嘀哒(High Resolution Tick), 以提供更精确的调度时钟中断.这个功能是基于 高清时钟(High Resolution Timer)框架,这个框架让内核支持可以提供纳秒级别的精度的硬件时钟(将会在时钟子系统里讲).

- 自动 NUMA 均衡(Automatic NUMA balancing ), 3.8(2013年2月发布)

NUMA 机器一个重要特性就是不同 node 之间的内存访问速度有差异,访问本地 node 很快,访问别的 node 则很慢.所以进程分配内存时,总是优先分配所在 node 上的内存.然而,前面说过,调度器的负载均衡是可能把一个进程从一个 node 迁移到另一个 node 上的,这样就造成了跨 node 的内存访问;Linux 支持 CPU 热插拔,当一个 CPU 下线时,它上面的进程会被迁移到别的 CPU 上,也可能出现这种情况.

调度者和内存领域的开发者一直致力于解决这个问题.由于两大系统都非常复杂,找一个通用的可靠的解决方案不容易,开发者中提出两套解决方案,各有优劣,一直未能达成一致意见.3.8内核中,内存领域的知名黑客 Mel Gorman 基于此情况,引入一个叫自动 NUMA 均衡的框架,以期存在的两套解决方案可以在此框架上进行整合; 同时,他在此框架上实现了简单的策略:每当发现有跨 node 访问内存的情况时,就马上把该内存页面迁移到当前 node 上.

不过到 4.2 ,似乎也没发现之前的两套方案有任意一个迁移到这个框架上,倒是,在前述的简单策略上进行更多改进.

如果需要研究此功能的话,可参考以下几篇文章:
-介绍 3.8 前两套竞争方案的文章: A potential NUMA scheduling solution [LWN.net]
- 介绍 3.8 自动 NUMA 均衡 框架的文章: NUMA in a hurry [LWN.net]
- 介绍 3.8 后进展的两篇文章,细节较多,建议对调度/内存代码有研究后才研读:
NUMA scheduling progress [LWN.net]
lwn.net/Articles/591995

CPU 调度与节能

从节能角度讲,如果能维持更多的 CPU 处于深睡眠状态,仅保持必要数目的 CPU 执行任务,就能更好地节约电量,这对笔记本电脑来说,尤其重要.然而这不是一个简单的工作,这涉及到负载均衡,调度器,节能模块的并互,Linux 调度器中曾经有相关的代码,但后来发现问题,在3.5, 3.6 版本中,已经把相关代码删除.整个问题需要重新思考.

在前不久,一个新的 patch 被提交到 Linux 内核开发邮件列表,这个问题也许有了新的眉目,到时再来更新此小节.可阅读此文章: Steps toward power-aware scheduling [LWN.net]

========== 调度子系统 结束分割线 ==========

  • 内存管理子系统(memory management) [内容多且庞杂,陆续更新,时间待定]
  • 时间子系统(timer)
  • 中断子系统(interrupt)
  • 同步机制子系统(synchronization)
  • 文件子系统(VFS, block layer, etc)
  • 网络子系统(networking)
  • 调试和追踪子系统(debugging, tracing)
  • 多核及可伸缩性(SMP, NUMA, scalability)
  • 虚拟化子系统(kvm)
  • 其他? (想到再补充)


---
引用:
[1] Single UNIX Specification
[2] POSIX 关于调度规范的文档: nicolas.navet.eu/publi/
[3] Towards Linux 2.6
[4] Linux内核发布模式与开发组织模式(1)
[5] IBM developworks 上有一篇综述文章,值得一读 : Linux 调度器发展简述
[6] CFS group scheduling [LWN.net]
[7] lse.sourceforge.net/num
[8] CFS bandwidth control [LWN.net]
[9] kernel/git/torvalds/linux.git
共计8个压缩包 本压缩包是:part01.rar 出版社:人民邮电出版社 ·页码:368 页 ·出版日期:2008年 ·ISBN:7115187118/9787115187116 ·条形码:9787115187116 ·包装版本:1版 ·装帧:平装 ·开本:16 ·中文:中文 ·附带品描述:附光盘一张 ·市场价格:49元 内容简介 Linux内核Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。《Linux2.6内核标准教程》深入、系统地讲解了 Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导过程;然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么研究Linux内核 2 1.1.1 Linux的历史来源 2 1.1.2 Linux的发展现状 3 1.1.3 Linux的前景展望 3 1.2 选择什么版本进行研究 3 1.3 内核基本结构 4 1.3.1 内核在操作系统中的地位 4 1.3.2 Linux 2.6内核源代码目录树简介 5 1.3.3 Linux 2.6内核的新特性 8 1.4 如何阅读本书 9 1.4.1 内核探索工具 10 1.4.2 推荐阅读方法 12 第2章 引导过程分析 14 2.1 内核镜像的构建过程 15 2.1.1 编译内核的步骤及分析 15 2.1.2 内核镜像构建过程分析 16 2.2 系统引导过程分析 18 2.2.1 傀儡引导扇区 18 2.2.2 探测系统资源 21 2.2.3 解压内核镜像 35 2.2.4 进入保护模式 40 2.2.5 系统最终初始化 47 2.3 系统引导过程总结 47 第3章 内存管理 50 3.1 基础知识 51 3.1.1 存储器地址 51 3.1.2 分段机制 52 3.1.3 分页机制 59 3.2 内核页表的初始化过程 65 3.2.1 启用分页机制 65 3.2.2 构建内核页表 68 3.3 物理内存的描述方法 76 3.3.1 内存节点 77 3.3.2 内存区域 81 3.3.3 物理页框 85 3.4 物理内存的初始化过程 86 3.4.1 探测系统物理内存 87 3.4.2 初始化内存分配器 89 3.5 物理内存的分配与回收 101 3.5.1 伙伴分配算法 101 3.5.2 对象缓冲技术 103 3.6 内核地址空间 105 3.6.1 常规映射地址空间 105 3.6.2 固定映射地址空间 107 3.6.3 长久内核映射空间 109 3.6.4 临时内核映射空间 116 3.6.5 非连续映射地址空间 119 第4章 进程管理 128 4.1 进程与线程的概念 129 4.1.1 程序与进程 129 4.1.2 进程与线程 129 4.2 进程描述符 131 4.2.1 进程标识符 132 4.2.2 进程的状态 132 4.2.3 进程上下文 134 4.2.4 当前进程 139 4.3 进程的组织形式 143 4.3.1 进程标识符构成的哈希表 143 4.3.2 所有进程构成的双向链表 148 4.3.3 执行态进程组成的运行队列 149 4.3.4 阻塞态进程组成的等待队列 152 4.4 进程的创建过程 155 4.4.1 进程创建的接口函数 156 4.4.2 进程创建的处理过程 162 4.5 进程调度算法 177 4.5.1 进程的分类 178 4.5.2 进程优先级 178 4.5.3 时间片分配 181 4.5.4 进程调度时机 182 4.6 进程切换过程分析 183 4.6.1 选取合适进程 183 4.6.2 完成上下文切换 184 4.7 空闲进程的初始化 187 4.7.1 空闲进程的内核态栈 187 4.7.2 空闲进程的内存描述符 188 4.7.3 空闲进程的硬件上下文 190 4.7.4 空闲进程的任务状态段 190 第5章 中断和异常 192 5.1 基础知识 193 5.1.1 中断和异常的定义 193 5.1.2 中断和异常的分类 193 5.1.3 中断和异常的对比 194 5.2 处理机制 195 5.2.1 IA32架构下的处理机制 195 5.2.2 Linu
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值