[V-05] 虚拟化基础-异常模型(Exception model)(基于AArch64)

ver 0.1
在这里插入图片描述

更多精彩内容,请关注公众号

前言

前文的叙述中反复提到了ARM的异常模型,很多地方也留下了伏笔。异常模型对于ARM的架构来说是非常重要的,可以说是AArch64的很多设计思想的基础,对于虚拟化来说那就更加的意义非凡,因此我们需要篇文章专门的阐述清楚AArch64的异常模型的概念、分类、以及处理机制。
一位伟大的人说过:这个世界唯一不变的真理就是这个世界永远处在不断的变化当中。人类的世界也是如此,正是每个人有了自己的想法,然后通过想法指导自己的行动,这个世界才能够绚丽精彩,如果每个人一直保持一种状态或者千篇一律运行,那么注定是无聊、无趣、也可能是无用的。延伸到计算机的世界也是一样的,如果让一个程序一直把持着计算机资源,那么这些硬件资源的能力就会受到限制,就不会迸发出更强大的功能。因此,聪明的计算机的大神们设计出了操作系统中的核心模块:调度子系统。这个子系统限制了进程的权力,使他不能一直占用系统资源,要大家共享。那么操作系统要去限制某一个进程,它怎么才能够乖乖的听话,这就引出了另外一个概念:权限。给操作系统赋予更高的权限能够控制进程就可以了,当然什么条件下才能控制,怎么控制,控制的时机点等,那就是操作系统的事情,毕竟权力越大,责任也就越大。这里举一个一般操作系统的调度模型(通过中断),如图1-1所示。
图灵

图1-1 一般系统的中断调度模型

如果没有中断机制,那么Task1会一直执行,直到它自己主动放弃。有了中断机制之后,事情就起了变化,有更多的任务得到了运行的机会,这时计算机的功能也更加的丰富和强大。Task1正常的执行流被打破,这种情况就属于异常

正文

异常概念

前言中我们通过调度子系统的设计,论述了怎么通过中断机制实现一个操作系统中任务调度,从一个侧面说明了“异常”的作用。广义上说一切不同寻常的情况,都可以被称之为异常。那么我们把格局变小,限制在计算机的世界内给异常下一定义。
异常(Exception)指的是cpu的某些非正常状态或一些系统的事件,这些状态或事件会导致CPU执行一些预先设定的具有更高执行权限的软件代码(exception handler),执行完毕后,返回产生异常的现场。让我们再来看看ARM怎么定义异常:

Exceptions are conditions or system events that require some action by privileged software (an exception handler) to ensure smooth functioning of the system.

在ARM体系下,异常被定义为一种事件和情况,是需要在特权模式下被处理的事件和情况,处理的目的是为了保证整个系统更加稳定、功能更加丰富。
ARM设计异常机制一些具体的原因,可以参考手册中的描述如下:

Exceptions are used for many different reasons, including the following:
• Emulating virtual devices
• Virtual memory management
• Handling software errors
• Handling hardware errors
• Debugging
• Performing calls to different privilege or security states
• Handling interrupts (timers, device interactions)
• Handling across different Execution states (known as interprocessing)

异常等级

再探讨等级之前,先讨论下特权或者权力,社会学范畴人们通过选举产生ZF,让ZF有了更大的调动资源的能力,让ZF去处理社会中非常正常情况,进而使社会额运行更加的稳定、和谐、高效。那么个人就要做出一些妥协,把一些事情让ZF去处理,ZF的权力来自于个体组成的群体,当然就要服务群体,是群体的利益最大化。
对于一个计算机系统也是一样的,资源是有限的,但是Task是无限的(当然肯定是有上限的),那么怎么能让Tasks能够和谐的共享资源,高效的执行,就要让渡出一部分权力给系统的服务于所有Tasks的软件模块,让系统的这些模块去管控硬件资源,处理异常的时间和情况,从而让整个系统的所有任务运行的更加稳定、高效。
演着这个思路,ARM体系也将系统的不同软件某块分成了不同的等级,每个等级的特权也不一样,这里我们直接引用ARM手册中的原文加以描述:

The AArch64 architectures enable this split by implementing different levels of privilege. The current privilege level can only change when the processor takes an exception or returns from an exception. Therefore, these privilege levels are referred to as Exception levels in the Arm architecture.

AArch64将系统中的软件模块按照权限分成了4个等级,如图1-2所示,每个等级处理异常的能力是不一样的,用EL(Exception Level)来做标识,当然从字面意思上看也可以被称之为异常等级
ELx

图1-2 AArch64 Exception Levels

功能划分

上述四个异常等级对应的软件功能大致可以分为:
(1) EL0 执行一般任务(Task、用户进程等非特权功能模块)程序的代码。
(2) EL1 执行一般操作系统代码(Native OS 、GuestOS)
(3) EL2 执行Hypervisor代码或者经过虚拟化扩展配置的OS代码。
(4) EL3 执行提供了安全/非安全的切换机制固件代码。
这里是一般意义上普遍的划分,实际的工程项目中会通过配置做一些微调,AArch64也支持这样微调。

执行状态

下面我们看一下异常等级和PE执行状态的联系。在前序的系列文章中,我们已经知道,ARMv8以后的PE架构是可以向前兼容的,因此PE的执行状态分成了AArch64和AArch32。而大多数的Cortex-A系列的PE是支持安全状态的,自然也就有非安全状态。这些状态和异常等级的映射关系如图1-3所示。
EL-x

图1-3 Exception levels and Security states using different Execution states

切换时机

异常等级是可以切换的,切换的时机点总结如下:
(1) 异常发生时 当PE会识别到异常事件,通过一些寄存器的配合能够识别寄存器的类型,根据预先的配置,跳转到相应的异常等级中的软件模块中去响应异常,这也是我们后续讨论的重点。
(2) 从异常返回 异常事件被处理后需要按照原路径返回,此时也可以实现异常等级的切换。
(3) 处理器重置 最高优先级的异常场景,不做重点讨论。
(4) Debug模式 特殊模式不做重点讨论。

切换规则

异常切换的时机结合PE的状态构成了ARM复杂的异常处理场景,需要规则去做约束,这里我们先简单的总结一下:
(1) EL0不能响应任何异常。
(2) 响应异常时,异常等级可以维持在当前级别或者增加异常等级。
(3) 响应异常时,不能降低异常等级。
(4) 退出异常时,异常等级只能维持在当前级别或者降低异常等级。
(5) 退出异常时,异常等级不能提高。

下面以中断为例,解释一下规则:
(1) EL0这个级别的软件模块一般都属于普通应用,他们对于系统的诉求就是稳定,底层负责的中断逻辑对他们是透明的,比如网卡通过中断通知CPU已经通过DMA写满了内存中的接受缓冲区,请通知应用过来取数据吧,此时应用如果block在等数据这个点上的话,那么它直接拿走数据就行了,而不需要关注中断事件,以及中断事件怎么处理。其他的异常事件也是类似的处理机制,应用让渡了这些异常处理的能力给系统,系统透明化这些异常事件,应用只关注结果就行了。这样应用的权限就被配置为最小,不需要处理异常。
(2) 中断到来时,被路由到ELx(x > 0)提前都是规划好的,比如当PE正在执行GuestOS的kernel代码,此时系统正处于EL1等级中,此时串口GIC向CPU的接口发送了一个中断信号,如果此时的串口资源是系统的所有系统共享,而不是GuestOS独占,那么这个中断信号就应用由GuestOS的上级Hypervisor(EL2)进行处理,由Hypervisor决定如何处理这个中断,是分发给当前的GuestOS还是其他软件模块。如果这个串口设备是GuestOS独占,那么此时Hypervisor可以使用GIC直接将这个中断信号映射成虚拟中断直接注入给vCPU进行响应,那么此时响应中断的异常级别还是EL1。也就是说响应这个中断可以保持不变在EL1或者EL2,那么能不能路由到EL0来处理,那肯定是不行的,前面已经叙述了原因,也可以说“大人的事儿小孩别管”。
(3) 当RichOS的kernel在服务一个进程时接收到了timer中断,此时系统会重新评估各个进程的执行时间,看看有没有进程多吃多占,有的进程快饿死了,那么就会根据评估结果,对这些进程做标记,当退出中断处理程序时,就会根据标记重新调度新的进程进行执行。这就是典型的退出异常时,特权等级降级的场景,因为普通用户进程运行在EL0级别。中断返回时,如果要使用更高等级的服务,那么需要先切换回进入异常处理之前的上下文,然后重新使用系统调用等接口重新进行申请。

权限类型

通过前面章节的讨论,我们已经清楚了AArch64状态下,ARM将整个系统的软件功能模块划分为了四个等级,异常的等级越高权限也就越大。那么代码在执行的过程中码农或者CPU怎么去识别这些所谓权限呢,哪些事儿能干,那些事儿不能干,判断的依据是啥?还是看看ARM咋说的:

There are two types of privilege relevant to the AArch64 Exception model:
• Privilege in the memory system
• Privilege from the point of view of accessing processor resources
Both types of privilege are affected by the current privileged Exception level.

内存权限
前文讲CPU架构的时候,我们提到过,软件的世界通过内存模型而不是硬件总线和所有的硬件资源关联到了一起,合理的推断对这些硬件资源的使用权限肯定是要通过内存模型这个枢纽加以限制。完整的内存模型,那真是几天几夜也说不完,那么我们这里简单说一下原理,其实就是通过给页表或者页块赋予一定的属性,这些属性包括读写权限,使不同的异常等级拥有不同的访问权限,进而达到特权控制的目的。
下面简要描述一下ARM的具体设计,先看一下AArch64的页表项长啥样,如图1-4、1-5所示:
PageDescriptor

图1-4 AArch64页描述符

Attribute fields

图1-5 AArch64页属性

内存的管理不论是物理内存管理或者虚拟内存管理都是很宏大艰深的课题,这里我们简要的做一下说明:内存资源是除了CPU资源外最重要的系统资源,现代一般处理器都是通过虚拟内存结合MMU映射物理内存的方式使用内存,那么这种映射的颗粒度称之为页(page)或者块(block),这个页的size是可以配置的,例如配置为4k一个页,那么这些页会通过一个叫页表的数据结构管控起来,页表里面的每一项称之为页表项或者叫页描述符,如图1-4所示。当然页表(Page Table)也可以多级管理,这里我们只关注直接关联页的最低级别页表。每个页表描述符大致可以分为两部分,一部分是指向具体页的地址,另外一部分就是该页的属性。而我们关注的内存访问权限(AP)就在这里进行配置,具体的可选配置项如图1-6所示。
AP

图1-6 AArch64内存访问权限配置

到这里就比较清晰了,内存资源经过配置决定了指令在访问的时候在响应的异常等级下是否合法,如果出现越权的行为,那么CPU就会抛出相应的异常。

寄存器访问
处理器的上下文是需要系统寄存器的配合的,前文我们也讨论了系统寄存器的作用。而访问这些系统寄存器,在AArch64的设计中也是受到异常等级的控制的。ARM将很多的系统寄存器进行了异常等级的划分,这些寄存器名字相同但是后缀不一样,这里我们列车异常处理相关的寄存,大家就会一目了然,如图1-7所示。
SysReg

图1-7 异常处理相关系统寄存器

ELx : x一般为1 、2、 3,代表相应的异常等级。这个规则也比较简单,相应的等级可以访问对应等级的系统寄存器; 高等级状态下可以访问低等级的系统寄存器。如果违反规则,同样会有相应的异常抛出。

异常分类

前面章节,我们已经了解AArch64中异常的概念,异常其实就是非正常的状态和系统事件,需要系统做截获,在更高的权限下做进一步处理让系统更稳定,功能也更丰富。AArch64的架构下,异常被分成了同步异常(synchronous exceptions )和异步异常(asynchronous exceptions)。

同步异常(synchronous exceptions )

符合如下特征的异常都称之为同步异常:

If the following all apply, then an exception is synchronous:
• The exception has been generated as a result of direct, or attempted, execution of an instruction.
• The address to return to once the exception has been handled (return address) has an architecturally-defined relationship with the instruction that caused the exception.
• The exception is precise, meaning the register state presented on entry to the exception handler is consistent with every instruction before the offending instruction having been executed, and none after it.

很多文章都在分析这三句话,各种解读都有,其实可以简单理解为同步异常来自于PE内部执行指令的过程中产生的打断当前程序指令流的事件都可以归类为同步异常。

下面结合具体的触发场景,来理解一下同步异常:
Invalid instructions and trap exceptions
没有定义、当前异常等级不被允许、或者被禁止的指令都被称为无效指令,当PE执行这些指令时都会抛出异常。手册中举了一个关于浮点运算的例子,为了加快进程context切换的效率,会临时关闭一些处理的功能,例如浮点和向量运算,这样这些运算相关的寄存器(Bx、Sx、Dx、Vx)就不需要参与上下文切换。当进程执行到相关的浮点或者向量运算的指令时,这时PE就会抛出异常,kernel截获这个异常后,通过配置相关的系统寄存器重新使能相关的feature,返回后进程就可以正常使用浮点和向量运算的指令了。类似这种情况产生的异常就是同步异常。

Memory accesses
这个就比较容易理解了,前面我们已经简要的叙述了内存资源的访问权限配置,当非特权模式下去访问特权配置的内存、对只读配置的内存 执行写操作、Kernel去申请一个新的页,PE都会抛出MMU fault异常。

** Exception-generating instructions**
这个也比较容易理解,显示通过指令使用更高等级的服务,也会引起异常,这里直接用一张图展示SVC、HVC、SMC三个指令和异常等级之间的关系,如图1-8所示:
svc

图1-8 AArch64 系统调用

Debug exceptions
不探讨,感兴趣大家可以自行查阅手册。

异步异常(asynchronous exceptions)

对比同步异常异步异常就比较容易理解了,具备如下特征就可以归类为异步异常:
(1) 不依赖PE内部的任何指令产生的异常。
(2) 异常的来源需要通过PE的外部interface,或者说来自于PE外部。
(3) 具有随机性,每次打断PE当前执行流不像同步中断那样是一个精确的位置。

下面结合具体的触发场景,来理解一下异步异常:

中断(物理中断/虚拟中断)
中断这个非常好理解了,事实上也是我们研究异常的重点。一个外设意识到自己需要和PE交互一下信息或者数据,只能通过中断的方式强行打算PE当前的指令流,让PE紧急处理一下,然后各自安好。当然现在我们搞虚拟化,有了vCPU之后也要有机制去给它发送虚拟的中断,AArch64体系下GIC在Hypervisor的配合下,也支持给vCPU发送虚拟中断,事实上也是随机打断当前PE的执行流。

System error (SError)异步的data abort
我们先来看一下手册中的描述:

A typical example of SError is what was previously referred to as an external asynchronous abort. Examples of SError interrupts include:
• Memory access which has passed all the MMU checks but then encounters an error on the memory bus
• Parity or Error Correction Code (ECC) checking on some RAMs, for example those in built-in caches
• An abort triggered by write-back of dirty data from a cache line to external memory

事实上这个描述对于软件工程师来说还是过于抽象,其实就是想表达当前异常和PE内部执行的指令无关,是硬件错误(总线错误)产生的异常,这里我们举例说明一下。
前文中我们讲述CPU架构的时候提到了cache机制,AArch64的cache机制也比较复杂,主存中的指令数据需要cache才能和PE交互,这是一个背景,如图1-9 、1-10所示。当PE产生的数据需要经过cache回写到内存的时候,此时如果cache执行的是WB策略,那么实际上这是一个异步的过程,PE的存储指令会先行返回,需要更新的数据会暂存在cache中,等到满足一定的条件才会经过cache更新到内存,这个过程PE是不参与的,但是如果这个过程发生错误例如BUS错误导致回写失败,总线就会向PE抛出异常,而这个异常显然是一个异步异常。
cache

图1-9 AArch64 Cache

WB

图1-10 AArch64 cache Writeback策略

Reset
这里直接引用ARM手册中的描述,不做重点讨论:

Reset is treated as a special vector for the highest implemented Exception level. This is the location of the instruction that the ARM processor jumps to when an exception is raised. This vector uses an IMPLEMENTATION DEFINED address. RVBAR_ELn contains this reset vector address, where n is the number of the highest implemented Exception level.
All cores have a reset input and take the reset exception immediately after they have been reset. It is the highest priority exception and cannot be masked. This exception is used to execute code on the core to initialize it, after power-up.

异常处理

异常发生了,PE就需要按照既定的配置和策略处理异常,大概的流程如图1-11所示:
handler

图1-11 Simple Exception Handler

大致的处理流程如下:
(1) 当异常事件通知到PE,PE保存当前PE的状态快照到系统寄存器SPSR_ELx、保存异常的返回地址到系统寄存器ELR_ELx、对于同步异常要记录原因到系统寄存器ESR_ELx、对于地址相关的异常要记录虚拟地址到系统寄存器FAR_ELx。
(2) 更新PSTATE,PE根据异常类型跳转到异常向量表中去执行向量表中的指令,跳转到对应的异常handler中(体系相关的汇编代码)。
(3) 保存PE被打断时的上下文,主要是对通用寄存器等进行压栈处理,这里要用到SP_ELx寄存器。
(4) 根据异常类型跳转到handler中执行处理异常操作,这个handler一般由操作系统提供的C代码别写。
(5) handler处理完异常之后,从SP_ELx处进行退栈处理恢复PE被打断前的上下文(这里其实也隐含了如何进行进程调度的机制,后面会有专门的文章进行分析)。
(5) 从SPSR_ELx中恢复PE PSTATE,调用ERET指令调转到ELR_ELx处继续执行原来的执行流。

注意这里面我们没有考虑异常嵌套的情况,实际上流程差不多,大家可自行阅读相关手册。

异常向量(exception vector)

异常向量起到的作用是串联异常事件和处理异常事件组件的桥梁作用,下面引用一段ARM手册中对异常向量的描述:

• When an exception occurs, the processor must execute handler code which corresponds to the exception. The location in memory where the handler is stored is called the exception vector. In the ARM architecture, exception vectors are stored in a table, called the exception vector table.
• Each entry in the vector table is 16 instructions long.
• Each Exception level has its own vector table, that is, there is one for each of EL3, EL2 and EL1. The table contains instructions to be executed, rather than a set of addresses. Vectors for individual exceptions are located at fixed offsets from the beginning of the table. The virtual address of each table base is set by the Vector Based Address Registers VBAR_EL3, VBAR_EL2 and VBAR_EL1.

前文已经讨论过AArch64中的指令为固定长度32字节,因此一个异常向量的长度为16x32=0x080,这个决定了异常向量表的格局如图1-12所示:
EVT

图1-12 AArch64异常向量表

除了EL0外每个异常等级都有自己的异常向量表,下面引用两段Linux kernel的代码加以说明:
代码片段一和二分别描述了在开机阶段在EL2和EL1层面初始中断向量表的过程,事实上就是初始化vbar_el2和vbar_el1这两个寄存器。

// KVM hyp-init.S
	/* Install stub vectors */
	adr_l	x5, __hyp_stub_vectors
	msr	vbar_el2, x5
	eret
// Entry.S
	alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0
	msr	far_el1, x29
	ldr_this_cpu	x30, this_cpu_vector, x29
	tramp_alias	x29, tramp_exit
	msr		vbar_el1, x30		// install vector table
	ldr		lr, [sp, #S_LR]		// restore x30
	add		sp, sp, #PT_REGS_SIZE	// restore sp
	br		x29

异常屏蔽

为了文章的完整性,这里还是提一下,这里直接引用原文,不再赘述,感兴趣的读者可以自行查阅相关手册。

When the processor takes an exception to an AArch64 execution state, the PSTATE interrupt masks (PSTATE.DAIF) are set automatically. DAIF stands for debug, abort (SError), IRQ, and FIQ. The DAIF field is 4 bits, with each bit corresponding to one of the mentioned exception types. By writing a 1 to a bit in the field, we mask or ignore the exception type. It can go to pending, but not get handled. In other words, the PE does not branch to the exception handler until the bit is unmasked, effectively disabling further exceptions of that type from being taken.

异常与虚拟化

虚拟化技术的软件核心就是Hypervisor,Hypervisor的在虚拟化中的功能,例如内存管理、设备模拟、设备分配、指令捕获、中断管理等对AArch64的异常机制都是强依赖。这个在后续的文章中都会慢慢渗透讲到。这里面看一段手册中的描述:

EL0 and EL1 are the only Exception levels that must be implemented and are mandatory. EL2 and EL3 are optional. EL2 contains much of the virtualization functionality. Implementations that do not have EL2 do not have access to these features. There are several software implementations that have been developed requiring platforms to support these Exception levels. For example, KVM Linux requires EL2, EL1, and EL0.

从这里可以看出,虽然EL2和EL3对与AArch64不是必须需要实现的异常等级,但是如果要支持虚拟化,那么至少EL2是必须要实现的,可以说异常机制是在AArch64上实现虚拟化技术的基础。

结语

本文我们重点介绍了AArch64的异常模型,包括概念、异常分类、处理机制等。为了突出主线,有些内容没有详细的阐述,例如结合PE的执行状态和安全状态的异常等级的切换规则等。考虑到我们是服务于虚拟化这个主体,因此有些内容后续会在ARM系列的文章中专门加以详细的说明,这里不再赘述。
经过异常机制的洗礼之后,我们有了进一步向虚拟化核心技术进军的基础,下面的文章我们会继续依托AArch64介绍实现虚拟化技术的必备基础知识。
在这里插入图片描述

更多精彩内容,请关注公众号

Reference

[01] <learn_the_architecture_aarch64_virtualization_guide_102142_0100_04_en.pdf>
[02] <80-ARM-ARCH-OVW-cs0002_ARM64体系结构编程与实践-基础知识.pdf>
[03] <aarch64_exception_and_interrupt_handling_100933_0100_en.pdf>
[04] <learn_the_architecture_aarch64_memory_attributes_and_properties_102376_0200_01_en.pdf>
[05] <Armv8-A-virtualization.pdf>
[06] <Realm_Management_Extension_DEN0126_en.pdf>
[07] <aarch64_exception_model_102412_0102_en.pdf>
[08] <102412_0102_01_en_Exception_model.pdf>
[09] <learn_the_architecture_aarch64_exception_model_102412_0103_01_en.pdf>
[10] <aarch64_exception_and_interrupt_handling_100933_0100_en.pdf>
[13] <DDI0360F_arm11_mpcore_r2p0_trm.pdf>
[14] <DDI0240A.pdf>
[15] <Arm_A-profile_support_for_non-maskable_interrupts.pdf>
[16] <DEN0024A_v8_architecture_PG.pdf>
[17] <learn_the_architecture_aarch64_memory_model_102376_0100_02_en.pdf>

Glossary

AP - Application Processors
SCP - System Control Processor (SCP)
SMC - Secure Monitor Call (SMC)
HVC - Hypervisor Call (HVC)
OPP - Operating Performance Point (OPP)
DVFS - Dynamic Voltage and Frequency Scaling (DVFS)
DCVS - Dynamic Clock and Voltage Scaling (DCVS)
AMU - Arm Activity Monitors Extension (AMU)
EAS - Energy Aware Scheduling
IPA - Intelligent Power Allocation
AP - Application Processor (AP)
SIMD - Single Instruction Multiple Data(SIMD)
VBAR - Vector Base Address Register
SCTLR - System Control Register
ELR - Exception Link Register
ESR - Exception Syndrome Register
FAR - Fault Address Register
HCR - Hypervisor Configuration Register
SCR - Secure Configuration Register
SCTLR - System Control Register
SPSR - Saved Program Status Register
UART - Universal Asynchronous Receiver/Transmitter
ALU - Arithmetical Logical Unit
NMI - non-maskable interrupts
BSA - Base System Architecture
SBSA - Server Base System Architecture
BBR - Base Boot Requirements
TBSA - Trusted Base System Architecture
AMBA - Advanced Microcontroller Bus Architecture
TRM - Technical Reference Manual
MMU - Memory Management Unit
DSU - DynamIQ Shared Unit
BPU - Branch Predictor Unit
INTID - Interrupt ID
SBSA - Server Base System Architecture
ESR - Exception Syndrome Register
FAR - Fault Address Register
ELR - Exception Link Register
VBAR - Vector Base Address Registers
PSTATE - Process state, PSTATE
PCS - Procedure Call Standard
ABI - Application Binary Interface

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值