认真聊聊中断(软中断)

Linux 下半部机制介绍(二)——softirq(软中断)框架介绍 阅读详情

前面认认真真聊聊中断,其实讲的都是硬中断,注意是硬中断不是硬件中断哦,硬中断的概念更大。

硬中断包括中断、异常以及 INT 指令这种软件中断,整个中断机制是纯硬件实现的逻辑,别管触发它的是谁,所以通通叫硬中断。

当然这里也要有软件的配合,比如软件需要提前把中断向量表写在内存里,并通过 IDTR 寄存器告诉 CPU 它的起始位置在哪里。

好了,这就是之前硬中断的回顾了,如果上面这几句总结你看着很困惑,那强烈建议你先把那片文章看一遍。

软中断与硬中断很像

软中断是纯软件实现的,宏观效果看上去和中断差不多的一种方式。

什么叫宏观效果呢?意思就是说,中断在宏观层面看来,就是打断当前正在运行的程序,转而去执行中断处理程序,执行完之后再返回原程序。从这个层面看,硬中断可以达到这个效果,软中断也可以达到这个效果,所以说宏观效果一样。

那微观层面呢?就是我们需要了解的原理啦。

硬中断的微观层面,就是 CPU 在每一个指令周期的最后,都会留一个 CPU 周期去查看是否有中断,如果有,就把中断号取出,去中断向量表中寻找中断处理程序,然后跳过去。这个在上面那篇文章里讲的很清楚啦。

软中断的微观层面,简单说就是有一个单独的守护进程,不断轮询一组标志位,如果哪个标志位有值了,那去这个标志位对应的软中断向量表数组的相应位置,找到软中断处理函数,然后跳过去。你看,微观层面其实也和硬中断差不多。

接下来我们具体看看,以 Linux-2.6.0 内核为例,扒开它的外套。

软中断的守护进程

不想之前有断档,我们直接从开机开始讲起。

知识都是想通的,学了从不会浪费,如果你对开机启动流程有很直观的了解,那这块那就完全可以自己跟源码知道内核软中断处理线程是怎么从零到一开始的了。Linux启动过程:

ch09-2b

注:这里内核主方法叫 kernel_start,Linux-2.6.0 里叫 start_kernel

asmlinkage void __init start_kernel(void) {
    ...
    trap_init();
    sched_init();
    time_init();
    ...
    rest_init();
}

省略了很多部分,但可以看出这个方法里就是各种初始化。接着看 rest_init() 这个方法:

static void rest_init(void) {
    kernel_thread(init, NULL, CLONE_KERNEL);
} 

static int init(void * unused) {
    do_pre_smp_initcalls();
}

static void do_pre_smp_initcalls(void) {
    spawn_ksoftirqd();
}

看到一个 spawn_ksoftirqd(),翻译过来就是 spawn kernel soft irt daemon,开启内核软中断守护进程,这名字太直观了,都不用讲了!再往里跟。很长,但有用的信息很少

__init int spawn_ksoftirqd(void) {
    cpu_callback(&cpu_nfb, CPU_ONLINE, (void *)(long)smp_processor_id());
    register_cpu_notifier(&cpu_nfb);
    return 0;
}

static int __devinit cpu_callback(...) {
    kernel_thread(ksoftirqd, hcpu, CLONE_KERNEL);
}

static int ksoftirqd(void * __bind_cpu) {
    for (;;) {
        while (local_softirq_pending()) {
            do_softirq();
            cond_resched();
        }
    }
}

asmlinkage void do_softirq(void) {
    h = softirq_vec;
    pending = local_softirq_pending();
    do {
        if (pending & 1) {
            h->action(h);
        h++;
        pending >>= 1;
    } while (pending);
}

前面的不用管,直接看最后一个方法,do_softirq(),这个方法展示了软中断处理守护进程所做的事情的精髓:

// 这就是软中断处理函数表(软中断向量表)
// 和硬中断的中断向量表一样
static struct softirq_action softirq_vec[32];

asmlinkage void do_softirq(void) {
    // h = 软中断向量表起始地址指针
    h = softirq_vec;
    // 这个是软中断标志位们,一次性拿到所有的软中断标志位
    pending = local_softirq_pending();
    do {
        // 此时的软中断标志位有值(说明有软中断)
        if (pending & 1) {
            // 去对应的软中断向量表执行对应的处理函数
            h->action(h);
        // 软中断向量表指针向后移动
        h++;
        // 同时软中断处理标志位也向后移动
        pending >>= 1;
    } while (pending);
}

来几个图你就懂了。

首先 h 代表软中断向量表 softirq_vec,和硬中断的中断向量表的存在是一个目的,就是个数组,然后里面的元素存储着软中断处理程序的地址指针,在 action 中:

int10

然后 pending 代表软中断标志位(们)。

这里完全由于 Linux 里用了好多 C 语言的宏定义搞得很绕,我先放出来,别担心

typedef struct {
    unsigned int __softirq_pending;
    unsigned long idle_timestamp;
    unsigned int __nmi_count;   /* arch dependent */
    unsigned int apic_timer_irqs;   /* arch dependent */
} irq_cpustat_t;

extern irq_cpustat_t irq_stat[];    /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
#define softirq_pending(cpu)  __IRQ_STAT((cpu), __softirq_pending)
#define local_softirq_pending() softirq_pending(smp_processor_id())

pending = local_softirq_pending();

把这些宏定义都翻译过来,再去掉多处理器的逻辑,就当只有一个核心,就变得很简单了

pending = irq_stat[0].__softirq_pending;

它就是个 int 值而已,32 位。

回过头看之前的,pending(软中断标志位) 与 h(软中断向量表) 的向后移动的步长

// 软中断向量表指针向后移动
h++;
// 同时软中断处理标志位也向后移动
pending >>= 1;

可以看出软中断标志位的一位对应着软中断向量表中的一个元素,这就不难理解为什么中断向量表这个数组大小是 32 位了

int11

好了,这样这个内核软中断处理这个守护进程做的事,就完全搞懂了。

就是不断遍历 pending 这个软中断标志位的每一位,如果是 0 就忽略,如果是 1,那从上面的 h 软中断向量表中找到对应的元素,然后执行 action 方法,action 就对应着不同的软中断处理函数。

而且也能看到,内核软中断处理守护进程,在 Linux 启动后,会自动跑起来,那也就代表了,软中断机制生效了。

如果让你使用这个内核功能,做软中断的事情,那不难想象,很简单。

第一步,注册软中断向量表,其实就是把软中断向量表的每个 action 变量赋值,相当于硬中断中注册中断向量表的过程。

第二步,触发一个软中断,其实就是修改 pending 的某个标志位,触发一次软中断,相当于硬中断中由外部硬件、异常、或者 INT 指令来触发硬中断一样。

而实际上,Linux 就是这样做的,和我们猜的一样,我们一步步看

注册软中断向量表

就是给 softirq_vec 这个软中断向量表,也是一个数组,里面的每一个元素的 action 附上值,赋的就是软中断处理函数的函数地址:

softirq_vec[0].action = NULL;
softirq_vec[1].action = run_timer_softirq;
softirq_vec[2].action = net_tx_action;
...
softirq_vec[31].action = xxx;

没错,就是这样,不要以为 Linux 有啥神奇的操作,也是得这样老老实实给他们赋值。

比如,网络子系统的初始化,有一步就需要注册网络的软中断处理函数

subsys_initcall(net_dev_init);

static int __init net_dev_init(void) {
    ...
    // 网络发包的处理函数
    open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
    // 网络收包的处理函数
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
    ...
}

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
    softirq_vec[nr].data = data;
    // 简直完全一样
    softirq_vec[nr].action = action;
}

这和我们写的不能说是相似,简直完全是一样呀,只是多包装了一层函数叫 open_softirq 方便调用罢了。NET_TX_SOFTIRQ 这些是枚举值,具体看这些枚举也会发现 Linux-2.6.0 中也不多

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    SCSI_SOFTIRQ,
    TASKLET_SOFTIRQ
};

Linux5.11版

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    IRQ_POLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,
    NR_SOFTIRQS
};

触发一次软中断

同上,这代码也很容易就可以想到,表示软中断标志位的 p 不是这样取值的么:

pending = local_softirq_pending();

取出来的是个 32 位的 int 值。

那只需要把 local_softirq_pending() 对应的标志位改成 1 就触发了软中断了,比如我们想触发一个 2 号软中断,就像这样:

int12

代码:

local_softirq_pending() |= 1UL << 2;

而 Linux 居然也是这么做的,我们看网络数据包到来之后,有一段代码:

#define __raise_softirq_irqoff(nr) \
do { local_softirq_pending() |= 1UL << (nr); } while (0)

static inline void __netif_rx_schedule(struct net_device *dev) {
    list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
    // 发出软中断
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

如果把 do while(0) 这种 C 语言宏定义的一种玩法去掉,其实就和我们的完全一样了,这回可真的是完全一样。

static inline void __netif_rx_schedule(struct net_device *dev) {
    list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
    // 发出软中断
    local_softirq_pending() |= 1UL << (NET_RX_SOFTIRQ)
}

所以当你真的去接触这个东西的时候,一个个细节逐步拨开后,会发现一点也不难,而且都是顺理成章,和我们猜测的也一样。

总结

软中断没什么神奇的骚操作,就是一组一位一位的软中断标志位,对应着软中断向量表中一个一个的中断处理函数,然后有个内核守护进程不断去循环判断调用,而已。

然后,由各个子系统调用 open_softirq 负责把软中断向量表附上值。

再由各个需要触发软中断的地方调用 raise_softirq_irqoff 修改中断标志位的值。

后面的工作就交给内核那个软中断守护进程,去触发这个软中断了,其实就是个遍历并查找对应函数的简单过程。

int13

记住上面这张图,就可以了。

软中断是 Linux 处理一个中断的下半部的主要方式,比如 Linux 某网卡接收了一个数据包,此时会触发一个硬中断,由于处理数据包的过程比较耗时,而硬中断资源又非常宝贵,如果占着硬中断函数不返回,会影响到其他硬中断的相应速度,比如点击鼠标、按下键盘等。

所以一般 Linux 会把中断分成上下两半部分执行,上半部分处理最简单的逻辑,下半部分直接丢给一个软中断异步处理。

比如网卡收到了一个数据包,假如这个网卡型号是 e1000,那对应的硬中断处理函数是,e1000_intr,我们看看它做了什么事情。

static irqreturn_t e1000_intr(int irq, void *data, struct pt_regs *regs) {
   __netif_rx_schedule(netdev);
}

static inline void __netif_rx_schedule(struct net_device *dev) {
    list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

看到没,后面直接 __raise_softirq_irqoff 丢给软中断就不管了。

这个会在后面讲内核接受网络包的全过程中详细讲解,本次的两篇文章硬中断和软中断,都是为之后的内核收包做铺垫,大家一定要把它们整明白了。

所有复杂的技术,都是由诸多简单技术拼接起来的

硬中断和软中断 嵌入式系统:硬中断用于实时响应外部事件,软中断用于处理可以延迟的任务。服务器:硬中断用于处理高优先级的硬件事件,软中断用于处理可以延迟的任务。 阅读详情

相关推荐

【操作系统】软中断vs硬中断

(如键盘、网卡、磁盘)触发,通知 CPU 有紧急事件需要处理。(如系统调用、异常处理),用于请求内核服务或处理异常。是 CPU 响应外部事件的重要机制,分为。理解二者的区别,有助于优化。

司六米希的博客 1268

硬中断与软中断的区别

摘自:https://blog.51cto.com/noican/1361087 硬中断: 1. 硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。 2. 处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一..

自主无人机 PX4 ROS SLAM 路径规划 MVS等技术分享 1731

12、中断与异常处理:中断亲和性、软中断、下半部机制、NMI

中断,是操作系统响应硬件事件的“神经反射”。我做了这么多年性能优化,见过太多系统因为中断处理不当而“卡死”。说白了,中断就像你家门铃——有人按了,你得去开门。但如果每次开门都跟客人聊半小时,那正事就别干了。这一章,我们聊聊中断的那些“门道”。

woailuojp的博客 292

软中断-小结

软中断小结

qq_42693685的博客 1623

单片机中的软中断(SoftIRQ)详解

软中断是一种事件驱动的处理方式,它通过在单片机的内核中设置软件中断标志来触发中断处理程序的执行。当发生一个软中断时,单片机会在合适的时机(通常是在任务切换或者中断处理程序返回时)执行相应的中断处理程序。软中断是一种事件驱动的处理方式,它通过在单片机的内核中设置软件中断标志来触发中断处理程序的执行。当发生一个软中断时,单片机会在合适的时机(通常是在任务切换或者中断处理程序返回时)执行相应的中断处理程序。函数用于注册软中断处理程序,将指定的处理程序函数指针存储在相应的数组位置中。注册软中断处理程序。

PkdgBlockchain的博客 345

海康威视门禁考勤设备数据对接实战:从被动推送到主动拉取

本文深入探讨了海康威视门禁考勤设备数据对接的两种核心方案:HTTP推送与SDK主动拉取。文章对比了两种方式的适用场景、技术实现与优缺点,为开发者提供了从设备配置、代码编写到工程化部署的完整实战指南,帮助读者根据项目规模、网络环境及数据稳定性要求,做出最合适的技术选型。

weixin_29284657的博客 670

【操作系统】聊聊Linux软中断

聊聊Linux软中断

芝兰生于深谷,不以无人而不芳 622

从STM32F411到华大HC32F460:一个真实项目移植的踩坑与填坑全记录

本文详细记录了从STM32F411移植到华大HC32F460芯片的全过程,包括开发环境搭建、时钟树配置、中断处理、外设驱动、看门狗陷阱、Flash操作和低功耗设计等关键坑点及解决方案。特别针对HC32F460的200MHz主频、双PLL架构和HSRUN模式等特性进行了深入分析,为工程师提供实用的移植指南,帮助节省大量调试时间。

weixin_33691817的博客 418

别再傻傻分不清了!用大白话聊聊Linux里的硬中断和软中断

本文通过生活化类比解析Linux中的硬中断和软中断机制,帮助读者轻松理解这两种关键的中断类型。硬中断如同紧急门铃需立即响应,而软中断则像系统内部的待办事项,共同协作提升计算机响应效率。文章还提供了实际案例和性能调优技巧,适合开发者深入掌握Linux中断处理原理。

weixin_30505225的博客 505

Go语言集成Ollama本地大模型:gollama库实战指南

在当今的AI应用开发中,大型语言模型(LLM)的集成已成为关键技术。其核心原理是通过API调用,将自然语言处理能力嵌入到各类应用中。从技术价值看,本地化部署的LLM解决了数据隐私、网络延迟和持续成本三大痛点,尤其适合处理敏感数据或对实时性要求高的场景。在工程实践中,开发者常使用专门的客户端库来简化与模型服务的交互,例如通过封装REST API、提供类型安全接口和流式处理支持,从而提升开发效率和代码健壮性。具体到Go语言生态,结合Ollama这一流行的本地大模型运行工具,可以实现完全离线、低延迟的AI功能集成

weixin_28728443的博客 605

三维荧光光谱(EEM)散射干扰实战:从识别到精准切除的完整流程

本文详细解析三维荧光光谱(EEM)中散射干扰的识别与精准切除流程,涵盖瑞利散射和拉曼散射的实战处理技巧。通过drEEM工具箱的应用示例和高级场景解决方案,帮助研究者有效消除散射干扰,提升数据准确性,特别适用于环境样品和生物活体样本分析。

weixin_42554042的博客 327

中断 异常 软中断

中断 Linux内核

qq_64790377的博客 1066

Flowable高级篇 -6.8.0 最全表结构说明-主要表结构字段注释

flowable 28张表 详细

billzhen的博客 1万+

玩转KVM: 了解网卡软中断RPS

本篇给大家讲网卡中断如何提高虚拟机性能。 KVM的网卡软中断技术介绍 (1)硬中断 由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。 (2)软中断 为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些...

RJ0024的博客 1227

Claude Code 学习笔记之一:理解 Agentic Loop 与内置工具

本文介绍了Claude Code这一终端智能助手的工作原理与使用方式。作者从ABAP开发经验出发,指出学习Claude Code的高级用法具有长期价值。文章详细解析了Claude Code的Agentic Loop机制:通过大语言模型进行推理,借助各类工具执行任务,并循环收集上下文、执行操作、验证结果三个步骤。系统阐述了Claude Code支持的五大类工具及其扩展方式,说明了其访问本地资源的范围和自治处理能力,同时介绍了权限模式等安全机制。最后提供了从小任务开始、充分探索、及时引导、使用CLAUDE.md

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 546

药物对接仿真软件:HADDOCK_(3).分子对接基本原理

分子对接(Molecular Docking)是计算化学和结构生物学中的一种重要技术,用于预测小分子(如药物分子)与大分子(如蛋白质)之间的相互作用模式和结合亲和力。这一技术在药物设计和开发中具有广泛的应用,尤其是在寻找和优化潜在的药物候选物方面。本节将详细介绍分子对接的基本原理,包括其理论基础、计算方法和应用。

kkchenjj的博客 1169

认认真真的聊聊“软“中断

软中断没什么神奇的骚操作,就是一组一位一位的软中断标志位,对应着软中断向量表中一个一个的中断处理函数,然后有个内核守护进程不断去循环判断调用,而已。然后,由各个子系统调用负责把软中断向量表附上值。再由各个需要触发软中断的地方调用修改中断标志位的值。后面的工作就交给内核那个软中断守护进程,去触发这个软中断了,其实就是个遍历并查找对应函数的简单过程。记住上面这张图,就可以了。好了,上篇文章的硬中断,和本篇文章的软中断,它们最基本的原理,和他们的异同点,你整明白了么?

m0_53157173的博客 543

用Python爬取大连商品交易所期货实时行情:从入门到实战

python复制编辑# utils.py"豆粕": "M0","铁矿石": "I0","聚乙烯": "L0","玉米": "C0","聚丙烯": "PP0","棕榈油": "P0"本项目展示了:✅ 如何从新浪财经 API获取主力合约实时数据✅ 使用Python异步爬虫技术) 提升效率✅ 利用pandas和matplotlib进行结构化分析与可视化✅ 具备良好的扩展性,可对接金融、农业、能源等数据需求场景✅ 可作为期货量化策略数据源✅ 可嵌入自动化报告系统。

2201_76125261的博客 1247

Linux软中断实战:如何用tasklet优化你的驱动性能(附代码示例)

本文深入探讨了Linux内核中利用tasklet优化驱动性能的实战方法。针对中断处理函数执行时间过长影响系统实时性的问题,文章通过对比分析硬中断与软中断机制,详细阐述了tasklet的工作原理、适用场景及代码实现。通过一个高速数据采集卡驱动的真实案例,展示了如何将耗时操作从中断上下文移至tasklet中执行,从而显著降低中断延迟、减少数据丢失并提升系统整体吞吐量。

h6i7j8的博客 893

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

操作系统之中断

操作系统中断的实现过程

weixin_45370502的博客 1648

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【操作系统】浅谈 Linux 中的中断机制

【操作系统】浅谈 Linux 中的中断机制

blblccc 2986

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

认认真真的聊聊中断

所以总结起来就是,理解中断,只要回答了这几个问题就好。如何给 CPU 一个中断号?外部设备通过 INTR 引脚,或者 CPU 执行指令的过程中自己触发,或者由软件通过 INT n 指令强行触发。同样中断也是这样进行分类的。CPU 收到中断号后如何寻找到中断程序的入口地址?通过 IDTR 寄存器找到中断描述符表,通过中断描述符表和中断号定位到中断描述符,取出中断描述符表中存储的程序入口地址。中断描述符表是谁写的?操作系统代码写上去的。找到程序入口地址之后,CPU 做了什么?

m0_53157173的博客 475

【中断处理优化】如何避免延时陷阱?中断上下文的黄金法则

本文深入探讨了中断处理中的延时陷阱及其对系统实时性的危害,阐述了中断上下文的黄金法则:中断处理程序必须尽可能短平快,避免任何延时操作。文章详细分析了硬中断与软中断的区别,并提供了使用Tasklet与Workqueue等下半部机制的实战替代方案,以优化系统性能。

android的专栏 889

为了学明白中断机制,我努力了

我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。如果你也喜欢研究底层,欢迎关注我的公众号【硬核子牙】

子牙老师 560

系统调用深度解析:从软中断到内核实现,手把手完成操作系统实验

系统调用是操作系统为用户程序提供服务的核心接口,它定义了用户态与内核态之间的标准通信机制。其基本原理是通过软中断(如int 0x80或syscall指令)触发处理器特权级切换,使CPU从用户态(Ring 3)切换到内核态(Ring 0),从而安全地执行内核代码。这一机制的技术价值在于实现了资源访问的隔离与保护,确保了系统的安全性和稳定性。在应用场景上,系统调用支撑了文件操作、进程管理、网络通信等几乎所有上层应用功能。本文以“头歌”平台的操作系统实验为切入点,详细剖析了系统调用的完整实现路径,包括添加系统调用

weixin_30444111的博客 346
上一篇: Python sort原理
下一篇: SSH免密登录失败
wq_0708
博客等级 码龄6年 2万+粉丝 95原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值