ldd -14.1. Kobjects, Ksets, and Subsystems

14.1. Kobjects, Ksets, and Subsystems

The kobject is the fundamental structure that holds the device model together. It was initially conceived as a simple reference counter, but its responsibilities have grown over time, and so have its fields. The tasks handled by struct kobject and its supporting code now include:
kobject 是将设备模型组织在一起的最基本的数据结构。最初她被设计为一个简单的引用计数器,可是她的功能随着时间扩展,这样有了自己的字段(地盘)。 结构体 kobject 和对应代码支持的功能包括:

 

Reference counting of objects   引用计数的对象

Often, when a kernel object is created, there is no way to know just how long it will exist. One way of tracking the lifecycle of such objects is through reference counting. When no code in the kernel holds a reference to a given object, that object has finished its useful life and can be deleted.

常常,当一个内核对象被创建时,没有办法知道它会存在多久。跟踪对象的生命周期的一个办法就是通过引用计数来实现。当没有内核代码持有其对象的计数器时,说明这个对象已经结束,可以被删除了。
Sysfs representation

Sysfs表示

Every object that shows up in sysfs has, underneath it, a kobject that interacts with the kernel to create its visible representation.

每一个在sysfs中显示的对象,在下面,有一个Kobject和内核交互来实现对象的显示。

Data structure glue

数据结构之间的粘合

The device model is, in its entirety, a fiendishly complicated data structure made up of multiple hierarchies with numerous links between them. The kobject implements this structure and holds it together.

设备模型是一个极其复杂的多级数据结构,而多级之间有许多连接。kobject就是实现和组织这个结构。
Hotplug event handling 热插拔事件处理

      The kobject subsystem handles the generation of events that notify user space about the comings and goings of hardware on the system.

      Kobject子系统处理一般事件,通知用户空间硬件在系统的来去。

One might conclude from the preceding list that the kobject is a complicated structure. One would be right. By looking at one piece at a time, however, it is possible to understand this structure and how it works.

有可能你从前面kobject的功能列表总结出它是一个很复杂的数据结构。是这样的。不过,通过对其中一个部分的一次的了解,是有可能理解这个数据结构以及如何工作的。

14.1.1. Kobject Basics
14.1.1 Kobject基础

A kobject has the type struct kobject ; it is defined in <linux/kobject.h> . That file also includes declarations for a number of other structures related to kobjects and, of course, a long list of functions for manipulating them.

一个kobject类型为 struct kobject;它定义在<linux/kobject.h>中。这个文件还包括了许多其他和相关kobject的数据结构的声明,当然也有一系列操作他们的函数。

14.1.1.1 Embedding kobjects
14.1.1.1 嵌入Kobjects

Before we get into the details, it is worth taking a moment to understand how kobjects are used. If you look back at the list of functions handled by kobjects, you see that they are all services performed on behalf of other objects. A kobject, in other words, is of little interest on its own; it exists only to tie a higher-level object into the device model.

在我们进入细节之前,来看看如何去使用Kobject的。如果回头如看看kobject传递过来的函数列表,你就会开到他们都是服务于其他的对象。换句话说,一个kobject对自己事情不感兴趣;而是将高级的对象绑定到设备模型中去。

Thus, it is rare (even unknown) for kernel code to create a standalone kobject; instead, kobjects are used to control access to a larger, domain-specific object. To this end, kobjects are found embedded in other structures. If you are used to thinking of things in object-oriented terms, kobjects can be seen as a top-level, abstract class from which other classes are derived. A kobject implements a set of capabilities that are not particularly useful by themselves but that are nice to have in other objects. The C language does not allow for the direct expression of inheritance, so other techniques—such as embedding one structure in another—must be used.

因此,很少(甚至没有)内核代码区去创建一个单独的kobject;相反,kobject用来控制和存取更大的,特定部分的对象。为此,kobject被嵌入到其他结构体里。如果你常用面向对象的思想想问题,kobject可被当做顶级抽象类,其他类继承于他。kobject实现的一系列功能不是针对自身,而是很好的应用于其他对象。C语法不允许直接表达继承,因此用其他手段来实现-如嵌入数据结构到另一个。

As an example, let's look back at struct cdev , which we encountered in Chapter 3 . That structure, as found in the 2.6.10 kernel, looks like this:

下面一个例子,让我们回过头看看 struct cdev,这在第3章遇到过。在内核2.6.10中如下:
struct cdev {

    struct kobject kobj;

    struct module *owner;

    struct file_operations *ops;

    struct list_head list;

    dev_t dev;

    unsigned int count;

};

 

As we can see, the cdev structure has a kobject embedded within it. If you have one of these structures, finding its embedded kobject is just a matter of using the kobj field. Code that works with kobjects often has the opposite problem, however: given a struct kobject pointer, what is the pointer to the containing structure? You should avoid tricks (such as assuming that the kobject is at the beginning of the structure), and, instead, use the container_of macro (introduced in Section 3.5.1 ). So the way to convert a pointer to a struct kobject called kp embedded within a struct cdev would be:

如我们所见,cdev结构体里嵌入一个kobject。如果你有一个这样的结构体,找到嵌入的kobject只需调用成员kobj。而使用kobject的代码常常会遇到相反的问题,给出一个strcut kobject指针,如何得到包含它的结构体的指针呢?可以采取规避措施(如将kobject放在结构体的开始部分),或用  container_of 宏(2.5.1节有介绍)。这样转换嵌入在struct cdev 中的struct kobject 类型的指针kp 方法:

struct cdev *device = container_of(kp, struct cdev, kobj);

 

Programmers often define a simple macro for " back-casting" kobject pointers to the containing type.

编程人员常定义一个简单的宏,即指向被包含类型的“back-casting”kobject指针。

14.1.1.2 Kobject initialization
14.1.1.2 kobject初始化

This book has presented a number of types with simple mechanisms for initialization at compile or runtime. The initialization of a kobject is a bit more complicated, especially when all of its functions are used. Regardless of how a kobject is used, however, a few steps must be performed.

本书展示了许多数据类型,都是简单在编译或运行初始化。初始化kobject稍微有些复杂,特别是要使用他们自己的函数。不管如何使用一个kobject,需要几个步骤:

The first of those is to simply set the entire kobject to 0 , usually with a call to memset . Often this initialization happens as part of the zeroing of the structure into which the kobject is embedded. Failure to zero out a kobject often leads to very strange crashes further down the line; it is not a step you want to skip.

首先,设置整个kobject为0,常调用memset函数。常常这个初始化动作作为被kobject嵌入的数据结构的归零操作的一部分。kobject清零失败常会引起奇怪的崩溃,甚至掉线;这一步是不可绕过的。

The next step is to set up some of the internal fields with a call to kobject_init( ) :

下一步是通过调用kobject_init()设置内部成员:

void kobject_init(struct kobject *kobj);

 

Among other things, kobject_init sets the kobject's reference count to one. Calling kobject_init is not sufficient, however. Kobject users must, at a minimum, set the name of the kobject; this is the name that is used in sysfs entries. If you dig through the kernel source, you can find the code that copies a string directly into the kobject's name field, but that approach should be avoided. Instead, use:

除了别的以外,kobject_init 设置kobject的引用计数为1。调用kobject_init还不够,kobject用户至少还需设置kobject的名字;这个名字作为sysfs的入口。如果你去分析内核源码,就会发现时其实是直接拷贝一字符串到kobject的name成员,但这种做法需避免,应为:

int kobject_set_name(struct kobject *kobj, const char *format, ...);

This function takes a printk -style variable argument list. Believe it or not, it is actually possible for this operation to fail (it may try to allocate memory); conscientious code should check the return value and react accordingly.

这个函数使用的是printk风格的参数列表。不管你相信与否,这个操作有可能失败(它可能试图分配内存);健壮的代码需检查返回值以及相应的处理。

The other kobject fields that should be set, directly or indirectly, by the creator are ktype , kset , and parent . We will get to these later in this chapter.

kobject的其他成员需直接或间接地由创建者设置,创建者包括ktype,kset和parent。我们将本章后面介绍。

14.1.1.3 Reference count manipulation
14.1.1.3 引用计数的操作

One of the key functions of a kobject is to serve as a reference counter for the object in which it is embedded. As long as references to the object exist, the object (and the code that supports it) must continue to exist. The low-level functions for manipulating a kobject's reference counts are:

kobject的一个关键功能是作为嵌入对象的引用计数器。只要这个对这个对象的引用存在,对象(和对象的嗲吗)就必须继续存在。来操作kobject引用计数的低层次函数为:

struct kobject *kobject_get(struct kobject *kobj);

void kobject_put(struct kobject *kobj);

 

A successful call to kobject_get increments the kobject's reference counter and returns a pointer to the kobject. If, however, the kobject is already in the process of being destroyed, the operation fails, and kobject_get returns NULL . This return value must always be tested, or no end of unpleasant race conditions could result.

成功调用一次kobject_get会增加一次kobject的引用计数并返回kobject指针。如果这时kobject已经在被销毁过程中,操作将失败,kobject_get返回NULL.这个返回值应该被检查,否则多个令人讨厌的竞争条件会发生。

When a reference is released, the call to kobject_put decrements the reference count and, possibly, frees the object. Remember that kobject_init sets the reference count to one; so when you create a kobject, you should make sure that the corresponding kobject_put call is made when that initial reference is no longer needed.

当一个引用被释放的时候,对kobject_put的调用将减少引用计数以及肯能会释放对象。记住kobjet_init初始化引用计数为1;因此创建一个kobject,应该确保在初始化的引用不再需要的时候,相应的调用kobject_put。

Note that, in many cases, the reference count in the kobject itself may not be sufficient to prevent race conditions. The existence of a kobject (and its containing structure) may well, for example, require the continued existence of the module that created that kobject. It would not do to unload that module while the kobject is still being passed around. That is why the cdev structure we saw above contains a struct module pointer. Reference counting for struct cdev is implemented as follows:

注意:在很多情况下,kobject的引用计数自身无法有效地阻止竞争发生。但kobject的存在(和他包含的数据结构)可以,例如必须已经存在的模块才能创建kobject,而正在传递kobject的模块是不能再次被加载的。这就是为什么我们上面看到cdev结构体含有 struct module指针。struct cdev的引用计数的实现如下:

struct kobject *cdev_get(struct cdev *p)

{

    struct module *owner = p->owner;

    struct kobject *kobj;



    if (owner && !try_module_get(owner))

        return NULL;

    kobj = kobject_get(&p->kobj);

    if (!kobj)

        module_put(owner);

    return kobj;

}

Creating a reference to a cdev structure requires creating a reference also to the module that owns it. So cdev_get uses try_module_get to attempt to increment that module's usage count. If that operation succeeds, kobject_get is used to increment the kobject's reference count as well. That operation could fail, of course, so the code checks the return value from kobject_get and releases its reference to the module if things don't work out.

创建一个cdev结构的引用需要同时创建一个模块的引用。这样cdev_get 用try_module_get 试图增加模块的使用计数。如果操作成功,kobject_get 增加kobject的引用计数就没有问题。当然操作(kobject_get )可能失败,因此检查kobject_get的返回值并且如果不工作了就释放模块的引用。

14.1.1.4 Release functions and kobject types
14.1.1.4 kobject的释放函数和数据类型

One important thing still missing from the discussion is what happens to a kobject when its reference count reaches 0 . The code that created the kobject generally does not know when that will happen; if it did, there would be little point in using a reference count in the first place. Even predictable object life cycles become more complicated when sysfs is brought in; user-space programs can keep a reference to a kobject (by keeping one of its associated sysfs files open) for an arbitrary period of time.

一个重要的事情仍然没有讨论,那就是当引用计数变味0时kobject会发生什么。创建kobject的代码本身不知道这会什么时候发生;如果知道,首先引用计数就没有存在意义了。另外sysfs的引入使对象的生命周期的可预见行更加复杂;用户空间的程序可在任意的时间持有对象的引用。

The end result is that a structure protected by a kobject cannot be freed at any single, predictable point in the driver's lifecycle, but in code that must be prepared to run at whatever moment the kobject's reference count goes to 0 . The reference count is not under the direct control of the code that created the kobject. So that code must be notified asynchronously whenever the last reference to one of its kobjects goes away.

结果是一个kobject保护的数据结构不能单独在可预测的驱动周期里被释放,但(释放)代码需要随时等待kobject的引用计数变为0时运行。引用计数不受创建kobject代码的直接控制。因此当任何时间一个kobject的最后一次引用被释放,相应的代码应该异步的被通知。

This notification is done through a kobject's release method. Usually, this method has a form such as:

这个通知是通过kobject的释放函数来做的。一般的形式如下:
void my_object_release(struct kobject *kobj)

{

    struct my_object *mine = container_of(kobj, struct my_object, kobj);



    /* Perform any additional cleanup on this object, then... */

    kfree(mine);

}

 

One important point cannot be overstated: every kobject must have a release method, and the kobject must persist (in a consistent state) until that method is called. If these constraints are not met, the code is flawed. It risks freeing the object when it is still in use, or it fails to release the object after the last reference is returned.

有一点要强调的是:每个kobect都有一个释放函数,在释放函数被调用之前,kobect应一直存在。如果这些限制条件不满足的话,代码就有缺陷。(因为,)释放正在使用的对象是有风险的,或者最后一次引用返回之后释放对象会失败。

Interestingly, the release method is not stored in the kobject itself; instead, it is associated with the type of the structure that contains the kobject. This type is tracked with a structure of type struct kobj_type , often simply called a "ktype." This structure looks like the following:

有趣的是,释放函数不是保存在kobject里;而是与一个包含kobject的数据结构有关。这个结构为struct kobj_type,常简写为"ktype."如下:

struct kobj_type {

    void (*release)(struct kobject *);

    struct sysfs_ops *sysfs_ops;

    struct attribute **default_attrs;

};

 

The release field in struct kobj_type is, of course, a pointer to the release method for this type of kobject. We will come back to the other two fields (sysfs_ops and default_attrs ) later in this chapter.

struct kobj_type的成员 release 就是 一个指向kobject的释放函数的指针。我们在本章的后面来看其他两个成员(sysfs_ops和default_attrs)。

Every kobject needs to have an associated kobj_type structure. Confusingly, the pointer to this structure can be found in two different places. The kobject structure itself contains a field (called ktype ) that can contain this pointer. If, however, this kobject is a member of a kset, the kobj_type pointer is provided by that kset instead. (We will look at ksets in the next section.) Meanwhile, the macro:

struct kobj_type *get_ktype(struct kobject *kobj);

finds the kobj_type pointer for a given kobject.

每个kobject需要一个对应的kobj_type结构体。易迷惑的是,这个结构体的指针会在两个地方遇到。kobject结构体里包含一个成员(ktype),它包含这个指针。如果这个kobject是kset的一个成员,这个kset将提供kobj_type指针。(我们将在下一节介绍ksets)同时,宏:struct kobj_type *get_ktype(struct kobject *kobj);  给定一个kobject来得到kobj_type指针。

14.1.2. Kobject Hierarchies, Ksets, and Subsystems
14.1.2.  Kobject 层次,Ksets 及其子系统

The kobject structure is often used to link together objects into a hierarchical structure that matches the structure of the subsystem being modeled. There are two separate mechanisms for this linking: the parent pointer and ksets.

kobject结构体常用来将对象加入一个层次结构中,来匹配正在建模的结构的子系统。这有两种连接体系:parent指针和ksets。

The parent field in struct kobject is a pointer to another kobject—the one representing the next level up in the hierarchy. If, for example, a kobject represents a USB device, its parent pointer may indicate the object representing the hub into which the device is plugged.

struct kobject的parent成员是一个指向其它kobject的指针—其代表下一个级的层次。如果,假设,kobject是一个USB设备,他的parent指针可能表示(USB)设备所插入的hub对象。

The main use for the parent pointer is to position the object in the sysfs hierarchy. We'll see how this works in Section 14.2 .

parent指针的主要作用在sysfs层次中定位。我们在14.2节中看到其如何工作:

14.1.2.1 Ksets
14.1.2.1 Ksets

In many ways, a kset looks like an extension of the kobj_type structure; a kset is a collection of kobjects embedded within structures of the same type. However, while struct kobj_type concerns itself with the type of an object, struct kset is concerned with aggregation and collection. The two concepts have been separated so that objects of identical type can appear in distinct sets.

在很多情况下,kset像是一个kobj_type结构的扩展;kset是多个相同类型kobject的集合。但是,struct kobj_type关心自己的对象类型,struct kset 关心的(对象)是收集和聚合。两个侧重点的不同使其(kobject)呈现在不同的集合中。

Therefore, the main function of a kset is containment; it can be thought of as the top-level container class for kobjects. In fact, each kset contains its own kobject internally, and it can, in many ways, be treated the same way as a kobject. It is worth noting that ksets are always represented in sysfs; once a kset has been set up and added to the system, there will be a sysfs directory for it. Kobjects do not necessarily show up in sysfs, but every kobject that is a member of a kset is represented there.

因此,kset的主要的功能是容纳(kobject);她被当做是顶级kobject的容器类。实际上,每个kset内部包含自身的kobject,在多数情况下,他可被当做是一个kobject对待。值得注意的是kset常出现sysfs中;一旦一个kset被加进系统中,会有一个sysfs目录对应。kobject没有出现在sysfs中,但所有的kobject是kset作为kset的成员出现。

Adding a kobject to a kset is usually done when the object is created; it is a two-step process. The kobject's kset field must be pointed at the kset of interest; then the kobject should be passed to:

增加一个kobject到kse是t在对象创建时完成;有两步进行。kobject的kset成员应指向对应的kset;然后kobject传递到:

int kobject_add(struct kobject *kobj);

As always, programmers should be aware that this function can fail (in which case it returns a negative error code) and respond accordingly. There is a convenience function provided by the kernel:

同时,程序员应该注意到这个函数可能失败(失败返回负值)并作出相应的响应。有一个内核提供的方便的函数:

extern int kobject_register(struct kobject *kobj);

This function is simply a combination of kobject_init and kobject_add .

这个函数是kobject_init和kobject_add的简单合体。

When a kobject is passed to kobject_add , its reference count is incremented. Containment within the kset is, after all, a reference to the object. At some point, the kobject will probably have to be removed from the kset to clear that reference; that is done with:

当一个kobject被传递到kobject_add,它的引用计数增加。作为一个容器的kset,也就是一个对象的引用(kobject)。有时,kobject可能需要从kset中移除,并清理引用;需要:

void kobject_del(struct kobject *kobj);

 

There is also a kobject_unregister function, which is a combination of kobject_del and kobject_put .

还有一个 kobject_unregister函数,它是kobject_del和kobject_put的合体。

A kset keeps its children in a standard kernel linked list. In almost all cases, the contained kobjects also have pointers to the kset (or, strictly, its embedded kobject) in their parent's fields. So, typically, a kset and its kobjects look something like what you see in Figure 14-1 . Bear in mind that:

  • All of the contained kobjects in the diagram are actually embedded within some other type, possibly even other ksets.

  • It is not required that a kobject's parent be the containing kset (although any other organization would be strange and rare).

一个kset在标准的内核链表呵护子女。在多数情况下,被包含的kobject在他们的parent的成员中同时也有一个指针指向kset(或者严格来说,一个嵌入的kobject)。因此典型的,一个kset和他的kobject看起来如图14-1。 请记住:

  • 所有被包含的在图中的kobject实际上嵌入到其他的类型中去,可是其他的kset。

  • 可能不需要一个kobject的parent是包含的kset(虽然其它的结构很古怪和很少用到)。

 

 

Figure 14-2. A simple kset hierarchy

一个简单的 kset 层次


14.1.2.2 Operations on ksets
14.1.2.2 kset的操作

For initialization and setup, ksets have an interface very similar to that of kobjects. The following functions exist:

初始化和设置的kset操作接口和kobject非常相似。如下:

void kset_init(struct kset *kset);

int kset_add(struct kset *kset);

int kset_register(struct kset *kset);

void kset_unregister(struct kset *kset);

 

For the most part, these functions just call the analogous kobject_ function on the kset's embedded kobject.

大多部分,这些函数只是调用嵌入在kset的kobject的类似kobject_开头函数。

To manage the reference counts of ksets, the situation is about the same:

管理kset的引用计数也同样如下:

struct kset *kset_get(struct kset *kset);

void kset_put(struct kset *kset);

 

A kset also has a name, which is stored in the embedded kobject. So, if you have a kset called my_set , you would set its name with:

kset也有一个名字,保存在嵌入的kobject重。因此,如果有一个my_set的kset,你可以这样设置他的名字:

kobject_set_name(&my_set->kobj, "The name");

 

Ksets also have a pointer (in the ktype field) to the kobj_type structure describing the kobjects it contains. This type is used in preference to the ktype field in a kobject itself. As a result, in typical usage, the ktype field in struct kobject is left NULL , because the same field within the kset is the one actually used.

kset也有一个指针指向描述它包含的kobject类型的kobj_type(成员ktype)。这个类型在kobject的成员ktype也有。那么,在实际使用中,struct kobject的成员ktype为NULL,因为在kset的相同的成员实际只用一个就行了。

Finally, a kset contains a subsystem pointer (called subsys). So it's time to talk about subsystems.

最后,一个kset包含一个子系统指针(subsys)。现在该讨论下它了。

14.1.2.3 Subsystems
14.1.2.3 子系统

A subsystem is a representation for a high-level portion of the kernel as a whole. Subsystems usually (but not always) show up at the top of the sysfs hierarchy. Some example subsystems in the kernel include block_subsys (/sys/block , for block devices), devices_subsys (/sys/devices , the core device hierarchy), and a specific subsystem for every bus type known to the kernel. A driver author almost never needs to create a new subsystem; if you feel tempted to do so, think again. What you probably want, in the end, is to add a new class, as discussed in Section 14.5 .

一个子系统从整体来看作为内核高级部分的代表。它常常(但不是一直)出现在sysfs的顶层。一些内核的例子子系统包括_subsys(/sys/block,作为块设备),devices_subsys(/sys/devices,作为设备核心层),以及一些特定的所有内核已知的总线子系统。一个驱动作者几乎不需要创建一个子系统;如果你想去尝试,那就想想你可能需要什么,最后是增加一个新类,如在14.5 中讨论的那样。

A subsystem is represented by a simple structure:

一个子系统的简单数据结构:

struct subsystem {

    struct kset kset;

    struct rw_semaphore rwsem;

};

A subsystem, thus, is really just a wrapper around a kset, with a semaphore thrown in.

一个子系统实际上是一个kset的包装,放一个信号量(semaphore)给里面。

Every kset must belong to a subsystem. The subsystem membership helps establish the kset's position in the hierarchy, but, more importantly, the subsystem's rwsem semaphore is used to serialize access to a kset's internal-linked list. This membership is represented by the subsys pointer in struct kset . Thus, one can find each kset's containing subsystem from the kset's structure, but one cannot find the multiple ksets contained in a subsystem directly from the subsystem structure.

所有的kset都属于一个字系统。这个系统的成员关系组织kset在层次的位置,但重要的是,子系统的rwsem成员用来连续存取kset的内部链表。这个关系有struct kset的subsys指针来表示。因此,一方面可以在kset的结构中发现每个kset包含子系统,但不能再子系统的结构中发现多个kset包含在子系统中。

Subsystems are often declared with a special macro:

子系统常用一个特定的宏来声明:

decl_subsys(name, struct kobj_type *type, 

            struct kset_hotplug_ops *hotplug_ops);

 

This macro creates a struct subsystem with a name formed by taking the name given to the macro and appending _subsys to it. The macro also initializes the internal kset with the given type and hotplug_ops . (We discuss hotplug operations later in this chapter.)

这个宏通过所给的name和_subsys组合成一个名字的struct subsystem。这个宏业通过所给的type和hotplug_ops定义了一个内部kset。(我们在本章的后面介绍hotplug操作)

Subsystems have the usual list of setup and teardown functions:

子系统常用的建立和撤销函数:

void subsystem_init(struct subsystem *subsys);

int subsystem_register(struct subsystem *subsys);

void subsystem_unregister(struct subsystem *subsys);

struct subsystem *subsys_get(struct subsystem *subsys)

void subsys_put(struct subsystem *subsys);

 

Most of these operations just act upon the subsystem's kset.

这些大部分操作时建立在子系统的kset之上的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值