Linux 字符设备驱动程序

linux字符设备驱动程序学习实验记录 这是自己平常学习中曾经困惑的问题。Linux系统下的字符型设备和块设备的区分? 这里指的区分不是说如何从定义上进行区分,从实用的角度来看。打个比方就是,如果你自己写了个设备的驱动,或者从网上下载了一个驱动。安装好驱动之后,你没有相应的设备节点,要用到这样一条命令。mknod /dev/yourdevice  type Major Slave,这里就牵扯到类型是字符型的还是块设备型的。如何从驱动 阅读详情

前段时间遇到一个功能需求,需要将内核空间的一些数据结构和接口导出给用户空间,当然这可以通过自定义文件系统、netlink等机制实现,但为了实现更一般的接口,决定通过字符设备来实现,编写字符设备驱动程序,向用户空间导出设备文件,这样用户空间程序可以使用简单的读写文件操作设备,由于Linux内核当前更新到5.x版本了,字符设备的实现较之前版本(很早了,我以前研究的2.6.x)还是有些区别,所以赶紧研究了5.x版本内核的驱动管理实现,并整理了当前内核版本的字符设备驱动编写流程。

驱动程序是操作系统很重要组成部分,往往驱动程序的稳定性和支持设备数量,是衡量操作系统性能的重要指标,在Linux中主要有 字符设备和块设备 。

字符设备:

        按照字符流的方式顺序访问的设备(串口、键盘等)

块设备:

        按照指定的数据块,随机访问数据的设备(硬盘、软盘、CD-ROM等)

使用 ls -l /dev 可以看到当前Linux中的设备列表

字符设备

 块设备

 我们通过系统调用来使用这些设备提供的功能,如 open、write、read等,系统内核最终通过调用对应设备的驱动程序来完成我们的功能调用。

  • Linux字符设备数据结构

字符设备结构体cdev

struct cdev {
	struct kobject kobj; //内核对象
	struct module *owner; //驱动程序由外部模块进入内核时,owner指驱动程序所属的内核模块实例
	const struct file_operations *ops; //包含设备功能函数的结构体
	struct list_head list; //设备链表元素,系统中的字符设备通过链表关联。
	dev_t dev; //设备号 主设备:从设备号
	unsigned int count; //设备被引用数量,当驱动程序卸载时,如果count!=0,内核会阻止卸载。
};

设备操作函数结构体

struct file_operations {
	loff_t (*llseek) (struct file *, loff_t, int); //设置设备文件的偏移量位置
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备文件
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备文件
...

设备号(内核使用32位无符号整数存储 主设备号和从设备号)

typedef __kernel_dev_t		dev_t;
typedef u32 __kernel_dev_t;

字符设备驱动开发涉及到的内核函数

//内核为设备分配主设备号(可以手动分配,但不建议那样做,容易发生设备号冲突)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

//创建设备类,实际上就是导出到/sys/class
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

//初始化字符设备结构
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

//添加字符设备到内核(实际就是将cdev添加到内核的设备链表)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

//创建设备节点(用户空间进程udev会根据设备节点,在/dev目录创建设备文件,也可以手工在用户空间通过mkmod命令创建设备文件)
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)

//移除设备节点
void device_destroy(struct class *class, dev_t devt)

//从内核删除字符设备
void cdev_del(struct cdev *p)

//从内核删除设备类
void class_destroy(struct class *cls)

//释放注册的主从设备号
void unregister_chrdev_region(dev_t from, unsigned count)
  • 向内核注册字符设备驱动流程
  1. 分配主从设备号
  2. 初始化字符设备
  3. 添加字符设备实例到内核
  4. 创建设备节点
  • 删除已注册的字符设备流程
  1. 删除创建的设备节点
  2. 删除字符设备实例
  3. 删除注册的设备类
  4. 删除主从设备号

一个基于当前最新5.14内核的字符设备驱动程序demo

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>

//设备名称
#define MYCDEV_NAME "mycdev_simple"
//设备类名称
#define MYCDEV_CLASS "mycdev_class"
//设备数量
#define CDEV_NUM 1

//设备类
static struct class *mycdev_class;
//所有设备的数组
static struct cdev cdevs[CDEV_NUM];
static struct device devices[CDEV_NUM];

//设备号
static dev_t dev_num;


static int strLength(const char *s);
static int mycdev_open(struct inode *inode, struct file *file);
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos);
static ssize_t mycdev_write(struct file *, const char __user *buff, size_t len, loff_t *pos);
static int mycdev_release(struct inode *inode, struct file *file);

static struct file_operations op_f={
	.read=mycdev_read,
	.write=mycdev_write,
	.open=mycdev_open,
	.release=mycdev_release
};



//模块初始化函数
static int __init simple_chardev_init(void)
{
	int i,retVal;
    /*设备号*/
    dev_t curr_devnum; 

    /*请求内核分配设备号*/
    retVal=alloc_chrdev_region(&dev_num,0,CDEV_NUM,MYCDEV_CLASS);
    if(retVal!=0)
    {
    	printk("alloc_chrdev_region error %d\n",retVal);
    	return retVal;
    }

    //为字符设备创建设备类,在/sys/class中可见
    mycdev_class=class_create(THIS_MODULE,MYCDEV_CLASS);

    /*创建字符设备结构并注册到内核*/
    for(i=0;i<CDEV_NUM;i++)
    {
    	/*初始化字符设备*/
    	cdev_init(&cdevs[i],&op_f);
    	cdevs[i].owner=THIS_MODULE;

    	/*设置设备号*/
    	curr_devnum=MKDEV(MAJOR(dev_num),MINOR(dev_num)+i);
    	/*添加设备到内核*/
    	retVal=cdev_add(&cdevs[i],curr_devnum,1);
        printk("cdev_add retVal %d\n",retVal);

    	/*创建设备节点*/
    	devices[i]=*(device_create(mycdev_class,NULL,curr_devnum,NULL,MYCDEV_NAME "%d",i));
        printk("device_create %p\n",&devices[i]);
    }
	return retVal;
}

static void __exit simple_chardev_exit(void)
{
    dev_t _devnum;
    int i;

    for(i=0;i<CDEV_NUM;i++)
    {
        _devnum=MKDEV(MAJOR(dev_num),MINOR(dev_num)+i);
        device_destroy(mycdev_class, _devnum);
        cdev_del(&(cdevs[i]));
    }
    class_destroy(mycdev_class);

    unregister_chrdev_region(dev_num,CDEV_NUM);
    printk("simple_chardev_exit\n");
    return;
}


/*打开设备*/
static int mycdev_open(struct inode *inode, struct file *file)
{
	/*打印一条消息到内核日志*/
	printk("call mycdev_open\n");
	return 0;
}

/*读操作*/
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos)
{
    int count,retVal;
    const char *s1="from mycdev_simple!";
	printk("call mycdev_read\n");
	count=strLength(s1);
	retVal=copy_to_user(buff,s1,count+1);
    if(retVal==-EFAULT)
    {
        printk("mycdev_read error\n");
        return -EFAULT;
    }
	return count+1;
}

/*写操作*/
static ssize_t mycdev_write(struct file *file, const char __user *buff, size_t len, loff_t *pos)
{
	char *buffer;
    int retVal;
	buffer=NULL;

    printk("call mycdev_write\n");
    buffer = (char*)kmalloc(len, GFP_KERNEL);
    if(buffer==NULL)
    {
    	printk("kmalloc error\n");
    	return -EFAULT;
    }

    retVal=copy_from_user(buffer, buff, len);
    if(retVal==-EFAULT)
    {
        printk("mycdev_write error\n");
        return -EFAULT;
    }
    printk("recv %s\n",buffer);
    return len;
}

/*关闭文件*/
static int mycdev_release(struct inode *inode, struct file *file)
{
	printk("call mycdev_release\n");
	return 0;
}

/*计算字符串长度*/
static int strLength(const char *s)
{
	int len;
	len=0;
	while((*s)!=0)
	{
		s++;
		len++;
	}
	return len;
}


module_init(simple_chardev_init);
module_exit(simple_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxx");
MODULE_DESCRIPTION("内核描述");

关于linux中字符设备驱动程序的设计 作为初学者自己总结了一下一个简单的linux字符设备驱动程序中需要学习的内容 一.字符设备驱动程序的组成: 1.头文件:头文件部分包含进数据结构及内核函数需要用到的头文件。 2.file_operations结构体变量:声明了驱动程序的所有接口函数。 3.接口函数:包含字符设备驱动程序与内核及应用程序交互的read()函数,write()函数,open()函数,ioctl()函数, 阅读详情

相关推荐

Linux 字符设备驱动程序 异常处理

掌握异常处理,让bug的藏身地显露出来。

huilin9966的博客 1419

Linux设备驱动程序(三)——字符驱动

本章的目的是编写一个完整的字符设备驱动,我们开发一个字符驱动是因为这一类适合大部分简单硬件设备,字符驱动也比块驱动易于理解。贯穿本章,我们展示从一个真实设备驱动提取的代码片段:scull区域装载的简单字符工具。scull 是一个字符驱动,操作一块内存区域好像它是一个设备,在本章,因为 scull 的这个特殊之处,我们可互换地使用“设备”这个词和"scull 使用的内存区"。scull 的优势在于它不依赖硬件,scull 只是操作一些从内核分配的内存。

明天你好的博客 2911

Linux 字符设备驱动程序实现

这里, major 是感兴趣的主编号, name 是驱动的名子(出现在 /proc/devices), fops 是缺省的 file_operations 结构. 一个对 register_chrdev 的调用为给定的主编号注册 0 - 255 的次编号, 并且为每一个建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.它的使用在后面讨论.这个机制在将来内核中可能会消失.

m0_74282605的博客 572

Linux设备驱动开发---字符设备驱动程序

字符设备驱动程序1 主设备和次设备的概念设备号的注册和释放静态方法动态方法区别2 设备文件操作struct file_operations与struct file、struct inode关系3 分配和注册字符设备class_createcdev_adddevice_create4 字符设备驱动程序 字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过/dev目录下的特殊文件公开设备的属性和功能,通过这个文件可以在设备和用户应用程序之间交换数据,也可以通过它来控

qq_41683305的博客 4369

Ubuntu24.04下Librenms的完整安装与配置指南

本文提供了在Ubuntu 24.04 LTS系统上完整安装与配置LibreNMS网络监控系统的详细指南。内容涵盖系统环境准备、依赖安装、源码获取、PHP/数据库/Nginx配置、Composer安装、网页向导设置以及设备添加与日常使用,帮助用户快速部署一套功能强大的开源网络监控解决方案。

tgb3456789的博客 1138

【学习笔记】《基于φ-OTDR的分布式扰动传感系统定位算法研究-北交-通信与信息系统-吴》重点笔记

一、绪论 1.1 引言 光纤传感器的优点:跟电子传感器相比较,光纤传感器有诸多独特的优势:对电磁干扰免疫;具有很好的绝缘性和耐腐蚀性;灵敏度高,可测量微弱信息;可实现大范围监测和分布式传感,仅需一根传感光纤,更为经济便捷;传感光纤体积小、重量轻、可形变,适用于一些特殊应用场所;还有无需供电、耐高温、便于组网等等优点 光纤传感器的感知信号的方法:当探测光入射到光纤中时会发生散射并产生各种散射光...

qq_36045093的博客 2万+

Linux字符设备驱动

具体就是:内核虽然可以访问用户空间的缓冲区,但在访问之前会检查其合法性,通过。file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会被应用程序调用Linux系统调用如open()/read()/write()/close()等时,最终被内核调用。调用register_chrdev()不能指定次设备号,那么函数内部会在内核中连续注册256个次设备号,也就是所有的次设备号都会被占用,而在大多数情况下都不会用到这么多次设备号,所以会造成极大的资源浪费。

ljl113的博客 1205

避开RK3588开发板SDIO配置的坑:O9201SB WiFi模组接入实战指南

本文为嵌入式开发者提供了在RK3588开发板上,利用TF卡槽接入O9201SB WiFi6模组的实战指南。文章深度剖析了SDIO配置中常见的电源管理序列冲突问题,并给出了从设备树节点修改、内核编译到系统验证的完整解决方案,帮助开发者避开配置陷阱,成功实现高性能无线连接。

sprite的博客 954

linux 设备驱动(一)——字符设备驱动

linux 设备驱动一、概述1.1 设备驱动介绍二、字符设备驱动2.1. 字符设备驱动简介2.2. LCD驱动 一、概述 1.1 设备驱动介绍 二、字符设备驱动 2.1. 字符设备驱动简介 2.2. LCD驱动 感谢阅读,祝君成功! -by aiziyou ...

qq_27094347的博客 5347

深入解析YT8521S千兆以太网PHY芯片的设计与调试要点

本文深入解析了YT8521S千兆以太网PHY芯片的设计与调试要点。重点剖析了硬件设计中的电源方案、RGMII接口电平配置(尤其是1.8V电平方案)以及PCB布局布线等关键细节,并提供了从硬件检查、驱动配置到性能测试的完整调试流程,帮助工程师规避常见陷阱,实现稳定可靠的千兆网络连接。

weixin_29190573的博客 687

计算机网络-自顶向下方法 第三章课后习题答案(第七版)

复习题 R1. a) 就叫这个协议为简单传输协议STP(Simple Transport Protocol)。在发送方,STP从发送进程接收不超过1196字节的数据块、目标主机地址和目标端口号。STP向每个块添加一个4字节的头,并将目标进程的端口号放在头中。STP然后将目标主机地址和结果段提供给网络层。网络层将段交付给目的地主机的STP。目的主机基于STP检查段中的端口号,从段中提取数据,并将数据传递给由端口号标识的进程。 b) 在发送方,STP从发送进程接收不超过1192字节的数据块、目标主机地址和目标端

Erekilu的博客 2万+

linux设备驱动程序:简单字符设备驱动

分为3类:字符设备、块设备、网络设备。使用驱动程序:1、:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。2、:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

m0_74282605的博客 1025

STM32 bootloader程序跳转到APP卡死在B_ENDP_ALIGN,原因:有未处理的中断。解决方法:重启系统

Bootloader和App中都用到了串口,定时器等,所以从Bootloader跳转App后未卡死。 后来写的另一个App里没有用到这几个外设,然后就是无限的卡死。 现在Bootloader中跳转App之前将已打开的外设关掉,再跳转至App就不再出现卡死。  if(((*(vu32*)(APPLICATION_ADDRESS+4))&0xFF000000)==0x08000000) {

jingzhixingyun的博客 7920

linux字符设备驱动程序

linux系统中万物皆文件,驱动程序加载后会在/dev目录下生成一个对应的文件,如/dev/led。应用程序就是先用open打开该文件,用write控制led的亮灭,用read读取led的亮灭,用完之后用close关闭该文件。这里需要注意的是,应用程序运行在用户空间,驱动程序运行在内核空间。应用程序必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。一个open函数执行的过程如下。

m0_71262188的博客 1338

【华为OD机试真题 2025C卷】238、贪吃蛇 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 2025C卷】238、贪吃蛇 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2025e卷,od机试,2025E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2025华为od,如何备战华为od,

正在更新最新华为OD机试2025B卷,提供OJ在线刷题 196

【SMPL简介】SMPL: A Skinned Multi-Person Linear Model【源码复现】

SMPL是一种3D人体建模方法.在数字人或者人物角色三维重建领域有着广泛应用支持人体的各种形状及动作可以简单理解为通过训练获取的人物模型SMPL 输入是:姿态shape vectorβ\betaβ+ 影响动作Pose的参数θ\thetaθ输出是: 各种变化后vertex顶点的数据作者:Loper, Matthew and Mahmood, Naureen and Romero, Javier and Pons-Moll, Gerard and Black, Michael J.

zik的博客 3369

掌握Linux字符设备驱动程序的核心要点

掌握Linux字符设备驱动程序的核心要点

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 1202

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Linux系统下的字符设备驱动程序编程

实验项目名称: 设备驱动: Linux系统下的字符设备驱动程序编程 一、实验目的 通过一个简单的设备驱动的实现过程。学会Linux中设备驱动程序的编写。 深入理解内核驱动模块编写和编译过程。 二、实验内容 1、编写一个字符设备驱动程序,并在设备的打开操作中打印主次设备号; 2、编写一个用户测试程序,实现设备的读操作。 三、实验涉及的系统调用函数以及内核函数 1、分配设备号函数register_chrdev( ),用于指定设备号的情况。函数原型为: int register_chrdev(unsigned i

weixin_44636558的博客 4742

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

linux 字符设备驱动程序原理

1. 理解驱动程序 由于unix的万物皆文件的思想,底层的设备也用特殊文件类型,设备文件来操作,通过读写设备文件就可以读写设备内存或者设备端口了,因此需要一个在虚拟文件系统和底层设备之间的一个适配器,这就是设备驱动程序,用于粘合虚拟文件系统和底层的设备,提供上层的虚拟文件系统统一的接口,并且管理和底层设备的交互。 字符设备可以通过一种比较简单的方式管理,因为不需要I/O调度、底层同步、重排请求

lonewolfxw的专栏 1302

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

linux简单的字符设备驱动程序

要求:掌握添加设备驱动程序的方法 内容: 采用模块方法,添加一个新的设备驱动程序。 要求添加字符设备的驱动。 编写一个应用程序,测试添加的驱动程序。 <!-- p {margin-bottom:0.21cm} --> linxu系统中,在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的

朱胜本的专栏 1万+

Linux字符设备驱动程序编写基本流程

Linux字符设备驱动程序编写基本流程Linux device driver 的概念   系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能: 1、对设备初始化和释放;

zhenwenxian的专栏 9337

Linux下字符设备驱动程序的结构

首先,希望可以明确一下概念: 1、 要说的是,驱动,必然是与内核紧密相连的底层程序;并且接口对用户是不可见的。所以你要想到的是如何把驱动和内核联系起来,第一个就想到了模块(modules),模块就像一个载体,一个容器,通过它,会把你写好的程序插入(装载)到内核可见的区域,从而使内核感知到你的驱动的存在,然后用户空间才能通过系统调用的形式联系到驱动,从而完成它的任务,所以你首要了解模块。 2、

dreambegin 的 优快云 2345

Linux字符设备驱动程序开发之笔记及摘要

                                                                                                                       东方绿洲  2009-12-06/21:33:36一、Linux字符设备驱动程序        Linux支持3类硬件设备:字符设备、块设备及网络接口。

xiaoxiaopig的专栏 1288

Linux字符设备驱动程序的设计

2007-12-25 09:23:28 摘 要  介绍了Linux字符设备驱动程序中建立设备 ,初始化设备、设备的资源分配和如何访问设备的方法及相关函数的实现.关键词  Linux 字符设备  设备驱动程序 设备驱动程序实质上是一组完成不同任务的函数的集合 ,通过这些函数所提供的功能可以使得从设备接受输入和将输出送到设备就象读写文件一样 ,因此 ,Linu

mybirdsky的专栏 604

Linux字符设备驱动程序的编写框架

Linux字符设备驱动程序的编写框架 一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能: 1、对设备初始化和

catamout的专栏 753

linux 简单的字符设备驱动,[操作系统]简单linux字符设备驱动程序

[操作系统]简单linux字符设备驱动程序0 2013-11-23 22:00:03本文代码参考《LINUX设备驱动程序》第三章 字符设备驱动程序本文中的“字符设备”是一段大小为PAGE_SIZE的内存空间功能:向字符设备写入字符串;从字符设备读出字符串代码:1. scull.c1 #include 2 #include 3 #include 4 #include 5 #in...

weixin_36131079的博客 257
上一篇: Linux内核模块原理
下一篇: Linux内核中断(X86)
IT回忆录
博客等级 码龄16年 42粉丝 100原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值