Android 7.1 从底层到上层分析 Led 例子

Led 硬件原理图
Led硬件原理图

上图为我板子的Led硬件原理:通过 改变LED_CTL引脚的输出电平 点亮Led 。低电平时红灯亮,高电平时蓝灯亮。

 

1、驱动部分为上层提供 /dev/led 操作节点。

 led.c:

/*********************************************************************************
 *      Copyright:  (C) 2019 Arctan<2757904225.com>
 *                  All rights reserved.
 *
 *       Filename:  mycdev.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2019年04月10日)
 *         Author:  Arctan
 *      ChangeLog:  1, Release initial version on "2019年04月10日 10时39分47秒"
 *                 
 ********************************************************************************/
#include <linux/module.h>   /*  模块所需的大量符号和函数定义 */
#include <linux/init.h>     /*  指定初始化和清除函数 */
#include <linux/kernel.h>   /*  printk() */
#include <linux/fs.h>       /*  struct fops */
#include <linux/errno.h>    /*  error codes */
#include <linux/cdev.h>     /*  cdev_alloc()  */
#include <asm/uaccess.h>    /*在内核和用户空间中移动数据的函数copy_to_user和copy_from_user*/
#include <linux/device.h>    /*包含了device、class 等结构的定义*/
#include <linux/slab.h>     /* kmalloc()*/
#include <linux/platform_device.h> 
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio.h>
#include <asm/ioctl.h>      /*  系统空间的_IO 和 ioctl  */
#ifndef __KERNEL__
#include <sys/ioctl.h>      /*  用户空间的_IO 和 ioctl */
#endif
 
#define DRV_AUTHOR "Arctan 2757904225@qq.com"
#define DEV_NAME "led"
 
//#define DRV_NAME "mycdev_driver"               //ioctl() 函数传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值
#define MAGIC_NUM 0x97                           //ioctl()的cmd可以通过使用宏_IO()得到
#define LED_IOCTL_RED _IO(MAGIC_NUM, 0x01)        //_IO (魔数, 基数);
#define LED_IOCTL_BLUE _IO(MAGIC_NUM, 0x02)        //
#define LED_IOCTL_FLASH _IO(MAGIC_NUM, 0x03)        //详细解释:
//#define GET_NUM _IO(MAGIC_NUM, 0x04)        //http://blog.csdn.net/ghostyu/article/details/8085693
 
#define DEBUG_ON 

#ifdef DEBUG_ON
#define Led_Dbg(fmt,arg...) do{\
    printk("Led %s:%d "fmt"\n",__FUNCTION__,__LINE__,##arg);\
}while(0)
#else 
#define Led_Dbg

#endif 
 

static int Led_Ctrl;

int i_major = 0;
int i_minor = 0;
int private_data = 0;                         //驱动运行过程中可以用来保存中间数据,本例用来保存读写的数据
static struct cdev *LedDev;                    //字符设备结构体,每个字符设备对应一个结构体
static struct class *LedDev_class;            //为了实现设备节点文件自动挂载,class_create为该设备创建一个class,再为每个设备调用                      
                                            //class_device_create创建对应的设备
 
static int LedDev_open(struct inode *inode, struct file *file)  //用户空间调用open函数,最终调用的是这里的mycdev_open
{                                                                //下面带有read write ioctl字眼的函数同上
    file->private_data = &private_data;     //每个打开的文件都会对应一个内核分配的file结构体,file结构体中有一成员为:
    Led_Dbg("open \n");                        //void *private_data,我们可以利用该成员进行数据的传递。
    return 0;
}
 
static ssize_t LedDev_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
  
    char rcvBuf[20];
    
    Led_Dbg("mycdev read");    
    int ret = copy_from_user(rcvBuf,buf,len);
    int cmdValue = rcvBuf[0]&0x0f;
        
    
    Led_Dbg("--> mycdev read %d\n",cmdValue);    
    gpio_direction_output(Led_Ctrl,cmdValue);
    return sizeof(int);                         
    
}
 
static ssize_t LedDev_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
     
    char rcvBuf[20];
    
    Led_Dbg("mycdev write ");    
    int ret = copy_from_user(rcvBuf,buf,len);
    int cmdValue = rcvBuf[0]&0x0f;            
    Led_Dbg("cmdV : %d\n",cmdValue);
    gpio_direction_output(Led_Ctrl,cmdValue);
    
    return len;

}

 
static int LedDev_release(struct inode *inode, struct file *file)    //用户空间的close函数调用最终由本函数执行
{
    printk("closed \n");
    return 0;
}
 

static long CompatLedDev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int *data = file->private_data;                //驱动里的private_data ==》file->private_data ==》 data
    Led_Dbg("Compate ioctl cmd is %d ",cmd);
    switch(cmd)
    {
        case LED_IOCTL_RED:
        
            gpio_direction_output(Led_Ctrl,1);  // 红灯
            Led_Dbg("ioctl : LED_RED\n");
            break;

        case LED_IOCTL_BLUE:

            gpio_direction_output(Led_Ctrl,0);  // 蓝灯
            Led_Dbg("ioctl : LED_BLUE\n");
            break;
        
        case LED_IOCTL_FLASH:
            break;
        
        default:Led_Dbg("unkown commad\n");
    }
 
    return 0;
}

static long unLockLedDev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int *data = file->private_data;                //驱动里的private_data ==》file->private_data ==》 data
    Led_Dbg("unLock ioctl cmd is %d ",cmd);
    switch(cmd)
    {
        case LED_IOCTL_RED:
        
            gpio_direction_output(Led_Ctrl,1);  // 红灯
            Led_Dbg("ioctl : LED_RED\n");
            break;

        case LED_IOCTL_BLUE:

            gpio_direction_output(Led_Ctrl,0);  // 蓝灯
            Led_Dbg("ioctl : LED_BLUE\n");
            break;
        
        case LED_IOCTL_FLASH:
            break;
        
        default:Led_Dbg("unkown commad\n");
    }
 
    return 0;
}
 
 
static struct file_operations fops = // file_operation结构体详解
{                                        //http://blog.csdn.net/l627859442/article/details/7513817
    .owner = THIS_MODULE,                //就是这个结构体决定了用户空间的各种文件操作函数最终调用的实际函数
    .write = LedDev_write,
    .read = LedDev_read,
    .open = LedDev_open,
    .release = LedDev_release,
    .unlocked_ioctl = unLockLedDev_ioctl,
    .compat_ioctl = CompatLedDev_ioctl,
};

 


static const struct of_device_id of_gpio_match[] = {
    { .compatible = "videostrong,gpio" },
    { /*Sentinel*/ }

};

static int Led_probe(struct platform_device *pdev){

    int ret = -1;
    enum of_gpio_flags flag;

    struct device_node* led_node = pdev->dev.of_node;
    
    Led_Ctrl = of_get_named_gpio_flags(led_node,"POWR_LED",0,&flag);
    if(!gpio_is_valid(Led_Ctrl)){
        Led_Dbg("The Led_Ctr is not valid %d\n",Led_Ctrl);
    }
    ret = gpio_request(Led_Ctrl,"Led_Ctrl");
    if(ret!=0){
        gpio_free(Led_Ctrl);
        Led_Dbg("Gpio requese Led_Ctrl fail");
    }    

    return ret;
}


static int Led_remove(struct platform_device *pdev){
    
    return 0;
}


static struct  platform_driver Led_driver={
    .probe     = Led_probe,
    .remove   = Led_remove,
    .driver    ={
            .name  = "led_gpio",
            .owner = THIS_MODULE,
            .of_match_table = of_gpio_match,
    },

};

 

static int __init Led_cdev_init(void)        //整个驱动的执行起点
{
    int ret;
    int devno;
 

    int result = platform_driver_register(&Led_driver);  // 平台硬件初始化
    if(result){
        Led_Dbg("platform register fail");
        return -1;            
    }
 
    if( 0 != i_major)            //如果已经指定了主设备号
    {
        devno = MKDEV(i_major, 0);     //产生设备号,MKDEV(主设备号,次设备号)。
        ret = register_chrdev_region (devno, 1, DEV_NAME);       //静态注册字符设备号
    }
    else
    {
        ret = alloc_chrdev_region(&devno, i_minor, 1, DEV_NAME); //动态注册字符设备号,保证不和现有设备号冲突
        i_major = MAJOR(devno);                                        //由设备号计算出主设备号
    }
 
    if( ret < 0 )
    {
        Led_Dbg("%s driver can't use major %d\n", DEV_NAME, i_major);
        return -ENODEV;
    }   
    Led_Dbg("%s driver use major %d\n", DEV_NAME, i_major);
 
    if(NULL == (LedDev = cdev_alloc()) )                                    //静态内存定义初始化:
    {                                                                       //struct cdev my_cdev;
        Led_Dbg(KERN_ERR "%s driver can't alloc for the cdev.\n", DEV_NAME); //cdev_init(&my_cdev, &fops);
        unregister_chrdev_region(devno, 1);                                 //my_cdev.owner = THIS_MODULE;
        return -ENOMEM;                                                     //动态内存定义初始化:
    }                                                                       //struct cdev *my_cdev = cdev_alloc();
                                                                            //my_cdev->ops = &fops;
    LedDev->owner = THIS_MODULE;                                            //my_cdev->owner = THIS_MODULE;
    LedDev->ops = &fops;                                                    //
 
    ret = cdev_add(LedDev, devno, 1);            //向内核添加cdev结构体
    if (0 != ret)
    {   
        Led_Dbg("%s driver can't reigster cdev: result=%d\n", DEV_NAME, ret); 
        goto ERROR;
    }
    
    LedDev_class = class_create(THIS_MODULE, DEV_NAME);    
    device_create(LedDev_class, NULL, MKDEV(i_major ,0), NULL, DEV_NAME);    //在系统/dev/目录下动态创建节点
 
 
    Led_Dbg(KERN_ERR "%s driver[major=%d]  installed successfully!\n", DEV_NAME, i_major);
 
    return 0;
 
ERROR:
    Led_Dbg("%s driver installed failure.\n", DEV_NAME);
    cdev_del(LedDev);                                        //释放 cdev占用的内存
    unregister_chrdev_region(devno, 1);                        //解除注册
    return ret;
 
}
 
static void __exit Led_cdev_exit(void)                        //驱动卸载时执行的函数
{
    dev_t devno = MKDEV(i_major, i_minor);
    cdev_del(LedDev);
    device_destroy(LedDev_class, devno);             //删除设备节点
    class_destroy(LedDev_class);
 
    unregister_chrdev_region(devno, 1);
    
    platform_driver_unregister(&Led_driver);

    Led_Dbg(KERN_ERR "%s driver removed!\n", DEV_NAME);
    return ;
}

module_init(Led_cdev_init);   //指定驱动入口函数
module_exit(Led_cdev_exit);    //指定驱动卸载函数
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DEV_NAME);
MODULE_LICENSE("GPL");

2、增加HAL层驱动模块

    (1) hal 层的驱动主要有三个重要的结构体  

 

struct hw_module_t;              //模块类型  

struct hw_module_methods_t;      //模块方法  

struct hw_device_t;              //设备类型,向上提供接口

 如果我们要写一个自定义设备的驱动的HAL层时,我们得首先自定义两个数据结构:

/*定义模块ID*/  

#define XXX_HARDWARE_MODULE_ID "XXX"   

 

/*硬件模块结构体*/  

//见hardware.h中的hw_module_t定义的说明,xxx_module_t的第一个成员必须是hw_module_t类型,其次才是模块的一此相关信息,当然也可以不定义,  

//这里就没有定义模块相关信息   

struct xxx_module_t {  

    struct hw_module_t common;  

};  

  

/*硬件接口结构体*/  

//见hardware.h中的hw_device_t的说明,要求自定义xxx_device_t的第一个成员必须是hw_device_t类型,其次才是其它的一些接口信息.   

struct xxx_device_t {  

    struct hw_device_t common;  

        //以下成员是HAL对上层提供的接口或一些属性  

        int fd;  

    int (*set_val)(struct xxx_device_t* dev, int val);  

    int (*get_val)(struct xxx_device_t* dev, int* val);

 };  

(2)创建led.h文件

#ifndef LED_H
#define LED_H
 
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
 
#define LED_HARDWARE_MODULE_ID "led"
 
struct led_module_t {
    struct hw_module_t common;
};
 
struct led_control_device_t {
    struct hw_device_t common;
    int fd;
 
    int (*set_blue_on)(struct led_control_device_t *dev, int led);
    int (*set_red_on)(struct led_control_device_t *dev, int led);
 
};
#endif 

    (3)如下创建led文件夹 如下文件添加 led文件加 添加 led.c 和 Android.mk

Android.mk:

 LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE := led.default
 
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional
 
include $(BUILD_SHARED_LIBRARY)

 

led.c

/*************************************************************************
    > File Name: led.cpp
    > Author: Arctan
    > Mail: 2757904225@qq.com 
    > Created Time: Tue 06 Aug 2019 02:16:44 AM PDT
 ************************************************************************/
#define LOG_TAG "Led"
 
#include <hardware/hardware.h>
#include <hardware/led.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>

#define MAGIC_NUM 0X97
#define LED_IOCTL_RED    _IO(MAGIC_NUM,0X01)
#define LED_IOCTL_BLUE    _IO(MAGIC_NUM,0X02) 
#define LED_IOCTL_FLASH _IO(MAGIC_NUM,0X03)

#define DEVICE_NAME "/dev/led"


 int led_device_close(struct hw_device_t* device){
    
    struct led_control_device_t* pdev = (struct led_control_device_t*)device;
    if(pdev!=NULL){
        close(pdev->fd);
        free(pdev);
        pdev = NULL;
    }
    
    return 0;
}

 int led_blue_on(struct led_control_device_t *pled,int led){

    if(pled->fd<0){
        return -1;
    }
    ALOGI("Led set blue %d\n",led);
    ioctl(pled->fd,LED_IOCTL_BLUE,NULL);
    
    return 0;
}

 int led_red_on(struct led_control_device_t *pled,int led){
    
    if((pled==NULL)||(pled->fd<0)){
        return -1;
    }
    ALOGI("Led set red %d\n",led);
    ioctl(pled->fd,LED_IOCTL_RED,NULL);
    return 0;
}


static int led_device_open(const hw_module_t* module, const char* name,
                    hw_device_t** device){
    
    struct led_control_device_t *dev;
    dev = (struct led_control_device_t *)calloc(1,sizeof(struct led_control_device_t));
    if(!dev){
        ALOGE("led device open error");    
                return -ENOMEM;
    }
       
    
    ALOGI("Led enter led open ");
    dev->common.tag         = HARDWARE_DEVICE_TAG;
    dev->common.version  = 0;
    dev->common.module   = (struct hw_module_t*)module;
    dev->common.close    = led_device_close;

    dev->set_blue_on  = led_blue_on;
    dev->set_red_on = led_red_on;

    dev->fd = open(DEVICE_NAME,O_RDWR);
    if(dev->fd<0){
        ALOGI("Led open /dev/led fail ");
    }else{
        ALOGI("Led open /dev/led success");            
    }

    *device = &(dev->common);

    return 0;
}

static struct hw_module_methods_t led_module_methods = {

    .open = led_device_open,    
};

struct led_module_t HAL_MODULE_INFO_SYM ={
    .common = {
        .tag           = HARDWARE_MODULE_TAG,
        .version_major = 1,
        .version_minor = 0,
        .id               = LED_HARDWARE_MODULE_ID,
        .name          = "LED FLASH HW HAL",
        .author        = "Arctan project",
        .methods       = &led_module_methods,
    },
};

至此驱动层和hal层驱动模块已经完成,编译代码烧写层序看看有没有生成相应的 *.so 文件和 /dev/led节点。

3.JNI层,编写JNI方法,在应用程序框架层提供Java接口访问硬件

jni在注册方法时用到com/android/server/led/LedService
所以在frameworks/base/services/core/java/com/android/server/目录下新建led文件夹 

在Android系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。
因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要通过代理来进行
创建aidl文件来描述通信接口

frameworks/base/core/java/android/os/ILedService.aidl


package android.os;
 
/**
 * {@hide}
 */
interface ILedService {
    boolean setOn(); 
    boolean setOff();
}
 

 

frameworks/base/services/core/java/com/android/server/led/LedService.java

/*************************************************************************
    > File Name: com_android_server_led_LedService.cpp
    > Author: Arctan
    > Mail: 2757904225@qq.com 
    > Created Time: Tue 06 Aug 2019 07:52:44 PM PDT
 ************************************************************************/

#include <iostream>
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/led.h> 
#include <stdio.h>

namespace android{

    struct led_control_device_t* ledDevices = NULL;

    static inline int led_control_open(const struct hw_module_t *module, struct led_control_device_t** device){
                
        return module->methods->open(module, LED_HARDWARE_MODULE_ID, 
                                            (struct hw_device_t**)device);
    }

    static jboolean led_blue(JNIEnv *env,jobject clazz,jint led){
    
        ALOGI("jni led blue");
        if(ledDevices ==NULL){
            ALOGI("led bulue ledDevices == NULL");    
            return -1;
        }else{
        
            return ledDevices->set_blue_on(ledDevices,led);  //蓝灯
        }

    }

    static jboolean led_red(JNIEnv* env,jobject clazz,jint led){
    
        ALOGI("jni led blue");
        if(ledDevices ==NULL){
            ALOGI("led bulue ledDevices == NULL");    
            return -1;
        }else{
        
            return ledDevices->set_red_on(ledDevices,led);  //蓝灯
        }

    }

    static jboolean led_init(JNIEnv* env,jclass clazz){
    
            struct led_module_t* ledModule;
            ALOGI("--> enter LedService.cpp init %s",LED_HARDWARE_MODULE_ID);        
            if((hw_get_module(LED_HARDWARE_MODULE_ID,(const struct hw_module_t**)&ledModule))==0){
                ALOGI("get Led MOUDLE SUCCESS");    
                if(led_control_open(&ledModule->common,&ledDevices)==0){
                        ALOGI("led_control open success");
                        return 0;
                }else{
                    return -1;
                }
            
            }
            ALOGI("led init fail");
            return -1;
    }
    
    static const JNINativeMethod method_table[]={
        {"init_native","()Z",(void*)led_init},
        {"led_blue","(I)Z",(void*)led_blue},
        {"led_red","(I)Z",(void*)led_red},
        
    };

    int register_android_server_LedService(JNIEnv *env){            
        return jniRegisterNativeMethods(env, "com/android/server/led/LedService", method_table, NELEM(method_table));
    }

};

 

(1)、frameworks/base/services/core/jni/Android.mk

(2)、在 frameworks/base/Android.mk里增加对aidl的编译项

 

4、增加Manger 来管理相应的服务

frameworks/base/core/java/android/os/LedManager.java

//*************************************************************************
//    > File Name: LedManager.java
//    > Author: Arctan
//    > Mail: 2757904225@qq.com 
//    > Created Time: Tue 06 Aug 2019 11:16:45 PM PDT
// ************************************************************************/

package android.os;

import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.util.Log;
import android.os.ILedService;

public class LedManager{

    private static final String TAG="LedManager";
        
    private ILedService mILedService;
    private Context mContext;

    public LedManager(Context context,ILedService service){
        mContext     = context;
        mILedService = service;
    
    }
    public boolean LedBlue(){
    
        try{
            Log.i(TAG,"enter set on");
            return mILedService.setBlue();
        }catch(RemoteException e){
            Log.e(TAG,"set on fail ");
            return false;
        }
    }
    
    public boolean LedRed(){
        
        try{
            Log.i(TAG,"ENTER SET OFF");
            return mILedService.setRed();
        }catch(RemoteException e){
            Log.e(TAG,"set on fail ");
            return false;
        }
    }


}

5.添加注册服务,addService开机服务就可以启动

 frameworks/base/services/java/com/android/server/SystemServer.java

6.更新API

make update-api -j16

7.修改节点权限

Android在Selinux下要获取对内核节点访问的权限,需要修改.te文件

1)为/dev/led节点定义一个名字led_device

--- a/system/sepolicy/file_contexts
+++ b/system/sepolicy/file_contexts
@@ -8,6 +8,7 @@
 /dev/ttymxc[0-9]*               u:object_r:tty_device:s0
 /dev/ttyUSB[0-9]*               u:object_r:tty_device:s0 
 /dev/mma8x5x                    u:object_r:sensors_device:s0
+/dev/led                        u:object_r:led_device:s0


2)将led_device声明为dev_type

--- a/system/sepolicy/device.te
+++ b/system/sepolicy/device.te
@@ -1,2 +1,3 @@
 type caam_device, dev_type;
 type pxp_device, dev_type;
+type led_device,dev_type;

3)修改读写权限

--- a/system/sepolicy/system_server.te
+++ b/system/sepolicy/system_server.te
@@ -4,3 +4,4 @@ allow system_server system_data_file:file {relabelto rw_file_perms};
 allow system_server system_data_file:dir {relabelto rw_dir_perms};
 allow system_server kernel:system { syslog_read };
 allow system_server debugfs_tracing:file { write };
+allow system_server led_device:chr_file { open read write ioctl };

4)修改linux下节点自身的权限

--- a/device/rockchip/common/ueventd.rockchip.rc
+++ b/device/rockchip/common/ueventd.rockchip.rc
@@ -1,3 +1,4 @@
+/dev/led                  0660   system     system

5)增加服务的权限

--- a/system/sepolicy/service_contexts
+++ b/system/sepolicy/service_contexts
@@ -116,6 +116,7 @@ scheduling_policy                         u:object_r:scheduling_policy_service:s
 search                                    u:object_r:search_service:s0
 sensorservice                             u:object_r:sensorservice_service:s0
 serial                                    u:object_r:serial_service:s0
+led                                       u:object_r:led_service:s0

--- a/system/sepolicy/service.te
+++ b/system/sepolicy/service.te
@@ -96,6 +96,7 @@ type rttmanager_service, app_api_service, system_server_service, service_manager
 type samplingprofiler_service, system_server_service, service_manager_type;
 type scheduling_policy_service, system_server_service, service_manager_type;
 type search_service, app_api_service, system_server_service, service_manager_type;
+type led_service, app_api_service, system_server_service, service_manager_type;
 

8.修改 android 自带app 添加相应代码

 

也可以 将out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar里的
android/os/LedManager.class
导入eclipse 的sdk/platforms/android-xx/android.jar包中
的android/os目录 自行编写测试app

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值