IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> windows的malloc到virtualAlloc的调用 -> 正文阅读

[系统运维]windows的malloc到virtualAlloc的调用

windows的malloc到virtualAlloc调用

? 先介绍下基本知识

Win32 内存结构

首先要搞清楚【虚拟内存、交换文件、页面文件、RAM、物理存储、虚拟地址空间】这几个概念!

虚拟内存是操作系统的内存管理方式,对上层应用屏蔽了页面文件与RAM的区别与调度方式。先看看操作系统,再来说吧!

Windows操作系统中的内存结构

? 在没有充分的理解函数集合是如何工作的以及它们各自是如何影响操作系统的情况下,在你的应用程序中确定用于管理内存的函数或函数集合是很困难的。下面重点介绍虚拟内存(Virtual Memory)以及如何使用它们,它们的是如何与操作系统交互的。

? 在这里插入图片描述

? 仔细看上图,CRT只提供了操Heap Memory API上的内存操纵方式(malloc/free、new/delete等)。

? Win32提供下列3中操纵内存的机制:

? 虚拟内存(Virtual Memory API,VirtualAlloc、VirtualFree等),适合于管理大的对象数据或结构

? 内存映射文件(Memory Mapped File API,CreateFileMapping、MapViewOfFile等),最适合于管理大的数据流(通常来自文件)和在多个进程间共享数据

? 堆(Heap Memory API,HeapCreate、HeapAlloc等;Local、Global Memory API在Windows NT上已经没有了,不分局部堆与全局堆,只有一种类型的堆),最适合于管理大量的小对象

虚拟内存提供操作功能

  • 保留(reserve)、提交(commit)和释放(free)虚拟内存
  • 修改虚拟内存页(Page)的保护属性
  • 虚拟内存页加锁(Lock)
  • 查询应用程序的虚拟内存

注意:虚拟内存指的是操作系统对上层应用提供的内存抽象,是内存管理的一种方式,与Windows系统属性中虚拟内存不是一回事。Windows系统属性中的虚拟内存是指分页文件。

在地址空间中保留区域(申请虚拟内存,不被其他申请操作给占用)
LPVOID
VirtualAlloc(
    _In_opt_ LPVOID lpAddress,	// 按64-KB向上取整
    _In_     SIZE_T dwSize,
    _In_     DWORD flAllocationType,
    _In_     DWORD flProtect
    )

虚拟内存(VirtualAlloc),堆(HeapAlloc/malloc/new)和Memory Mapped File

虚地址空间

? 在Win32中,每个进程的虚地址空间是4GB。32位指针的值能从0x00000000到0xFFFFFFFF。这使得指针能有4,294,967,296种值,这覆盖了进程的4GB空间。

	32位CPU,32位操作系统,32位应用程序的定义是什么?	

? 所有的Win32进程都有自己的私人地址空间。当进程种的线程在运行时,线程只能访问属于本进程的内存。对运行的线程来说,属于其他所有进程的内存是隐藏的和不可访问的(如果使用系统提供的api,还是可以对其他进程的内存进行读写的)。

? 进程拥有的4GB空间是虚地址空间,而不是物理内存。该地址空间只不过是一段内存地址。物理内存需要被分配或映射到地址空间的分区。如何将物理内存映射到虚地址空间的分区?

Windows NT如何划分进程的地址空间

? 在这里插入图片描述

地址空间中的区域

? 当进程被创建并分配了地址空间后,大部分可用的地址空间是空闲的(或未分配的)

? Win32调用VirtualAlloc函数来分配区域,分配一块区域的行为被称为保留(reserving)

? 页是系统用来管理内存的单位,Win32实现使用4KB的页大小

? 地址空间的分配以页为单位,例如:请求分配5KB大小的地址空间,系统保留的区域为8KB

? Win32调用VirtualFree释放(release)区域

?

在区域内提交物理存储

? 要使用虚地址空间中的一块保留区域,就必须分配物理存储,然后将其映射到保留区域,这一过程叫做提交物理存储。物理存储总是以页为单位提交的。要把物理存储提交给保留区域,还要调用VirtualAlloc函数。

? 通过调用VirtualAlloc来向地址空间提交物理存储时,空间实际上是分配在虚地址空间的一块连续内存,在物理存储上不一定连续。

物理存储

? 物理存储被认为是计算机上的RAM的多少。在Win32中,Microsoft提供了对硬盘交换文件形式的虚拟内存的支持。但只有在CPU直接支持交换文件的情况下操作系统才能使用它们。从应用程序的角度来说,交换文件透明地增加了应用程序所能使用地RAM(或存储)地数量。如果计算机上有2GB的RAM,在硬盘上还有2GB的交换文件的话,运行的应用程序会认为计算机共有4GB的RAM。

并不是实际上有4GB的RAM。相反,操作系统同CPU一起合作,把RAM的部分内容保存在交换文件中,而在运行程序需要的时候又把交换文件中的内容装回到RAM中。如果不适用交换文件,系统只是认为应用程序能使用的RAM较少一些。

物理存储 = RAM + 页面文件/虚拟内存 + 不包含在页面文件中的物理存储

? 当进程中的线程试图访问进程的地址空间中的一块数据时,必然会发生两件事情中的一件,如图:

在这里插入图片描述

? 第1种可能,线程试图访问的数据在RAM中。这时,CPU把数据的虚内存地址映射到内存中的物理地址,然后进行直接访问。
? 第2种可能,线程试图访问的数据不在RAM而在页面文件(虚拟内存)中。这时,访问会产生一个页面错误,CPU通知操作系统试图进行的访问。操作系统就从RAM找一页空闲内存。如果找不到,系统就必须释放一页。如果页面还没有被修改(脏),系统就能直接释放它。否则,系统首先要把该页从RAM拷贝到页面文件中。接下来,系统在页面文件中找到所要访问的数据库,把它加载到内存的空闲页中。操作系统然后把数据的虚地址映射到RAM中适当的物理地址。
? 当操作系统需要把内存和页面中的数据来回交换的时候,硬盘就会越多地咔咔作响,系统也会越慢。因为操作系统把时间都花在把页面换入和换出,而不是运行程序上了。其实这也是触发缺页异常,然后置换需要访问的数据内存。

? 在Win32中操作内存的第3种方法是使用堆。堆非常适合于分配很多小块的数据。例如,使用堆来管理链表和树要比使用虚拟内存或内存映射文件好很多。

堆的特性:
  1. 堆是属于进程的,一个进程的堆种的内容不能被其他进程种的线程访问

  2. 在通常的程序中,很多数据默认存放在缺省堆,例如:new、malloc等申请的内存空间

  3. 堆是虚拟内存上的一种特殊内存管理方式

  4. dll没有自己的堆,它使用的堆是进程地址空间的一部分

  5. 什么是Win32的堆?

    ? 一个Win32堆是一块保留地址空间。起初,该保留区域中的大部分页都没有用物理存储提交。当从堆中分配空间时,堆管理器会向堆提交更多的物理存储。当堆中空间被释放时,堆管理器会从堆中释放物理存储。物理存储是按页提交给堆的。

1.1 进程的缺省堆

? 当一个Win32进程被初始化时,系统在它的地址空间中创建了一个堆。该堆被称为进程的缺省堆。缺省时,该堆的区域是1MB。不贵,系统能增大进程的缺省堆。在编译程序时,可以使用/HEAP链接器开关来改变缺省的堆大小。DLL没有自己的堆,不应使用/HEAP开关。

? 进程的缺省堆被很多Win32函数使用。例如:new、malloc等默认都是从缺省堆上分配空间。
缺省堆的访问被序列化了,系统确保在任一时刻只可能有一个线程在缺省堆中分配或释放内存块。

1.2 创建自己的Win32堆

? 除了进程的缺省堆外,还能再进程的地址空间中创建更多的堆。一般来说,想要在应用程序中创建其他的堆主要有3个原因:

  1. 部件保护

  2. 更有效的内存管理

  3. 局部访问

  4. 部件保护

    ? 在缺省堆中的内存混在一起,可能由于其中一个内存块访问异常导致其他内存块错误。可创建独立的堆,使问题局部化。

  5. 更有效的内存管理

    ? 如果堆中包含的对象都是同样大小,在释放一个对象后,就能保证另一个对象能合适地得到这个空闲对象的空间。

  6. 局部访问

    ? 在设计应用程序时,最好把要同时访问的对象放在一起。若对象的内存分布在多个内存页面中,可能会因为缺页,需要系统在RAM和页面文件之间交换页面,导致性能降低。而将同时访问的对象放在一起将降低这种可能性。

以上就把内存相关的概念介绍的差不多了,主要参考了

https://blog.csdn.net/zj510/article/details/39400087

https://blog.csdn.net/lwwl12/article/details/88859044

https://blog.csdn.net/lwwl12/article/details/89926414相关文章,如果有兴趣可以详读或者了解相关内存的系统api

malloc如何申请内存

1.申请内存的开始

001B5308 8B F4                mov         esi,esp  
001B530A 8B 45 F4             mov         eax,dword ptr [ebp-0Ch]  
    92: 
    93: 		char* data = (char*)malloc(count);
001B530D 50                   push        eax  
001B530E FF 15 80 01 1C 00    call        dword ptr ds:[1C0180h]  //调用malloc函数
001B5314 83 C4 04             add         esp,4  
001B5317 3B F4                cmp         esi,esp  
001B5319 E8 CB BF FF FF       call        __RTC_CheckEsp (01B12E9h)  //检查缓冲区溢出
001B531E 89 45 E8             mov         dword ptr [ebp-18h],eax  

2.malloc的实现

位于C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\dbgmalloc.c目录

extern "C" _CRTIMP void * __cdecl malloc (
        size_t nSize
        )
{
        void *res = _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);

        RTCCALLBACK(_RTC_Allocate_hook, (res, nSize, 0));

        return res;
}

汇编

    50: *******************************************************************************/
    51: 
    52: extern "C" _CRTIMP void * __cdecl malloc (
    53:         size_t nSize
    54:         )
    55: {
0FEEE590 55                   push        ebp  
0FEEE591 8B EC                mov         ebp,esp  
0FEEE593 51                   push        ecx  
    56:         void *res = _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
0FEEE594 6A 00                push        0  
0FEEE596 6A 00                push        0  
0FEEE598 6A 01                push        1  
0FEEE59A A1 8C C1 F5 0F       mov         eax,dword ptr ds:[0FF5C18Ch]  
0FEEE59F 50                   push        eax  
0FEEE5A0 8B 4D 08             mov         ecx,dword ptr [nSize]  
0FEEE5A3 51                   push        ecx  
0FEEE5A4 E8 A7 F5 FF FF       call        _nh_malloc_dbg (0FEEDB50h)  

3._nh_malloc_dbg的实现

函数实现在C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\dbgheap.c

/***
*void * _nh_malloc_dbg() - Get a block of memory from the debug heap
*
*Purpose:
*       Allocate of block of memory of at least size bytes from the debug
*       heap and return a pointer to it. Assumes heap already locked.
*
*       If no blocks available, call new handler.
*
*       Allocates any type of supported memory block.
*
*Entry:
*       size_t          nSize       - size of block requested
*       int             nhFlag      - TRUE if new handler function
*       int             nBlockUse   - block type
*       char *          szFileName  - file name
*       int             nLine       - line number
*
*Exit:
*       Success:  Pointer to (user portion of) memory block
*       Failure:  NULL
*
*Exceptions:
*
*******************************************************************************/
extern "C" void * __cdecl _nh_malloc_dbg (
        size_t nSize,
        int nhFlag,
        int nBlockUse,
        const char * szFileName,
        int nLine
        )
{
        int errno_tmp = 0;
        void * pvBlk = _nh_malloc_dbg_impl(nSize, nhFlag, nBlockUse, szFileName, nLine, &errno_tmp);

        if ( pvBlk == NULL && errno_tmp != 0 && _errno())
        {
            errno = errno_tmp; // recall, #define errno *_errno()
        }
        return pvBlk;
}

汇编如下

   293: extern "C" void * __cdecl _nh_malloc_dbg (
   294:         size_t nSize,
   295:         int nhFlag,
   296:         int nBlockUse,
   297:         const char * szFileName,
   298:         int nLine
   299:         )
   300: {
0FEEDB50 55                   push        ebp  
0FEEDB51 8B EC                mov         ebp,esp  
0FEEDB53 83 EC 08             sub         esp,8  
   301:         int errno_tmp = 0;
0FEEDB56 C7 45 FC 00 00 00 00 mov         dword ptr [errno_tmp],0  
   302:         void * pvBlk = _nh_malloc_dbg_impl(nSize, nhFlag, nBlockUse, szFileName, nLine, &errno_tmp);
0FEEDB5D 8D 45 FC             lea         eax,[errno_tmp]  
0FEEDB60 50                   push        eax  
0FEEDB61 8B 4D 18             mov         ecx,dword ptr [nLine]  
0FEEDB64 51                   push        ecx  
0FEEDB65 8B 55 14             mov         edx,dword ptr [szFileName]  
0FEEDB68 52                   push        edx  
0FEEDB69 8B 45 10             mov         eax,dword ptr [nBlockUse]  
0FEEDB6C 50                   push        eax  
0FEEDB6D 8B 4D 0C             mov         ecx,dword ptr [nhFlag]  
0FEEDB70 51                   push        ecx  
0FEEDB71 8B 55 08             mov         edx,dword ptr [nSize]  
0FEEDB74 52                   push        edx  
   302:         void * pvBlk = _nh_malloc_dbg_impl(nSize, nhFlag, nBlockUse, szFileName, nLine, &errno_tmp);
0FEEDB75 E8 36 00 00 00       call        _nh_malloc_dbg_impl (0FEEDBB0h)  

4._nh_malloc_dbg_impl的实现

函数实现在C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\dbgheap.c

extern "C" static void * __cdecl _nh_malloc_dbg_impl (
        size_t nSize,
        int nhFlag,
        int nBlockUse,
        const char * szFileName,
        int nLine,
        int * errno_tmp
        )
{
        void * pvBlk;

        for (;;)
        {
            /* do the allocation
             */
            pvBlk = _heap_alloc_dbg_impl(nSize, nBlockUse, szFileName, nLine, errno_tmp);

            if (pvBlk)
            {
                return pvBlk;
            }
            if (nhFlag == 0)
            {
                if (errno_tmp)
                {
                    *errno_tmp = ENOMEM;
                }
                return pvBlk;
            }

            /* call installed new handler */
            if (!_callnewh(nSize))
            {
                if (errno_tmp)
                {
                    *errno_tmp = ENOMEM;
                }
                return NULL;
            }

            /* new handler was successful -- try to allocate again */
        }
}

汇编

   224: extern "C" static void * __cdecl _nh_malloc_dbg_impl (
   225:         size_t nSize,
   226:         int nhFlag,
   227:         int nBlockUse,
   228:         const char * szFileName,
   229:         int nLine,
   230:         int * errno_tmp
   231:         )
   232: {
0FEEDBB0 55                   push        ebp  
0FEEDBB1 8B EC                mov         ebp,esp  
0FEEDBB3 51                   push        ecx  
   233:         void * pvBlk;
   234: 
   235:         for (;;)
   236:         {
   237:             /* do the allocation
   238:              */
   239:             pvBlk = _heap_alloc_dbg_impl(nSize, nBlockUse, szFileName, nLine, errno_tmp);
0FEEDBB4 8B 45 1C             mov         eax,dword ptr [errno_tmp]  
0FEEDBB7 50                   push        eax  
0FEEDBB8 8B 4D 18             mov         ecx,dword ptr [nLine]  
0FEEDBBB 51                   push        ecx  
0FEEDBBC 8B 55 14             mov         edx,dword ptr [szFileName]  
0FEEDBBF 52                   push        edx  
0FEEDBC0 8B 45 10             mov         eax,dword ptr [nBlockUse]  
0FEEDBC3 50                   push        eax  
0FEEDBC4 8B 4D 08             mov         ecx,dword ptr [nSize]  
0FEEDBC7 51                   push        ecx  
0FEEDBC8 E8 63 F9 FF FF       call        _heap_alloc_dbg_impl (0FEED530h)  

5._heap_alloc_dbg_impl的实现

函数实现在C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\dbgheap.c

extern "C" static void * __cdecl _heap_alloc_dbg_impl(
        size_t nSize,
        int nBlockUse,
        const char * szFileName,
        int nLine,
        int * errno_tmp
        )
{
        long lRequest;
        size_t blockSize;
        int fIgnore = FALSE;
        _CrtMemBlockHeader * pHead;
        void *retval=NULL;

        /* lock the heap
         */
        _mlock(_HEAP_LOCK);
        __try {

            /* verify heap before allocation */
            if (check_frequency > 0)
                if (check_counter == (check_frequency - 1))
                {
                    _ASSERTE(_CrtCheckMemory());
                    check_counter = 0;
                }
                else
                    check_counter++;

            lRequest = _lRequestCurr;

            /* break into debugger at specific memory allocation */
            if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)
                _CrtDbgBreak();

            /* forced failure */
            if ((_pfnAllocHook) && !(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize, nBlockUse, lRequest, (const unsigned char *)szFileName, nLine))
            {
                if (szFileName)
                    _RPT2(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n",
                        szFileName, nLine);
                else
                    _RPT0(_CRT_WARN, "Client hook allocation failure.\n");
            }
            else
            {
                /* cannot ignore CRT allocations */
                if (_BLOCK_TYPE(nBlockUse) != _CRT_BLOCK &&
                    !(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF))
                    fIgnore = TRUE;

                /* Diagnostic memory allocation from this point on */

                if (nSize > (size_t)(_HEAP_MAXREQ - nNoMansLandSize - sizeof(_CrtMemBlockHeader)))
                {
                    _RPT1(_CRT_ERROR, "Invalid allocation size: %Iu bytes.\n", nSize);
                    if (errno_tmp)
                    {
                        *errno_tmp = ENOMEM;
                    }
                }
                else
                {
                    if (!_BLOCK_TYPE_IS_VALID(nBlockUse))
                    {
                        _RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
                    }

                    blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;

                    RTCCALLBACK(_RTC_FuncCheckSet_hook,(0));
                    pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);

                    if (pHead == NULL)
                    {
                        if (errno_tmp)
                        {
                            *errno_tmp = ENOMEM;
                        }
                        RTCCALLBACK(_RTC_FuncCheckSet_hook,(1));
                    }
                    else
                    {

                        /* commit allocation */
                        ++_lRequestCurr;

                        if (fIgnore)
                        {
                            pHead->pBlockHeaderNext = NULL;
                            pHead->pBlockHeaderPrev = NULL;
                            pHead->szFileName = NULL;
                            pHead->nLine = IGNORE_LINE;
                            pHead->nDataSize = nSize;
                            pHead->nBlockUse = _IGNORE_BLOCK;
                            pHead->lRequest = IGNORE_REQ;
                        }
                        else {
                            /* keep track of total amount of memory allocated */
                            if (SIZE_MAX - _lTotalAlloc > nSize)
                            {
                                _lTotalAlloc += nSize;
                            }
                            else
                            {
                                _lTotalAlloc = SIZE_MAX;
                            }
                            _lCurAlloc += nSize;

                            if (_lCurAlloc > _lMaxAlloc)
                            _lMaxAlloc = _lCurAlloc;

                            if (_pFirstBlock)
                                _pFirstBlock->pBlockHeaderPrev = pHead;
                            else
                                _pLastBlock = pHead;

                            pHead->pBlockHeaderNext = _pFirstBlock;
                            pHead->pBlockHeaderPrev = NULL;
                            pHead->szFileName = (char *)szFileName;
                            pHead->nLine = nLine;
                            pHead->nDataSize = nSize;
                            pHead->nBlockUse = nBlockUse;
                            pHead->lRequest = lRequest;

                            /* link blocks together */
                            _pFirstBlock = pHead;
                        }

                        /* fill in gap before and after real block */
                        memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
                        memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);

                        /* fill data with silly value (but non-zero) */
                        memset((void *)pbData(pHead), _bCleanLandFill, nSize);

                        RTCCALLBACK(_RTC_FuncCheckSet_hook,(1));

                        retval=(void *)pbData(pHead);
                    }
                }
            }

        }
        __finally {
            /* unlock the heap
             */
            _munlock(_HEAP_LOCK);
        }

        return retval;
}

6._heap_alloc_base的实现

#define _heap_alloc _heap_alloc_base

__forceinline void * __cdecl _heap_alloc (size_t size)

{

    if (_crtheap == 0) {
#if !defined (_CRT_APP) || defined (_DEBUG)
        _FF_MSGBANNER();    /* write run-time error banner */
        _NMSG_WRITE(_RT_CRT_NOTINIT);  /* write message */
#endif  /* !defined (_CRT_APP) || defined (_DEBUG) */
        __crtExitProcess(255);  /* normally _exit(255) */
    }

    return HeapAlloc(_crtheap, 0, size ? size : 1);
}



7.HeapAlloc

待补充

new 到 malloc

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                        _THROW_NCEE(_XSTD bad_alloc, );
                }

        return (p);
        }

可以看出,new其实底层就是调用malloc,然后会再调用构造函数。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-13 12:43:23  更:2021-08-13 12:47:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 19:30:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码