Linux驱动mmap内存映射详解及例子实现

26 篇文章 1 订阅
订阅专栏
17 篇文章 2 订阅
订阅专栏

mmap在linux哪里?

什么是mmap?

上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址读写数据。

但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。

操作设备还有很多方法,如ioctl、ioremap

mmap的好处是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于直接操作设备了,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。

 

什么是内存映射?

既然mmap是实现内存映射的接口,那么内存映射是什么呢?看下图

每个进程都有独立的进程地址空间,通过页表和MMU,可将虚拟地址转换为物理地址,每个进程都有独立的页表数据,这可解释为什么两个不同进程相同的虚拟地址,却对应不同的物理地址。

 

什么是虚拟地址空间?

每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间,下图形象地表达了这点

驱动程序运行在内核空间,所以驱动程序是面向所有进程的。

用户空间切换到内核空间有两种方法:

(1)系统调用,即软中断

(2)硬件中断

 

虚拟地址空间里面是什么?

了解了什么是虚拟地址空间,那么虚拟地址空间里面装的是什么?看下图

虚拟空间装的大概是上面那些数据了,内存映射大概就是把设备地址映射到上图的红色段了,暂且称其为“内存映射段”,至于映射到哪个地址,是由操作系统分配的,操作系统会把进程空间划分为三个部分:

(1)未分配的,即进程还未使用的地址

(2)缓存的,缓存在ram中的页

(3)未缓存的,没有缓存在ram中

操作系统会在未分配的地址空间分配一段虚拟地址,用来和设备地址建立映射,至于怎么建立映射,后面再揭晓。

现在大概明白了“内存映射”是什么了,那么内核是怎么管理这些地址空间的呢?任何复杂的理论最终也是通过各种数据结构体现出来的,而这里这个数据结构就是进程描述符。从内核看,进程是分配系统资源(CPU、内存)的载体,为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,这就是进程描述符,内核用task_struct结构体来表示进程,并且维护一个该结构体链表来管理所有进程。该结构体包含一些进程状态、调度信息等上千个成员,我们这里主要关注进程描述符里面的内存描述符(struct mm_struct mm)

 

内存描述符

具体的结构,请参考下图

现在已经知道了内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,映射过程就是驱动程序要做的事了。

 

内存映射的实现

以字符设备驱动为例,一般对字符设备的操作都如下框图

而内存映射的主要任务就是实现内核空间中的mmap()函数,先来了解一下字符设备驱动程序的框架

以下是mmap_driver.c的源代码

[cpp]  view plain  copy

  1. //所有的模块代码都包含下面两个头文件  
    #include <linux/module.h>  
    #include <linux/init.h>  
      
    #include <linux/types.h> //定义dev_t类型  
    #include <linux/cdev.h> //定义struct cdev结构体及相关操作  
    #include <linux/slab.h> //定义kmalloc接口  
    #include <asm/io.h>//定义virt_to_phys接口  
    #include <linux/mm.h>//remap_pfn_range  
    #include <linux/fs.h>  
      
    #define MAJOR_NUM 990  
    #define MM_SIZE 4096  
      
    static char driver_name[] = "mmap_driver1";//驱动模块名字  
    static int dev_major = MAJOR_NUM;  
    static int dev_minor = 0;  
    char *buf = NULL;  
    struct cdev *cdev = NULL;  
      
    static int device_open(struct inode *inode, struct file *file)  
    {  
        printk(KERN_ALERT"device open\n");  
        buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备  
        return 0;  
    }  
      
    static int device_close(struct inode *indoe, struct file *file)  
    {  
        printk("device close\n");  
        if(buf)  
        {  
            kfree(buf);  
        }  
        return 0;  
    }  
      
    static int device_mmap(struct file *file, struct vm_area_struct *vma)  
    {  
        vma->vm_flags |= VM_IO;//表示对设备IO空间的映射  
        vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出  
        if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里  
                           vma->vm_start,//虚拟空间的起始地址  
                           virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位  
                           vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍  
                           vma->vm_page_prot))//保护属性,  
        {  
            return -EAGAIN;  
        }  
        return 0;  
    }  
      
    static struct file_operations device_fops =  
    {  
        .owner = THIS_MODULE,  
        .open  = device_open,  
        .release = device_close,  
        .mmap = device_mmap,  
    };  
      
    static int __init char_device_init( void )  
    {  
        int result;  
        dev_t dev;//高12位表示主设备号,低20位表示次设备号  
        printk(KERN_ALERT"module init2323\n");  
        printk("dev=%d", dev);  
        dev = MKDEV(dev_major, dev_minor);  
        cdev = cdev_alloc();//为字符设备cdev分配空间  
        printk(KERN_ALERT"module init\n");  
        if(dev_major)  
        {  
            result = register_chrdev_region(dev, 1, driver_name);//静态分配设备号  
            printk("result = %d\n", result);  
        }  
        else  
        {  
            result = alloc_chrdev_region(&dev, 0, 1, driver_name);//动态分配设备号  
            dev_major = MAJOR(dev);  
        }  
          
        if(result < 0)  
        {  
            printk(KERN_WARNING"Cant't get major %d\n", dev_major);  
            return result;  
        }  
          
          
        cdev_init(cdev, &device_fops);//初始化字符设备cdev  
        cdev->ops = &device_fops;  
        cdev->owner = THIS_MODULE;  
          
        result = cdev_add(cdev, dev, 1);//向内核注册字符设备  
        printk("dffd = %d\n", result);  
        return 0;  
    }  
      
    static void __exit char_device_exit( void )  
    {  
        printk(KERN_ALERT"module exit\n");  
        cdev_del(cdev);  
        unregister_chrdev_region(MKDEV(dev_major, dev_minor), 1);  
    }  
      
    module_init(char_device_init);//模块加载  
    module_exit(char_device_exit);//模块退出  
      
    MODULE_LICENSE("GPL");  
    MODULE_AUTHOR("ChenShengfa");  

     

 

下面是测试代码test_mmap.c

[cpp]  view plain  copy

  1.  
#include <stdio.h>  
#include <fcntl.h>  
#include <sys/mman.h>  
#include <stdlib.h>  
#include <string.h>  
  
int main( void )  
{  
    int fd;  
    char *buffer;  
    char *mapBuf;  
    fd = open("/dev/mmap_driver", O_RDWR);//打开设备文件,内核就能获取设备文件的索引节点,填充inode结构  
    if(fd<0)  
    {  
        printf("open device is error,fd = %d\n",fd);  
        return -1;  
    }  
    /*测试一:查看内存映射段*/  
    printf("before mmap\n");  
    sleep(15);//睡眠15秒,查看映射前的内存图cat /proc/pid/maps  
    buffer = (char *)malloc(1024);  
    memset(buffer, 0, 1024);  
    mapBuf = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//内存映射,会调用驱动的mmap函数  
    printf("after mmap\n");  
    sleep(15);//睡眠15秒,在命令行查看映射后的内存图,如果多出了映射段,说明映射成功  
      
    /*测试二:往映射段读写数据,看是否成功*/  
    strcpy(mapBuf, "Driver Test");//向映射段写数据  
    memset(buffer, 0, 1024);  
    strcpy(buffer, mapBuf);//从映射段读取数据  
    printf("buf = %s\n", buffer);//如果读取出来的数据和写入的数据一致,说明映射段的确成功了  
      
      
    munmap(mapBuf, 1024);//去除映射  
    free(buffer);  
    close(fd);//关闭文件,最终调用驱动的close  
    return 0;  
}  

 

下面是makefile文件

[plain]  view plain  copy

  1. ifneq ($(KERNELRELEASE),)  
      
    obj-m := mmap_driver.o  
      
    else  
    KDIR := /lib/modules/3.2.0-52-generic/build  
      
    all:  
        make -C $(KDIR) M=$(PWD) modules  
    clean:  
        rm -f *.ko *.o *.mod.o *.mod.c *~ *.symvers *.order  
      
    endif  

     

下面命令演示一下驱动程序的编译、安装、测试过程(注:其他用户在mknod之后还需要chmod改变权限)

# make    //编译驱动

# insmod mmap_driver.ko    //安装驱动

# mknod /dev/mmap_driver c 999 0    //创建设备文件

# gcc test_mmap.c -o test.o    //编译应用程序

# ./test.o    //运行应用程序来测试驱动程序


拓展:

关于这个过程,涉及一些术语

(1)设备文件:linux中对硬件虚拟成设备文件,对普通文件的各种操作均适用于设备文件

(2)索引节点:linux使用索引节点来记录文件信息(如文件长度、创建修改时间),它存储在磁盘中,读入内存后就是一个inode结构体,文件系统维护了一个索引节点的数组,每个元素都和文件或者目录一一对应。

(3)主设备号:如上面的999,表示设备的类型,比如该设备是lcd还是usb等

(4)次设备号:如上面的0,表示该类设备上的不同设备

(5)文件(普通文件或设备文件)的三个结构

        ①文件操作:struct file_operations

        ②文件对象:struct file

        ③文件索引节点:struct inode

 

关于驱动程序中内存映射的实现,先了解一下open和close的流程

(1)设备驱动open流程

①应用程序调用open("/dev/mmap_driver", O_RDWR);

②Open就会通过VFS找到该设备的索引节点(inode),mknod的时候会根据设备号把驱动程序的file_operations结构填充到索引节点中(关于mknod /dev/mmap_driver c 999 0,这条指令创建了设备文件,在安装驱动(insmod)的时候,会运行驱动程序的初始化程序(module_init),在初始化程序中,会注册它的主设备号到系统中(cdev_add),如果mknod时的主设备号999在系统中不存在,即和注册的主设备号不同,则上面的指令会执行失败,就创建不了设备文件)

③然后根据设备文件的索引节点中的file_operations中的open指针,就调用驱动的open方法了。

④生成一个文件对象files_struct结构,系统维护一个files_struct的链表,表示系统中所有打开的文件

⑤返回文件描述符fd,把fd加入到进程的文件描述符表中

 

(2)设备驱动close流程

应用程序调用close(fd),最终可调用驱动的close,为什么根据一个简单的int型fd就可以找到驱动的close函数?这就和上面说的三个结构(struct file_operations、struct file、struct inode)息息相关了,假如fd = 3

 

(3)设备驱动mmap流程

由open和close得知,同理,应用程序调用mmap最终也会调用到驱动程序中mmap方法

①应用程序test.mmap.c中mmap函数

void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:映射后虚拟地址的起始地址,通常为NULL,内核自动分配

length:映射区的大小

prot:页面访问权限(PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE)

flags:参考网络资料

fd:文件描述符

offset:文件映射开始偏移量

 

②驱动程序的mmap_driver.c中mmap函数

上面说了,mmap的主要工作是把设备地址映射到进程虚拟地址,也即是一个vm_area_struct的结构体,这里说的映射,是一个很悬的东西,那它在程序中的表现是什么呢?——页表,没错,就是页表,映射就是要建立页表。进程地址空间就可以通过页表(软件)和MMU(硬件)映射到设备地址上了

virt_to_phys(buf),buf是在open时申请的地址,这里使用virt_to_phys把buf转换成物理地址,是模拟了一个硬件设备,即把虚拟设备映射到虚拟地址,在实际中可以直接使用物理地址。

 

总结

①从以上看到,内核各个模块错综复杂、相互交叉

②单纯一个小小驱动模块,就涉及了进程管理(进程地址空间)、内存管理(页表与页帧映射)、虚拟文件系统(structfile、structinode)

③并不是所有设备驱动都可以使用mmap来映射,比如像串口和其他面向流的设备,并且必须按照页大小进行映射。

Linux下得mmap内存映射机制分析
10-20
Linuxmmap内存映射机制分析 Linux操作系统中,mmap内存映射机制是一种高效的进程间通信方式。通过mmap机制,多个进程可以共享同一个文件的内存区域,从而实现高效的数据交换。下面是mmap机制的详细分析: 共享...
Python3 mmap内存映射文件示例解析
01-20
1. mmap内存映射文件 建立一个文件的内存映射将使用操作系统虚拟内存来直接访问文件系统上的数据,而不是使用常规的I/O函数访问数据。内存映射通常可以提供I/O性能,因为使用内存映射是,不需要对每个访问都建立一个...
Linuxmmap驱动实现
个人学习记录
04-11 1041
Linuxmmap驱动实现
内存映射mmap技术详解
最新发布
大隐隐于野
06-03 1237
前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如图1中过程3所示。当进程发起读写操作时,会访问虚拟地址空间,通过查询页表,发现这段地址不在物理页上,因为只建立了地址映射,真正的数据还没有拷贝到内存,因此引发缺页异常。
Linux设备驱动内存映射
04-10 4779
1. 内存映射所谓的内存映射就是把物理内存映射到进程的地址空间之内,这些应用程序就可以直接使用输入输出的地址空间,从而提高读写的效率。Linux提供了mmap()函数,用来映射物理内存。在驱动程序中,应用程序以设备文件为对象,调用mmap()函数,内核进行内存映射的准备工作,生成vm_area_struct结构体,然后调用设备驱动程序中定义的mmap函数。2. 映射的种类把同一个物理地址映射为虚拟地址有两种方法,第一种是mmap()函数将物理地址映射到进程的虚拟地址空间中去,第二种方法为ioremap()函
linux驱动模块中实现mmap
我的博客
10-17 650
linux驱动模块中实现mmap
Linux内存管理
Likes的博客
06-25 153
http://www.kerneltravel.net/journal/v/mem.htm 目录 一、前言 进程与内存 1、进程如何使用内存? 2、进程如何组织这些区域? 三、进程内存空间 1、进程内存管理 2、进程内存的分配与回收 3、如何由虚变实! 四、系统物理内存管理 1、物理内存管理(页管理) 2、内核内存使用 a、Slab b、Kmalloc c、内核非连...
Linux Kernel Map分享
vertor11的博客
03-21 1588
一. 原网址 https://makelinux.github.io/kernel/map/ 二. 大图
MMZ内存管理
09-22 7676
看完秒懂:Linux DMA mapping机制分析
weixin_71478434的博客
08-27 4842
Linux内核中提供了两种dma mapping的接口:Consistent mapping和Stream mapping。通常在使用consistent dma mapping时,首先需要通过。
怎么通过media foundation将图像数据写入虚拟摄像头_海思MPP:海思的图像内存管理
weixin_39842237的博客
11-15 367
写在前面《海思MPP》系列文章发表之后,通过知乎平台认识了不少也在海思芯片上开发的同学,大家互相交流了很多技术细节,笔者发现在了解“怎么用海思MPP做xxx”之前,有一个必须知道的知识点:海思MPP的图像内存管理。 本文将分享以下两块内容:笔者对于海思MPP的MMZ和VB的认识怎么在MMZ和VB上读写图像I. MPP和VB的介绍关于MPP和VB的概念,笔者在《海思MPP》系列开篇中就介绍过,这里再...
linux下的内存映射函数mmap详解及示例代码.pdf
12-04
Linux下的内存映射函数mmap详解及示例代码.pdf 本文档将详细介绍Linux下的内存映射函数mmap,包括其概念、实现机制、示例代码等方面的内容。 mmap概念 mmapLinux操作系统中的一种内存映射机制,可以将文件映射...
linux下的内存映射函数mmap详解及示例代码.doc
06-05
Linux下的内存映射函数mmap详解及示例代码 内存映射(Memory Mapping)是一种将文件映射到进程地址空间的机制,允许进程直接访问文件,而不需要使用传统的read和write系统调用,这种机制可以提高文件访问的效率和...
mmap实验例子
weixin_43300893的博客
11-09 1326
mmap内存映射实现 实验内容:在内核下面申请4K大小的内存(kmalloc申请的物理连续内存),在应用程序中通过mmap把内核态下申请的内存块映射到用户空间,然后在应用程序中通过read,write操作读写内核空间数据,最后读取用户态映射的地址空间,看内容是否和内核数据空间一致。 我的编译工具: /opt/gcc-linaro-6.2.1-2016.11-x86_64_aarch64-linux-gun/bin/arrch64-linux-gun-gcc 内核实现: #include <linux/
理清海思系统“内存”相关名词
JCYAO_的博客
05-20 740
易失性存储 分成 DRAM 和 SRAM 不刷新就会丢失数据,关机就没 DRAM:即动态随机存取存储器,最为常见的系统内存。利用场效应管的栅极对其衬底间的分布电容来保存信息,以存储电荷的多少,即电容端电压的高低来表示“1”和“0。采用 DRAM的计算机必须配置动态刷新电路,防止信息丢失。DRAM一般用作计算机中的主存储器。 SRAM:特点是工作速度快,只要电源不撤除,写入SRAM的信息就不会消失,不需要刷新电路,同时在读出时不破坏原来存放的信息,一经写入可多次读出。SRAM一般用来作为计算机中的高.
linux驱动mmap地址映射
qq_40684669的博客
11-17 1457
mmap实现,页表介绍
Linux驱动开发——物理地址映射(①)
跑不了的你的博客
01-02 2871
文章目录Linux驱动开发之物理地址映射ioremap函数使用方式方式一方式二不使用GPIO库函数方式来修改之前操作LED灯代码示例总结 Linux驱动开发之物理地址映射 如果不采用GPIO库函数,那么我们如何能在底层驱动中访问外设对应的硬件寄存器呢?是像类似单片机编程一样直接对硬件寄存器访问么? 在Linux系统中,不管是在用户空间还是内核空间一律不允许直接访问硬件外设的基地址(包括寄存器...
Linux驱动开发基础__mmap
cumtchw
02-09 4089
Linux驱动开发基础__mmap
linux内存映射函数mmap
07-28
mmap函数是Linux系统中用于内存映射的函数之一。它的原型如下: ```c void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` mmap函数的作用是将一个文件或者设备映射到进程的虚拟地址空间。它可以将文件的内容或者设备的I/O空间直接映射到进程的内存中,使得进程可以通过内存访问这些数据,而无需进行繁琐的读写操作。 参数说明: - addr:映射的起始地址,一般设置为NULL,由系统自动分配。 - length:映射的长度,以字节为单位。 - prot:映射区域的保护方式,可以是以下几个值的组合: - PROT_EXEC:可执行。 - PROT_READ:可读。 - PROT_WRITE:可写。 - PROT_NONE:无权限。 - flags:映射区域的标志位,可以是以下几个值的组合: - MAP_SHARED:与其他进程共享该映射区域。 - MAP_PRIVATE:独占该映射区域,对其进行修改不会影响其他进程。 - MAP_ANONYMOUS:创建一个匿名映射区域,不与文件关联。 - fd:要映射的文件描述符。 - offset:映射文件的偏移量。 mmap函数返回映射区域的起始地址,如果映射失败,则返回MAP_FAILED。 使用mmap函数可以方便地进行文件的读写操作,也可以用于实现进程间的共享内存。需要注意的是,在使用完映射区域后,需要使用munmap函数进行解除映射,以释放资源。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • 避免跳出谷歌人机验证 reCAPTCHA界面的解决方法 211089
  • 零拷贝原理详解 60255
  • teraterm--一款超好用堪比MobaXterm的远程终端连接软件,能解决shell端tab键等自动补全命令功能的缺失问题 58743
  • 把编译好的ko文件加载模块时出错:Error: could not insert module hello_world.ko: Invalid module format 57640
  • 错误ValueError: Object arrays cannot be loaded when allow_pickle=False的解决 57285

分类专栏

  • Linux C开发 26篇
  • 计算机常识 11篇
  • VScode使用技巧 3篇
  • Linux内核驱动 17篇
  • SDN的P4学习 5篇
  • 计算机网络 1篇
  • dpdk 3篇
  • cavium芯片包处理 9篇
  • 读书笔记 3篇
  • Android开发 1篇
  • Autojs 1篇
  • MATLAB安装 3篇
  • UIO用户空间驱动 1篇
  • 算法难题
  • 机器学习 1篇
  • 损失函数 1篇

最新评论

  • 360安全浏览器总是锁屏解锁后自启动打开网页,烦~

    千里马踢伯乐: 流氓就是流氓,没什么好说的,但也是矮子里面拔高个儿了,用起来确实比其它浏览器好用。 Edge功能强大、搜索质量好,但整体上操作不方便。 一直使用的是360浏览器.这次因为文中提到的自动打开网页问题后,也决心更换为风格一致的QQ浏览器了,可有些常用的网页,QQ浏览器半天打不开呀,而且下载速度也只有360浏览器的一半。 没办法,才找攻略解决360浏览器的问题,就找到这篇文章了。 好的一点时,似乎360浏览器的所有问题,似乎都在实验室里可以关闭。

  • 360安全浏览器总是锁屏解锁后自启动打开网页,烦~

    ttb0421: 流氓就是流氓,直接卸载,一劳永逸

  • Linux驱动mmap内存映射详解及例子实现

    修诡道的马前卒: 请问spi要怎么改写呢,参数要怎么设置呢

  • 关于Linux库(动态/静态库)、库文件、库函数,头文件、链接方式以及相关操作的知识汇总

    unlimited blade: 科普的很好,很系统化,给楼主点赞

  • VScode通过remote ssh连接虚拟机 & 报错 过程试图写入的管道不存在(已解决)

    qq_45915612: 我调整了.ssh的用户组权限http://t.csdnimg.cn/wUnwy,修改ssh默认的环境变量http://t.csdnimg.cn/NxwxX,调整了config文件的用户名等信息http://t.csdnimg.cn/1z7X7,删除了know_hosts文件里的对应服务器信息http://t.csdnimg.cn/6Z7t2,连上了。

大家在看

  • x264 编码器 CAVLC 熵编码源码分析
  • Linux中的用户与用户组 739
  • Linux系统之玩转fortune命令 997
  • 2025秋招LLM大模型多模态面试题(五)- 位置编码 19
  • 17th智能车NEUQ智能视觉组ADRC控制器介绍与调参经验 325

最新文章

  • 柔性数组详解
  • 360安全浏览器总是锁屏解锁后自启动打开网页,烦~
  • 运用paramiko远程执行命令报错:TypeError: from_buffer() cannot return the address of the raw string withi....
2021年2篇
2020年19篇
2019年116篇

目录

目录

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

PHP网站源码南山优秀网站设计东莞关键词按天计费松岗优化大浪网页设计西乡百度网站优化观澜关键词排名包年推广木棉湾高端网站设计西乡网站推广系统坪山网站定制双龙网站开发布吉网站推广工具福田阿里店铺托管宝安SEO按天收费坑梓营销型网站建设南联seo优化福永模板制作双龙网站定制东莞SEO按效果付费荷坳如何制作网站龙岗网页制作观澜百度网站优化丹竹头百度关键词包年推广坂田网站搜索优化坑梓百度关键词包年推广爱联百姓网标王推广西乡网站定制布吉网站建设南联网站seo优化坪地网页制作大鹏网站开发歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

PHP网站源码 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化