file_operation

下面开始学习Linux字符设备驱动,也是linux驱动中最简单的驱动模块。

在内存中虚拟出一段空间作为字符设备,并为之编写些列的驱动程序。


字符设备驱动cdev中用到的两个重要的结构体如下,现补充下基本知识

一、cdev

  1. /*  
  2. *内核源码位置  
  3. *linux2.6.38/include/linux/cdev.h  
  4. */  
  5.   
  6. struct cdev {  
  7.     struct kobject kobj;  
  8.     struct module *owner;   //一般初始化为:THIS_MODULE  
  9.     const struct file_operations *ops;   //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定  
  10.     struct list_head list;  
  11.     dev_t dev;  //主设备号24位 与次设备号8位,dev_t为32位整形  
  12.     unsigned int count;  
  13. };  
/*
*内核源码位置
*linux2.6.38/include/linux/cdev.h
*/

struct cdev {
    struct kobject kobj;
    struct module *owner;   //一般初始化为:THIS_MODULE
    const struct file_operations *ops;   //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定
    struct list_head list;
    dev_t dev;  //主设备号24位 与次设备号8位,dev_t为32位整形
    unsigned int count;
};

二、file_operations

熟悉C语言文件编程的应该知道 read write等函数,这些函数都在file_operations中声明,在read等函数中实现与硬件相关的操作,这样就让具体的硬件设备与操作系统联系在了一起

  1. /* 
  2. ~/include/linux/fs.h 
  3. */  
  4.   
  5. struct file_operations {  
  6.     struct module *owner;  
  7.     loff_t (*llseek) (struct file *, loff_t, int);  
  8.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
  9.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  
  10.     ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  
  11.     ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  
  12.     int (*readdir) (struct file *, void *, filldir_t);  
  13.     unsigned int (*poll) (struct file *, struct poll_table_struct *);  
  14.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  
  15.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  
  16.     int (*mmap) (struct file *, struct vm_area_struct *);  
  17.     int (*open) (struct inode *, struct file *);  
  18.     int (*flush) (struct file *, fl_owner_t id);  
  19.     int (*release) (struct inode *, struct file *);  
  20.     int (*fsync) (struct file *, int datasync);  
  21.     int (*aio_fsync) (struct kiocb *, int datasync);  
  22.     int (*fasync) (intstruct file *, int);  
  23.     int (*lock) (struct file *, intstruct file_lock *);  
  24.     ssize_t (*sendpage) (struct file *, struct page *, intsize_t, loff_t *, int);  
  25.     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  
  26.     int (*check_flags)(int);  
  27.     int (*flock) (struct file *, intstruct file_lock *);  
  28.     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);  
  29.     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);  
  30.     int (*setlease)(struct file *, longstruct file_lock **);  
  31.     long (*fallocate)(struct file *file, int mode, loff_t offset,  
  32.               loff_t len);  
  33. };  
/*
~/include/linux/fs.h
*/

struct file_operations {
    struct module *owner;
    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 *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
};

光有这cdev与file_operations定义的结构体变量是不行的,显然要让他们做一些初始化工作,然后通过某个函数,让我们定义的这两个结构体变量与内核联系在一起,所以,调用内核的下列函数

文件名:char_dev.c

  1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)  
  2. {  
  3.     memset(cdev, 0, sizeof *cdev);  
  4.     INIT_LIST_HEAD(&cdev->list);  
  5.     kobject_init(&cdev->kobj, &ktype_cdev_default);  
  6.     cdev->ops = fops;  
  7. }  
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops;
}
为cdev开辟内存空间,然后,将file_operations定义的变量fops赋值给cdev中的ops成员变量,这样,他们就紧密的连在一起了

以上才仅仅将cdev初始化,还未将其真正的添加到系统内核中,因此调用下列函数:

文件名:char_dev.c

  1. int cdev_add(struct cdev *p, dev_t dev, unsigned count)  
  2. {  
  3.     p->dev = dev;  
  4.     p->count = count;  
  5.     return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);  
  6. }  
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    p->dev = dev;
    p->count = count;
    return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}


有了以上的字符设备基础时候后,在开始看一下字符设备的基本结构,其实就是在hello word的基础之上添加了设备读、写、控制的函数。

头文件:一般包含下面几个

  1. #include<linux/cdev.h>  
  2. #include<linux/module.h>  
  3. #include<linux/types.h>  
  4. #include<linux/fs.h>  
  5. #include<linux/errno.h>  
  6. #include<linux/mm.h>  
  7. #include<linux/sched.h>  
  8. #include<linux/init.h>  
  9. #include<asm/io.h>  
  10. #include<asm/system.h>  
  11. #include<asm/uaccess.h>  
#include<linux/cdev.h>




#include<linux/module.h> #include<linux/types.h> #include<linux/fs.h> #include<linux/errno.h> #include<linux/mm.h> #include<linux/sched.h> #include<linux/init.h> #include<asm/io.h> #include<asm/system.h> #include<asm/uaccess.h>
定义的cdev结构体与设备空间数据

  1. /*习惯上将内部数据空间与cdev 绑定,与其封装*/  
  2. struct mychar_dev{  
  3.     struct cdev cdev;  
  4.     unsigned char mem[MYCHAR_MEM_SIZE];  
  5. };  
  6.   
  7. /*一个实例*/  
  8. struct mychar_dev* mychar_devp;  
/*习惯上将内部数据空间与cdev 绑定,与其封装*/
struct mychar_dev{
    struct cdev cdev;
    unsigned char mem[MYCHAR_MEM_SIZE];
};

/*一个实例*/
struct mychar_dev* mychar_devp;

然后是读、写、ioctl函数的实现

  1. /*实现file_operations结构体体的open函数*/  
  2. int mychar_open(struct inode *inode,struct file * filp)  
/*实现file_operations结构体体的open函数*/
int mychar_open(struct inode *inode,struct file * filp)

int mychar_release(struct inode *inode,struct file* filp);

/*read*/
ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos );

/*write*/
ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos);

/*llseek*/
static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig);

/*ioctl*/
int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg);


以上函数实现后将里赋值到file_operations中相应的函数成员变量

  1. static const struct file_operations mychar_fops = {  
  2.     .owner = THIS_MODULE,  
  3.     .llseek = mychar_llseek,  
  4.     .read = mychar_read,  
  5.     .write = mychar_write,  
  6.     .ioctl = mychar_ioctl,  
  7.     .open = mychar_open,  
  8.     .release =mychar_release,  
  9. };  
static const struct file_operations mychar_fops = {
    .owner = THIS_MODULE,
    .llseek = mychar_llseek,
    .read = mychar_read,
    .write = mychar_write,
    .ioctl = mychar_ioctl,
    .open = mychar_open,
    .release =mychar_release,
};

最后是init 与exit函数

/*init*/
static int __init mychar_init(void);

/*exit*/
static void __exit mychar_exit(void);


最后是

  1. MODULE_LICENSE(“Dual BSD/GPL”);  
  2. MODULE_AUTHOR(”ghostyu”);  
  3.   
  4. module_param(mychar_major,int,S_IRUGO);  
  5.   
  6. module_init(mychar_init);  
  7. module_exit(mychar_exit);  
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("ghostyu");

module_param(mychar_major,int,S_IRUGO);

module_init(mychar_init);
module_exit(mychar_exit);


下面看一下完整的源码:

  1. /*在内存中申请1k 大小的内存做为简单的一个设备来访问*/  
  2. /*一般包含的头文件*/  
  3. #include<linux/cdev.h>  
  4. #include<linux/module.h>  
  5. #include<linux/types.h>  
  6. #include<linux/fs.h>  
  7. #include<linux/errno.h>  
  8. #include<linux/mm.h>  
  9. #include<linux/sched.h>  
  10. #include<linux/init.h>  
  11. #include<asm/io.h>  
  12. #include<asm/system.h>  
  13. #include<asm/uaccess.h>  
  14.   
  15. /*设备空间*/  
  16. #define MYCHAR_MEM_SIZE 0x0400  
  17. /*主设备号*/  
  18. #define MYCHAR_MAJOR    260  
  19. /*自定义的清除内存的命令*/  
  20. #define MYCHAR_MEN_CLR  0x01  
  21. /*主设备号变量*/  
  22. static int mychar_major = MYCHAR_MAJOR;  
  23.   
  24. /*习惯上将内部数据空间与cdev 绑定,与其封装*/  
  25. struct mychar_dev{  
  26.     struct cdev cdev;  
  27.     unsigned char mem[MYCHAR_MEM_SIZE];  
  28. };  
  29.   
  30. /*一个实例*/  
  31. struct mychar_dev* mychar_devp;  
  32.   
  33. /*实现file_operations结构体体的open函数*/  
  34. int mychar_open(struct inode *inode,struct file * filp)  
  35. {  
  36.     filp->private_data = mychar_devp;  
  37.     return 0;  
  38. }  
  39. /*同上*/  
  40. int mychar_release(struct inode *inode,struct file* filp)  
  41. {  
  42.     return 0;  
  43. }  
  44.   
  45. /*read*/  
  46. ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos )  
  47. {  
  48.     unsigned long p=*ppos;  
  49.     unsigned int count = size;  
  50.     int ret = 0;  
  51.     struct mychar_dev *dev = filp->private_data;  
  52.     if(p>MYCHAR_MEM_SIZE)  
  53.         return 0;  
  54.     if(count > MYCHAR_MEM_SIZE-p)  
  55.         count = MYCHAR_MEM_SIZE-p;  
  56.     if( copy_to_user(buf,(void*)(dev->mem+p),count)){  
  57.         ret= -EFAULT;  
  58.     }else{  
  59.         *ppos +=count;  
  60.         ret = count;  
  61.         printk(KERN_INFO ”read %u bytes(s) from %1u\n”,count,p);  
  62.     }  
  63.     return ret;  
  64. }  
  65. /*write*/  
  66. ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos)  
  67. {  
  68.     unsigned long p=*ppos;  
  69.     unsigned int count=size;  
  70.     int ret = 0;  
  71.     struct mychar_dev *dev = filp->private_data;  
  72.     if(p > MYCHAR_MEM_SIZE)  
  73.         return 0;  
  74.     if(count > MYCHAR_MEM_SIZE-p)  
  75.         count = MYCHAR_MEM_SIZE-p;  
  76.     if(copy_from_user((void*)(dev->mem),buf,count)){  
  77.         ret = -EFAULT;  
  78.     }else{  
  79.         *ppos +=count;  
  80.         ret = count;  
  81.         printk(KERN_INFO ”written %u byte(s) from %1u\n”,count,p);  
  82.     }  
  83.     return ret;  
  84. }  
  85. /*llseek*/  
  86. static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig)  
  87. {  
  88.     loff_t ret = 0;  
  89.     switch(orig){  
  90.     case 0: /*相对于文件开始位置偏移*/  
  91.         if(offset < 0)  
  92.             ret = -EINVAL;  
  93.         break;  
  94.         if((unsigned int)offset > MYCHAR_MEM_SIZE){  
  95.             ret = -EINVAL;  
  96.             break;  
  97.         }  
  98.         filp->f_pos =(unsigned int )offset;  
  99.         ret = filp->f_pos;  
  100.         break;  
  101.     case 1: /*相对于文件当前位置*/  
  102.         if((filp->f_pos+offset)>MYCHAR_MEM_SIZE){  
  103.             ret = -EINVAL;  
  104.             break;  
  105.         }  
  106.         if((filp->f_pos+offset)< 0){  
  107.             ret = -EINVAL;  
  108.             break;  
  109.         }  
  110.         filp->f_pos +=offset;  
  111.         ret = filp->f_pos;  
  112.         break;  
  113.     default:  
  114.         ret = - EINVAL;  
  115.         break;  
  116.     }  
  117.     return ret;  
  118. }  
  119. /*ioctl*/  
  120. int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg)  
  121. {  
  122.     struct mychar_dev *dev =filp->private_data;  
  123.     switch(cmd){  
  124.     case MYCHAR_MEM_CLR:  
  125.         memset(dev->mem,0,MYCHAR_MEM_SIZE);  
  126.         printk(KERN_INFO ”mychar memery is set to zero\n”);  
  127.         break;  
  128.     default:  
  129.         return -EINVAL;  
  130.     }  
  131.     return 0;  
  132. }  
  133. static const struct file_operations mychar_fops = {  
  134.     .owner = THIS_MODULE,  
  135.     .llseek = mychar_llseek,  
  136.     .read = mychar_read,  
  137.     .write = mychar_write,  
  138.     .ioctl = mychar_ioctl,  
  139.     .open = mychar_open,  
  140.     .release =mychar_release,  
  141. };  
  142.   
  143. /*cdev结构初始化*/  
  144. static void mychar_setup_cdev(struct mychar_dev *dev,int index)  
  145. {  
  146.     int err;  
  147.     int devno = MKDEV(mychar_major,index);  
  148.     cdev_init(&dev->cdev,&mychar_fops);  
  149.     dev->cdev.owner = THIS_MODULE;  
  150.     err = cdev_add(&dev->cdev,devno,1);  
  151.     if(err)  
  152.         printk(KERN_NOTICE ” Error %d adding mychar %d”,err,index);  
  153.   
  154. }  
  155.   
  156.   
  157. /*init*/  
  158. static int __init mychar_init(void)  
  159. {  
  160.     int result;  
  161.     dev_t devno = MKDEV(mychar_major,0);  
  162.     if(mychar_major)  
  163.         result = register_chrdev_region(devno,1,”mychar”);  
  164.     else{  
  165.         result = alloc_chrdev_region(&devno,0,1,”mychar”);  
  166.         mychar_major = MAJOR(devno);  
  167.     }  
  168.     if(result<0)  
  169.         return result;  
  170.     mychar_devp = kmalloc(sizeof(struct mychar_dev),GFP_KERNEL);  
  171.     if(!mychar_devp){  
  172.         result = -ENOMEM;  
  173.         goto fall_malloc;  
  174.   
  175.     }  
  176.     memset(mychar_devp,0,sizeof(struct mychar_dev));  
  177.     mychar_setup_cdev(mychar_devp,0);  
  178.     return 0;  
  179.   
  180. fall_malloc:  
  181.     unregister_chrdev_region(devno,1);  
  182.     return result;  
  183. }  
  184.   
  185. /*exit*/  
  186. static void __exit mychar_exit(void)  
  187. {  
  188.     cdev_del(&mychar_devp->cdev);  
  189.     kfree(mychar_devp);  
  190.     unregister_chrdev_region(MKDEV(mychar_major,0),1);  
  191. }  
  192.   
  193.   
  194. MODULE_LICENSE(”Dual BSD/GPL”);  
  195. MODULE_AUTHOR(”ghostyu”);  
  196.   
  197. module_param(mychar_major,int,S_IRUGO);  
  198.   
  199. module_init(mychar_init);  
  200. module_exit(mychar_exit);  
/*在内存中申请1k 大小的内存做为简单的一个设备来访问*/
/*一般包含的头文件*/




#include<linux/cdev.h> #include<linux/module.h> #include<linux/types.h> #include<linux/fs.h> #include<linux/errno.h> #include<linux/mm.h> #include<linux/sched.h> #include<linux/init.h> #include<asm/io.h> #include<asm/system.h> #include<asm/uaccess.h> /*设备空间*/ #define MYCHAR_MEM_SIZE 0x0400 /*主设备号*/ #define MYCHAR_MAJOR 260 /*自定义的清除内存的命令*/ #define MYCHAR_MEN_CLR 0x01 /*主设备号变量*/ static int mychar_major = MYCHAR_MAJOR; /*习惯上将内部数据空间与cdev 绑定,与其封装*/ struct mychar_dev{ struct cdev cdev; unsigned char mem[MYCHAR_MEM_SIZE]; }; /*一个实例*/ struct mychar_dev* mychar_devp; /*实现file_operations结构体体的open函数*/ int mychar_open(struct inode *inode,struct file * filp) { filp->private_data = mychar_devp; return 0; } /*同上*/ int mychar_release(struct inode *inode,struct file* filp) { return 0; } /*read*/ ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos ) { unsigned long p=*ppos; unsigned int count = size; int ret = 0; struct mychar_dev *dev = filp->private_data; if(p>MYCHAR_MEM_SIZE) return 0; if(count > MYCHAR_MEM_SIZE-p) count = MYCHAR_MEM_SIZE-p; if( copy_to_user(buf,(void*)(dev->mem+p),count)){ ret= -EFAULT; }else{ *ppos +=count; ret = count; printk(KERN_INFO "read %u bytes(s) from %1u\n",count,p); } return ret; } /*write*/ ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos) { unsigned long p=*ppos; unsigned int count=size; int ret = 0; struct mychar_dev *dev = filp->private_data; if(p > MYCHAR_MEM_SIZE) return 0; if(count > MYCHAR_MEM_SIZE-p) count = MYCHAR_MEM_SIZE-p; if(copy_from_user((void*)(dev->mem),buf,count)){ ret = -EFAULT; }else{ *ppos +=count; ret = count; printk(KERN_INFO "written %u byte(s) from %1u\n",count,p); } return ret; } /*llseek*/ static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig) { loff_t ret = 0; switch(orig){ case 0: /*相对于文件开始位置偏移*/ if(offset < 0) ret = -EINVAL; break; if((unsigned int)offset > MYCHAR_MEM_SIZE){ ret = -EINVAL; break; } filp->f_pos =(unsigned int )offset; ret = filp->f_pos; break; case 1: /*相对于文件当前位置*/ if((filp->f_pos+offset)>MYCHAR_MEM_SIZE){ ret = -EINVAL; break; } if((filp->f_pos+offset)< 0){ ret = -EINVAL; break; } filp->f_pos +=offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } /*ioctl*/ int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg) { struct mychar_dev *dev =filp->private_data; switch(cmd){ case MYCHAR_MEM_CLR: memset(dev->mem,0,MYCHAR_MEM_SIZE); printk(KERN_INFO "mychar memery is set to zero\n"); break; default: return -EINVAL; } return 0; } static const struct file_operations mychar_fops = { .owner = THIS_MODULE, .llseek = mychar_llseek, .read = mychar_read, .write = mychar_write, .ioctl = mychar_ioctl, .open = mychar_open, .release =mychar_release, }; /*cdev结构初始化*/ static void mychar_setup_cdev(struct mychar_dev *dev,int index) { int err; int devno = MKDEV(mychar_major,index); cdev_init(&dev->cdev,&mychar_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev,devno,1); if(err) printk(KERN_NOTICE " Error %d adding mychar %d",err,index); } /*init*/ static int __init mychar_init(void) { int result; dev_t devno = MKDEV(mychar_major,0); if(mychar_major) result = register_chrdev_region(devno,1,"mychar"); else{ result = alloc_chrdev_region(&devno,0,1,"mychar"); mychar_major = MAJOR(devno); } if(result<0) return result; mychar_devp = kmalloc(sizeof(struct mychar_dev),GFP_KERNEL); if(!mychar_devp){ result = -ENOMEM; goto fall_malloc; } memset(mychar_devp,0,sizeof(struct mychar_dev)); mychar_setup_cdev(mychar_devp,0); return 0; fall_malloc: unregister_chrdev_region(devno,1); return result; } /*exit*/ static void __exit mychar_exit(void) { cdev_del(&mychar_devp->cdev); kfree(mychar_devp); unregister_chrdev_region(MKDEV(mychar_major,0),1); } MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("ghostyu"); module_param(mychar_major,int,S_IRUGO); module_init(mychar_init); module_exit(mychar_exit);


 

我个人觉得,如果不太会使用vim的ctag功能,可以在windows平台下source insight中编写linux驱动。

只要事先先建立一个linux内核源码的工程,在在这个工程中添加自己的linux驱动程序源码。

这样就实现了一个IDE,函数、变量、宏定义不全功能,语法着色等等非常方便,加快开发速度。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值