Android Binder框架实现之Binder中的数据结构

     Android Binder框架实现之Binder中的数据结构


Android Binder框架实现目录:

Android Binder框架实现之Binder中的数据结构
Android Binder框架实现之Binder相关的接口和类
Android Binder框架实现之Parcel详解之基本数据的读写
Android Binder框架实现之Parcel read/writeStrongBinder实现
Android Binder框架实现之servicemanager守护进程
Android Binder框架实现之defaultServiceManager()的实现
Android Binder框架实现之Native层addService详解之请求的发送
Android Binder框架实现之Native层addService详解之请求的处理
Android Binder框架实现之Binder服务的消息循环
Android Binder框架实现之Native层getService详解之请求的发送
Android Binder框架实现之Native层getService详解之请求的处理
Android Binder框架实现之Native层getService详解之请求的反馈
Android Binder框架实现之Binder Native Service的Java调用流程
Android Binder框架实现之Java层Binder整体框架设计
Android Binder框架实现之Framework层Binder服务注册过程源码分析
Android Binder框架实现之Java层Binder服务跨进程调用源码分析
Android Binder框架实现之Java层获取Binder服务源码分析


引言

   正所谓一切经典的设计都是建立在完美的数据结构前提下,Binder也不例外,在对Binder代码展开详细介绍之前,先列举出Binder机制中涉及到的数据结构。本文是一篇参考文章,读者在阅读代码的过程中遇到相关的数据结构,就可以查阅此文中的内容。本文列举的数据结构,涵盖了内核空间和用户空间两个部分。内核空间部分就是Binder驱动中涉及到的数据结构;而用户空间的部分,包括ServiceManager守护进程,以及Android的C++层和Framework层的相关数据结构。
   在正式开始正文介绍前附上本篇相关代码的所在文件的路径。

kernel/drivers/staging/android/binder.c
kernel/include/linux/list.h
kernel/drivers/staging/android/uapi/binder.h
external/kernel-headers/original/uapi/linux/android/binder.h
framework/native/cmds/servicemanager/binder.c
frameworks/native/cmds/servicemanager/service_manager.c
frameworks/native/include/binder/Parcel.h

注意:本文是基于Android 7.1版本进行介绍的!




1. 内核空间的Binder数据结构

   在介绍Binder驱动中的数据结构时,先回顾一下上一篇提到的"内核中的Binder设计图",有一个整体印象。
前面说过,binder_proc是描述进程上下文信息的,每一个用户空间的进程都对应一个binder_proc结构体。binder_node是Binder实体对应的结构体,它是具体Server在Binder驱动中的体现。binder_ref是Binder引用对应的结构体,它是Client在Binder驱动中的体现。

1.1 binder_proc

   binder_proc是内核中描述Binder进程上下文信息的结构体,其在用户空间相对应的是ProcessState。Binder驱动的文件节点是"/dev/binder",每当一个程序打开该文件节点时;Binder驱动中都会新建一个binder_proc对象来保存该进程的上下文信息。

struct binder_proc {
	struct hlist_node proc_node;	// 根据proc_node,可以获取该进程在"全局哈希表binder_procs(统计了所有的binder proc进程)"中的位置,即该进程是全局hash表中的一个节点
	struct rb_root threads;			// binder_proc进程内用于处理用户请求的线程binder_thread组成的红黑树(关联binder_thread->rb_node)
	struct rb_root nodes;			// binder_proc进程内的binder实体binder_node组成的红黑树(关联binder_node->rb_node)
	struct rb_root refs_by_desc;	// binder_proc进程内的binder引用binder_ref组成的红黑树,该引用以句柄来排序(关联binder_ref->rb_node_desc)
	struct rb_root refs_by_node;	// binder_proc进程内的binder引用binder_ref组成的红黑树,该引用以它对应的binder实体的地址来排序(关联binder_ref->rb_node)
	int pid;						//进程pid
	struct vm_area_struct *vma;		// 进程的内核虚拟内存
	struct mm_struct *vma_vm_mm;
	struct task_struct *tsk;		// 进程控制结构体(每一个进程都由task_struct 数据结构来定义)。
	struct files_struct *files;		// 保存了进程打开的所有文件表数据
	struct hlist_node deferred_work_node;
	int deferred_work;
	void *buffer;					// 该进程映射的物理内存在内核空间中的起始位置
	ptrdiff_t user_buffer_offset;	// 内核虚拟地址与进程虚拟地址之间的差值
	
	// 内存管理的相关变量
	struct list_head buffers;		 // 和binder_buffer->entry关联到同一链表,从而对Binder内存进行管理
	struct rb_root free_buffers;	// 空闲内存,和binder_buffer->rb_node关联。
	struct rb_root allocated_buffers;// 已分配内存,和binder_buffer->rb_node关联。
	size_t free_async_space;

	struct page **pages;			// 映射内存的page页数组,page是描述物理内存的结构体
	size_t buffer_size;				// 映射内存的大小
	uint32_t buffer_free;
	struct list_head todo;			// 该进程的待处理事件队列。
	wait_queue_head_t wait;			// 等待队列。
	struct binder_stats stats;
	struct list_head delivered_death;
	int max_threads;				// 最大线程数。定义threads中可包含的最大进程数。
	int requested_threads;
	int requested_threads_started;
	int ready_threads;				//默认优先级
	long default_priority;
	struct dentry *debugfs_entry;
};

   说明:binder_proc定义在kernel/drivers/staging/android/binder.c中,并且由于定义在.c文件中,可见binder_proc是Binder驱动的私有结构体,不能被其它模块使用。上面已经给出了相关成员的注释,这里只对部分比较重要的成员进行说明。
(01) proc_node, 它的作用是通过proc_node,将该binder_proc添加到"全局哈希表binder_procs(它记录了所有的binder_proc)"。 binder_procs总表的定义如下:

static HLIST_HEAD(binder_procs);

我们可以在kernel/include/linux/list.h中看到HLIST_HEAD的定义:

#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }

于是binder_procs的定义相当于:

struct hlist_head binder_procs  = { .first = NULL };

随着后续不断向binder_procs表中添加节点,这个表会不断加长,示意图如下:
在这里插入图片描述

(02) threads,它是包含该进程内用于处理用户请求的所有线程的红黑树。threads成员和binder_thread->rb_node关联到一棵红黑树,从而将binder_proc和binder_thread关联起来。
(03) nodes,它是包含该进程内的所有Binder实体所组成的红黑树。nodes成员和binder_node->rb_node关联到一棵红黑树,从而将binder_proc和binder_node关联起来。nodes树的示意图如下::

在这里插入图片描述
(04) refs_by_desc,它是包行该进程内的所有Binder引用所组成的红黑树。refs_by_desc成员和binder_ref->rb_node_desc关联到一棵红黑树,从而将binder_proc和binder_ref关联起来。示意图和nodes的类似。
(05) refs_by_node,它是包行该进程内的所有Binder引用所组成的红黑树。refs_by_node成员和binder_ref->rb_node_node关联到一棵红黑树,从而将binder_proc和binder_ref关联起来。示意图和nodes的类似。
关于binder_proc中的4个红黑树,可以使用下述的示意图来表述:
在这里插入图片描述
(06) buffer,它是该进程内核虚拟内存的起始地址。而user_buffer_offset,则是该内核虚拟地址和进程虚拟地址之间的差值。在Binder驱动中,将进程的内核虚拟地址和进程虚拟地址映射到同一物理页面,该user_buffer_offset则是它们之间的差值;这样,已知其中一个,就可以根据差值算出另外一个。
(07) todo,是该进程的待处理事务队列,而wait则是等待队列。它们的作用是实现进程的等待/唤醒。例如,当Server进程的wait等待队列为空时,Server就进入中断等待状态;当某Client向Server发送请求时,就将该请求添加到Server的todo待处理事务队列中,并尝试唤醒Server等待队列上的线程。如果,此时Server的待处理事务队列不为空,则Server被唤醒后;唤醒后,则取出待处理事务进行处理,处理完毕,则将结果返回给Client。


1.2 binder_thread

   binder_thread是内核层描述Binder线程的结构体,在用户层与其相对应的是IPCThreadState。binder_proc是描述进程的,而binder_thread是描述进程中的线程。从上面对binder_proc结构体的描述我们可以知道binder_proc可以同时包含多个binder_thread,即一对多的关系。

struct binder_thread {
	struct binder_proc *proc;	//线程所属的Binder进程
	struct rb_node rb_node;		//红黑树节点,关联到所属进程binder_proc->threads的红黑树中
	int pid;					//进程pid
	int looper;					// 线程状态。可以取BINDER_LOOPER_STATE_REGISTERED等值
	struct binder_transaction *transaction_stack;	// 正在处理的事务栈
	struct list_head todo;							// 待处理的事务链表
	uint32_t return_error; /* Write failed, return error code in read buf */
	uint32_t return_error2; /* Write failed, return error code in read */
		/* buffer. Used when sending a reply to a dead process that */
		/* we are also waiting on */
	wait_queue_head_t wait;							//等待队列
	struct binder_stats stats;						//保存一些统计信息
};

说明:binder_thread是Binder驱动的私有结构体,它定义在kernel/drivers/staging/android/binder.c中。binder_thread是内核层描述Binder线程相关信息的结构体。
(01) proc,它是binder_proc(进程上下文信息)结构体对象。目的是保存该线程所属的Binder进程。
(02) rb_node,它是红黑树节点。通过将rb_node binder关联到所属进程红黑树proc->threads中,从而将该线程添加到进程的threads红黑树中进行管理。


1.3 binder_node

   binder_node是内核层描述Binder实体的结构体,用户层与之相对应的是具体的Binder Server。其在Android整个框架中的位置如下所示:
在这里插入图片描述

struct binder_node {
	int debug_id;
	struct binder_work work;
	union {
		struct rb_node rb_node;			 // 如果这个Binder实体还在使用,则将该节点链接到proc->nodes中。
		struct hlist_node dead_node;	 // 如果这个Binder实体所属的进程已经销毁,而这个Binder实体又被其它进程所引用,则这个Binder实体通过dead_node进入到一个哈希表中去存放
	};
	struct binder_proc *proc;			//该Binder实体所属的Binder进程
	struct hlist_head refs;				//该Binder实体的所有Binder引用组成的链表
	int internal_strong_refs;
	int local_weak_refs;
	int local_strong_refs;
	binder_uintptr_t ptr;				// Binder实体在用户空间的地址(为Binder实体对应的Server在用户空间的本地Binder的引用)
	binder_uintptr_t cookie;			// Binder实体在用户空间的其他数据(为Binder实体对应的Server在用户空间的本地Binder自身)
	unsigned has_strong_ref:1;
	unsigned pending_strong_ref:1;
	unsigned has_weak_ref:1;
	unsigned pending_weak_ref:1;
	unsigned has_async_transaction:1;
	unsigned accept_fds:1;
	unsigned min_priority:8;
	struct list_head async_todo;
};

说明:binder_node是Binder驱动的私有结构体,它定义在drivers/staging/android/binder.c中。binder_node是内核层描述Binder实体相关信息的结构体。
(01) rb_nodedead_node属于一个union。如果该Binder实体还在使用,则通过rb_node将该节点链接到proc->nodes红黑树中;否则,则将该Binder实体通过dead_node链接到全局哈希表binder_dead_nodes中。
(02) proc,它是binder_proc(进程上下文信息)结构体对象。目的是保存该Binder实体的进程。
(03) refs,它是该Binder实体的所有引用所组成的链表。

在Binder驱动中,会为每一个Server都创建一个Binder实体,即会为每个Server都创建一个binder_node对象。


1.4 binder_ref

   binder_ref是内盒层描述Binder引用的结构体,用户层与之相对应的是某个具体服务的远程代理BpBinder或者BinderProxy。

struct binder_ref {
	/* Lookups needed: */
	/*   node + proc => ref (transaction) */
	/*   desc + proc => ref (transaction, inc/dec ref) */
	/*   node => refs + procs (proc exit) */
	int debug_id;
	struct rb_node rb_node_desc;	//关联到所属进程binder_proc->refs_by_desc红黑树中
	struct rb_node rb_node_node;	// 关联到所属进程binder_proc->refs_by_node红黑树中
	struct hlist_node node_entry;	// 关联到binder_node->refs哈希表中
	struct binder_proc *proc;		//该Binder引用所属的Binder进程
	struct binder_node *node;		//该Binder引用对应的Binder实体
	uint32_t desc;					//描述
	int strong;
	int weak;
	struct binder_ref_death *death;
};

说明:binder_ref是Binder驱动的私有结构体,它定义在drivers/staging/android/binder.c中。它是用来描述Binder引用的相关信息的。
(01) rb_node_descrb_node_node都是红黑树节点。通过rb_node_desc,Binder引用和binder_proc->refs_by_desc红黑树相关联;通过rb_node_node,Binder引用和binder_proc->refs_by_node红黑树相关联。
(02) node是该Binder引用所引用的Binder实体。而node_entry则是关联到该Binder实体的binder_node->refs哈希表中。
(03) proc,它是binder_proc(进程上下文信息)结构体对象。目的是保存该Binder引用所属的进程。
(04) desc是Binder引用的描述,实际上它就是Binder驱动为该Binder分配的一个唯一的int型整数。通过该desc,可以在binder_proc->refs_by_desc中找到该Binder引用,进而可以找到该Binder引用所引用的Binder实体等信息。

在Binder驱动中,会为每个Client创建对应的Binder引用,即会为每个Client创建binder_ref对象。

"Binder实体"和"Binder引用"可以很好的将Server和Client关联起来:因为Binder实体和Binder引用分别是Server和Client在Binder驱动中的体现。 Client获取到Server对象后,“Binder引用所引用的Binder实体(即binder_ref.node)” 会指向 “Server对应的Binder实体”;同样的,Server被某个Client引用之后,“Server对应的Binder实体的引用列表(即,binder_node.refs)” 会包含 “Client对应的Binder引用”。
现在,我们有了binder_ref和binder_node知识,可以通过如下的示意图来描述二者之间的联系:
在这里插入图片描述


1.5 binder_buffer

   binder_buffer是描述Binder进程所管理的每段内存的结构体。

struct binder_buffer {
	struct list_head entry; 	// 和binder_proc->buffers关联到同一链表,从而使Binder进程对内存进行管理。
	struct rb_node rb_node; 	// 和binder_proc->free_buffers或binder_proc->allocated_buffers关联到同一红黑树,从而对已有内存和空闲内存进行管理。
	unsigned free:1;			// 空闲与否的标记
	unsigned allow_user_free:1;
	unsigned async_transaction:1;
	unsigned debug_id:29;

	struct binder_transaction *transaction;

	struct binder_node *target_node;
	size_t data_size;
	size_t offsets_size;
	uint8_t data[0];
};

说明:binder_buffer是Binder驱动的私有结构体,它定义在kernel/drivers/staging/android/binder.c中。Binder驱动将Binder进程的内存分成一段一段进行管理,而这每一段则是使用binder_buffer来描述的。


1.6 flat_binder_object

   flat_binder_object是描述Binder对象信息的结构体。

struct flat_binder_object {
	/* 8 bytes for large_flat_header. */
	__u32		type;		 		// 在这里插入代码片binder类型:可以为BINDER_TYPE_BINDER或BINDER_TYPE_HANDLE等类型
	__u32		flags;		 		// 标记

	/* 8 bytes of data. */
	union {
		binder_uintptr_t	binder;		// 当type=BINDER_TYPE_BINDER时,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)的弱引用。 
		__u32			handle;			// 当type=BINDER_TYPE_HANDLE时,它等于Binder对象在Binder驱动中对应的Binder实体的Binder引用的描述。
	};

	/* extra data associated with local object */
	binder_uintptr_t	cookie;			// 当type=BINDER_TYPE_BINDER时才有效,它指向Binder对象位于C++层的本地Binder对象(即BBinder对象)。
};

说明: flat_binder_object是用来描述Binder信息的结构体。它也属于内核空间和用户空间的通信结构体。当它在用户空间被使用时(例如,Server发送添加服务请求给ServiceManager),flat_binder_object就是记录的Server位于用户空间的Binder对象的信息的结构体;此时的type的值一般都是BINDER_TYPE_BINDER类型,对应的union中的binder的值是该Binder对象在用户空间的本地Binder(即BBinder对象)的引用;同时,cookie则是本地Binder自身。 而当flat_binder_object在Binder驱动中被使用(例如,当Binder驱动收到发送服务请求时),它会将type修改为BINDER_TYPE_HANDLE,然后将联合体中的handle修改为"该Server对应的Binder实体的Binder引用"的描述;根据Binder引用的描述就能找到该Server。总体来说,在用户空间,flat_binder_object是描述该Binder实体在用户空间的存在形式;而在内核空间中,flat_binder_object则描述该Binder实体在内核中的存在形式。


1.7 binder_write_read

   binder_write_read是用来描述Binder在驱动层和用户层进行数据读写交互的的结构体。

struct binder_write_read {
	binder_size_t		write_size;	/* bytes to write */
	binder_size_t		write_consumed;	/* bytes consumed by driver */
	binder_uintptr_t	write_buffer;
	binder_size_t		read_size;	/* bytes to read */
	binder_size_t		read_consumed;	/* bytes consumed by driver */
	binder_uintptr_t	read_buffer;
};

说明:binder_write_read是内核空间和用户空间的通信结构体,它记录了Binder读写内容的相关信息。在内核中,它定义在kernel/drivers/staging/android/uapi/binder.h中;在Android用户层中,它对应的引用在external/kernel-headers/original/uapi/linux/android/binder.h中。 当用户空间的应用程序和Binder驱动通信时,它会将数据打包到binder_write_read中。write开头的是记录应用程序要发送给Binder驱动的内容,而read开头的是记录Binder驱动要反馈给应用程序的内容。
(01) write_size,是写内容的总大小;write_consumed,是已写内容的大小;write_buffer,是写的内容的虚拟地址。
(02) read_size,是读内容的总大小;read_consumed,是已读内容的大小;read_buffer,是读的内容的虚拟地址。


1.8 binder_transaction_data

   binder_transaction_data是描述Binder事务交互的数据格式的结构体。

struct binder_transaction_data {
	union {
		__u32	handle;		// 当binder_transaction_data是由用户空间的进程发送给Binder驱动时,
							// handle是该事务的发送目标在Binder驱动中的信息,即该事务会交给handle来处理;	
							// handle的值是目标在Binder驱动中的Binder引用,Binder的引用在代码中也叫句柄(handle)
		binder_uintptr_t ptr;	//当binder_transaction_data是由Binder驱动反馈给用户空间进程时
								//ptr是该事务的发送目标在用户空间中的信息,即该事务会交给ptr对应的服务来处理;
								// ptr是处理该事务的服务在用户空间的本地Binder对象,即指向Binder对象内存的指针
	} target;					//该事务的目标对象(即,该事务数据包是给该target来处理的,具体是由handle还是ptr来制定,根据实际情况来确定)
	binder_uintptr_t	cookie;	//只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象
	__u32		code;			//该成员存放收发双方约定的命令码,驱动完全不关心该成员的内容.如果是请求,则以BC_开头;如果是回复,则以BR_开头。

	__u32	        flags;
	pid_t		sender_pid;
	uid_t		sender_euid;
	binder_size_t	data_size;		//数据大小
	binder_size_t	offsets_size;	//数据包中包含的对象的个数

	union {
		struct {
			binder_uintptr_t	buffer;		//真正保存通信数据的缓冲区
			binder_uintptr_t	offsets;	//记录Binder对象偏移的数组	
		} ptr;
		__u8	buf[8];
	} data;							//数据
};

说明:binder_transaction_data使用来描述Binder事务交互的数据结构体。它也属于内核空间和用户空间的通信结构体。现在,我们有了binder_write_read和binder_transaction_data知识,可以通过如下的示意图来描述二者数据之间的联系:
在这里插入图片描述


1.9 binder_transaction

   binder_transaction用来描述进程间通信过程,这个过程又称为一个事务,进程通信事务。

struct binder_transaction {
	int debug_id;
	struct binder_work work;			//设置事务类型
	struct binder_thread *from;			//发起事务的线程,称为源线程
	struct binder_transaction *from_parent;	//该事务所依赖的事务
	struct binder_proc *to_proc;			//处理该事务的进程
	struct binder_thread *to_thread;		//处理该事务的线程
	struct binder_transaction *to_parent;	//目标线程的下一个事务
	unsigned need_reply:1;					//用来区分一个事务是同步的还是异步的
	/* unsigned is_dead:1; */	/* not used at the moment */

	struct binder_buffer *buffer;			//为该事务分配的一块内核缓冲区,该缓冲区保存了通信数据
	unsigned int	code;					//进程间通信代码
	unsigned int	flags;					//标志位,描述进程间通信行为的特征
	long	priority;						//发起事务的线程优先级
	long	saved_priority;					//保存处理该事务的线程原有优先级
	kuid_t	sender_euid;					//发起事务的euid
};

说明:binder_transaction 是Binder驱动的私有结构体,它定义在kernel/drivers/staging/android/binder.c中,用来描述进程间Binder通信过程。



2 .用户空间的Binder关联数据结构

2.1 ServiceManager守护进程中的数据结构

2.1.1 binder_state
struct binder_state
{
    int fd;			//文件节点"/dev/binder"的句柄
    void *mapped;	//映射内存的起始地址
    size_t mapsize;	//映射内存的大小
};

说明:binder_state定义在frameworks/native/cmds/servicemanager/binder.c中,它是ServiceManager调用binder_open后用来描述打开的"/dev/binder"的信息结构体。


2.1.2 binder_io

binder_io是与binder_transaction_data对应的轻量级结构体。

struct binder_io
{
    char *data;            /* pointer to read/write from */
    binder_size_t *offs;   /* array of offsets */
    size_t data_avail;     /* bytes available in data buffer */
    size_t offs_avail;     /* entries available in offsets array */

    char *data0;           /* start of data buffer */
    binder_size_t *offs0;  /* start of offsets buffer */
    uint32_t flags;
    uint32_t unused;
};

说明:binder_io定义在frameworks/native/cmds/servicemanager/binder.h中,它是ServiceManager中与binder_transaction_data对应的结构体。


2.1.3 svcinfo
struct svcinfo
{
    struct svcinfo *next;
    uint32_t handle;
    struct binder_death death;
    int allow_isolated;
    size_t len;
    uint16_t name[0];
};

说明:svcinfo定义在frameworks/native/cmds/servicemanager/service_manager.c中。它是ServiceManager守护进程的私有结构体。
svcinfo是保存"注册到ServiceManager中的服务"的相关信息的结构体。它是一个单链表,在ServiceManager守护进程中的svclist是保存注册到ServiceManager中的服务的链表,它就是struct info类型。svcinfo中的next是指向下一个服务的节点,而ptr是该服务在Binder驱动中Binder引用的描述。name则是服务的名称。


2.2 C++层的数据结构

2.2.1 Parcel

Parcel是描述Binder在用户层服务代理端和服务端进行通信的信息的结构体。

class Parcel {
public:
    ...

    // 获取数据(返回mData)
    const uint8_t*      data() const;
    // 获取数据大小(返回mDataSize)
    size_t              dataSize() const;
    // 获取数据指针的当前位置(返回mDataPos)
    size_t              dataPosition() const;

private:
    ...

    status_t            mError;                                   
    uint8_t*            mData;            // 数据
    size_t              mDataSize;        // 数据大小
    size_t              mDataCapacity;    // 数据容量
    mutable size_t      mDataPos;         // 数据指针的当前位置
    size_t*             mObjects;         // 对象在mData中的偏移地址
    size_t              mObjectsSize;     // 对象个数
    size_t              mObjectsCapacity; // 对象的容量

    ...
}

说明:Parcel定义在frameworks/native/include/binder/Parcel.h中。



总结

   如上的就是在Binder通信过程中,内核层和应用层会经常见到的一些数据结构,理解和掌握这些数据结构对于从整体上去理解Binder的实现有着非常重要的作用,并且各位在跟读文章时可以时不时的跳转到这一篇章来强化一下这些数据结构,会对Binder学习起到意想不到的作用。
   最后,贴上我写该篇文章时参见的一些大作,非常值得细看的好文章。

http://disanji.net/2011/02/28/android-bnder-design/
红茶一杯话Binder

下一篇章Android Binder入门指南之servicemanager守护进程,敬请期待!

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值