Glibc內存管理-ptmalloc2


一、linux的內存布局

 1、32位模式下內存的經典布局​

 

 

圖1 32位模式下內存經典布局

​         注:這種內存布局模式是linux內核2.6.7以前的默認內存布局形式

  說明:

    (1)在32的機器上,loader將可執行文件的各個段次依次載入到從0x80048000(128M)位置開始的空間中。程序能夠訪問的最后地址是0xbfffffff(3G)的位置,3G以上的位置是給內核使用的,應用程序不能直接訪問。

             (2)內存布局從低地址到高地址依次為:txet段、data段、bss段、heap、mmap映射區、stack堆棧區。

             (3)​heap和mmap是相對增長的,也就意味着heap只有1G的虛擬地址空間可供使用。​

             (4)​stack區域不需要映射的,用戶可以直接訪問該區域。這也是利用堆棧溢出進行攻擊的基礎。

2、32位模式下內存的默認布局​

 

 

圖2 32位模式下內存的默認布局

       注:這種內存布局是linux內核2.6.7之后32位機器的默認內存布局方式。

   說明:(1)這種內存布局方式加入了幾個Random offset的隨機偏移,這樣的話內存溢出的攻擊就不會那么容易了。

           (2)​棧是自頂向下擴展的,但是棧是有邊界的棧大小就有了限制(linux小t乃下可以使用ulimit  -s 命令進行查看其大小)。堆是自底向上擴展的,mmap映射區自頂向下擴展。故mmap和heap是相對擴展的,直至消耗盡虛擬地址空間中的剩余區域,這種結構便於C運行庫使用mmap映射區和堆進行內存分配。

3、​64位模式下內存的默認布局

 

 

圖3 64位模式下內存的默認布局

     說明:這種內存布局方式沿用的32位模式下內存的經典布局,但是棧和mmap的映射區域不再是從一個固定的地方開始,每次啟動時的值都不一樣。這樣一來,使得使用緩沖區溢出攻擊變得更加困難。

 

二、操作系統內存分配的相關函數

1、總括​

         heap和mmap映射區域是可以提供給用戶程序使用的虛擬內存空間,獲得該區域的內存的操作有:​

      (1)對於heap操作,操作系統提供了brk()系統調用,c運行庫提供了sbrk()庫函數。

 ​      (2)對於mmap映射區的操作,操作系統提供了mmap()和munm()系統調用。

      (3)linux內存管理的基本思想:內存延遲分配。即只有在真正訪問一個地址的時候才建立這個地址的物理映射。linux內核在用戶申請內存的時候,只是給它分配了一個虛擬地址,並沒有分配實際的物理地址,只有當用戶使用這塊內存的時候,內核才會分配具體的物理地址給用戶使用。

 2、heap操作的相關函數​

         (1)brk()是系統調用、sbrk()是庫函數。c語言的動態內存分配基本函數是malloc(),在​linux上的實現是:malloc()函數調用庫函數sbrk(),sbrk()的實質是調用brk()函數。brk()是一個簡單的系統調用,只是簡單的改變mm_struct結構體的成員變量brkd的值。

         (2)函數原型:

             #include

​​​             int brk(void * addr);

​            void * sbrk(intptr_t increment);  //當參數increment為0時,sbrk()返回的是進程當前的brk值,increment為正數時擴展brk值,當increment為負數時收縮brk值

         (3)mm_struct結構中的成員變量

                   start_code 和 end_code 是進程代碼段的起始和結束地址、

                    start_data 和 end_data 是進程數據段的起始和終止地址。

                    start_stack  是進程堆棧段的起始地址。

                    start_brk  是進程動態內存分配的起始地址(堆的起始地址)。

                   ​brk 是進程動態內存分配當前的終止地址(堆的當前最后地址)。

​3、Mmap映射區域操作的相關函數

​            (1)mmap()函數將一個文件或者其他對象映射進內存。文件被映射到多個頁上,如果文件大小不是所有頁大小之和,最后一個頁不被使用的空間將會清零。munmap執行相反的操作,刪除特定地址區域的對象映射。

              (2)函數原型

              #include​

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

           ​    int munmap(void *addr,size_t length);

                參數:start — 映射區的開始地址。

                          length — 映射區的長度。

                          prot — 期望的內存保護標志。

                           ​flags — 指定映射對象的類型,映射選項和映射頁是否可以共享。

                           fd — 有效的文件描述符。

                          offset — 被映射對象內容的起點。​

 

三、Ptmalloc 簡介

1、ptmalloc 的背景介紹​

          早期的ptmalloc出現在Doug Lea中,它有一個重要的問題是在處理​多線程並發請求內存時需要保證分配和回收的正確和高效。因此,改進后的Glibc的malloc可以支持多線程—ptmalloc。

2、​ 分配器簡介

          分配器處於內核和用戶程序之間。它的工作機制是這樣的:它響應用戶的分配請求,向操作系統申請內存,​然后將內存返回給用戶程序。為了保證高效,分配器一般都會預先分配一塊大於用戶請求的內存,然后管理這塊內存。用戶釋放掉的內存也不是立即返回給操作系統的,分配器會管理這些釋放掉的空閑空間以應對用戶以后的內存分配請求。

          分配器不僅需要管理分配的內存塊,還需要管理空間的內存塊。當響應用戶的請求​時,分配器會首先在空閑空間中尋找一塊合適的內存返回給用戶,在空閑空間中找不到的清苦下才會重新分配一塊新的內存。

3、ptmalloc的設計假設​

(1)​​具有長生命周期的大內存分配使用mmap。

(2)特別大的內存分配總是使用mmap.

(3)具有短生命周期的內存分配使用sbrk。

(4)盡量只緩存臨時使用的空閑小內存塊,當大內存塊或者是生命周期較長的大內存塊在釋放時都直接歸還給操作系統。

(5)需要長期存儲的程序不合適用ptmalloc來管理。

(6)對空閑的小內存塊只會在malloc和free的時候進行合並。free時空閑內存塊可能放入內存池中,而不是立即歸還給操作系統。

(7)為了支持多線程,多個線程可以同一個分配區中分配內存,ptmalloc假設線程A釋放掉一塊內存后,線程B申請類似大小的內存,但是A釋放的內存跟B需要的內存不一定完全相等。​

四、ptmalloc內存管理數據結構​

1、Main_arena與non_main_arena(主分配區和非主分配區)​

​     (1)為什么會出現主分配區和非主分配區?

  • Doug_Lea實現的內存分配器中只有一個主分配區,每次分配內存都必須對主分配區​加鎖,分配完成后再釋放鎖。在多線程的環境下,對主分配區的鎖的競爭很激烈,嚴重影響了malloc的分配效率。改進后的Glibc的malloc可以支持多線程,增加了非主分配區支持,主分配區和非主分配區形成一個環形鏈表進行管理。每一個分配區使用互斥鎖使多線程對於該分配區的訪問互斥。
  • 主分配區和非主分配區形成一個環形鏈表進行管理。
  • 每一個分配區利用互斥鎖使線程對於該分配區的訪問互斥。
  • 每個進程只有一個主分配區,也可以允許有多個非主分配區。
  • ptmalloc根據系統對分配區的爭用動態增加分配區的大小,分配區的數量一旦增加,則不會減少。
  • 主分配區可以使用brk和mmap來分配,而非主分配區只能使用mmap來映射內存塊
  • 申請小內存時會產生很多內存碎片,ptmalloc在整理時也需要對分配區做加鎖操作

    (2)主分配區和非主分配的區別

       <1>每個進程只有一個主分配區,可以有多個非主分配區,ptmalloc根據系統對分配區的爭用情況動態的增加非主分配區的個數,分配區的數量一旦增加了就不會再減少。

       <2>​主分配區可以訪問進程的heap和mmap映射區域,也就是說主分配區可以使用sbrk()和mmap()向操作系統申請虛擬內存。非主分配區只能訪問進程的mmap映射區域,非主分配區每次使用mmap()向操作系統批發HEAP_MAX_SIZE(32位-1M,64位-64M)大小的虛擬內存,當用戶向非主分配區請求分配內存時再切成小塊零售出去。系統調用的效率比較低,直接從用戶空間分配內存效率會比較高。所以ptmalloc在必要的情況下才會調用mmap()向操作系統申請內存。

        <3>主分配區可以訪問heap區域,如果用戶不調用sbrk()或者brk()函數,分配程序就可以保證分配到連續的虛擬內存,因為一個進程只有一個主分配區使用sbrk()分配heap區域的虛擬內存。如果主分配區的內存時通過mmap()向系統申請的,當free該內存時,主分配區會直接調用munmap()將內存歸還給操作系統。

        <4>​malloc()函數的實質

             當線程需要使用malloc函數分配內存空間時,該線程會先查看線程的私有變量中是否已經存在有一個分配區,如果存在,嘗試對分配區加鎖,如果加鎖成功就使用這個分配區分配內存。如果失敗,該線程就搜索循環鏈表試圖獲得一個沒有加鎖的分配區來分配內存。如果所有的分配區都加鎖,那么malloc會開辟一個新的分配區,把該分配區添加到循環鏈表中並加鎖,使用這個分配區進行分配內存操作。在釋放操作中,線程同樣試圖獲得待釋放內存塊所在的分配區的鎖,如果該分配區正在被別的線程使用,則需要等待直到其他線程釋放該分配區的互斥鎖之后才可以進行釋放操作。​

2、Malloc實現原理

因為brk、sbrk、mmap都屬於系統調用,若每次申請內存,都調用這三個,那么每次都會產生系統調用,影響性能;其次,這樣申請的內存容易產生碎片,因為堆是從低地址到高地址,如果高地址的內存沒有被釋放,低地址的內存就不能被回收。
  
  所以malloc采用的是內存池的管理方式(ptmalloc),Ptmalloc 采用邊界標記法將內存划分成很多塊,從而對內存的分配與回收進行管理。為了內存分配函數malloc的高效性,ptmalloc會預先向操作系統申請一塊內存供用戶使用,當我們申請和釋放內存的時候,ptmalloc會將這些內存管理起來,並通過一些策略來判斷是否將其回收給操作系統。這樣做的最大好處就是,使用戶申請和釋放內存的時候更加高效,避免產生過多的內存碎片。

3、chunk的結構

1.struct malloc_chunk {  
2.  INTERNAL_SIZE_T      prev_size;    /* Size of previous chunk (if free).  */  
3.  INTERNAL_SIZE_T      size;         /* Size in bytes, including overhead. */  
4.  
5.  struct malloc_chunk* fd;           /* double links -- used only if free. */  
6.  struct malloc_chunk* bk;  
7.  
8.  /* Only used for large blocks: pointer to next larger size.  */  
9.  struct malloc_chunk* fd_nextsize;      /* double links -- used only if free. */  
10.  struct malloc_chunk* bk_nextsize; 
11.}; 

      (1)chunk的使用:用戶請求分配的空間在ptmalloc中都使用一個chunk來表示。用戶調用free函數釋放掉的內存也不是立即就返回操作系統,它們會被表示成一個chunk。ptmalloc中使用特定的數據結構管理這些空閑的chunk。

       (2)​chunk的格式

             ptmalloc在給用戶分配的空間的前后加上一些控制信息,用這樣的方式來記錄分配的信息。一個正在使用中的chunk如下圖所示​:

 

 

圖1  使用中的chunk

         說明:

        <1>chunk指針指向chunk開始的地址;mem指針指向用戶內存塊開始的地址。

        <2> p=0時,表示前一個chunk為空閑,prev_size才有效​。

        <3> p=1時,表示前一個chunk正在使用,prev_size無效。 p主要用於內存塊的合並操作。

        <4> ptmalloc分配的第一個塊總是將p設為1,以防止程序引用到不存在的區域。​

        <5> M=1 為mmap映射區域分配;M=0為heap區域分配。

        <6> A=1 為非主分區分配;A=0 為主分區分配。

​       空閑chunk在內存中的結構:

 

 

圖2 空閑的chunk

​說明:

​      <1> 空閑的chunk會被放置到空閑的鏈表bins上。當用戶申請內存malloc的時候,會先去查找空閑鏈表bins上是否有合適的內存。

       <2> fp和bp分別指向前一個和后一個空閑鏈表上的chunk

       <3>fp_nextsize和bp_nextsize分別指向前一個空閑chunk和后一個空閑chunk的大小,主要用於在空閑鏈表上快速查找合適大小的chunk。

       <4>fp、bp、fp_nextsize、bp_nextsize的值都會存在原本的用戶區域,這樣就不需要專門為每個chunk准備單獨的內存存儲指針了。

4、chunk的組織-BINS

                用戶釋放掉的內存並不是馬上就歸還給操作系統,ptmalloc會統一管理heap和mmap映射區中的空閑的chunk,當用戶進行下一次請求分配時,ptmalloc會試圖從空閑的內存中挑選一塊給用戶,這樣可以避免頻繁的系統調用,降低了內存​分配的開銷。ptmalloc將大小相似的chunk用雙向循環鏈表連接起來,這樣的一個鏈表稱為bin。ptmalloc中一共維護了128個bin,並使用一個數組來存儲這些bin(數組實際存儲的是指針)。

 

 

圖3 bins結構圖
 
 

​(1)fast bins:fast bins是small bins的高速緩沖區。fast bin使用單向鏈表實現。當程序運行需要申請和釋放一些較小的內存空間。一般對於不大於max_fast(在SIZE_SZ為4B

的平台默認是64B)的chunk釋放后就會先被放到fast bins中,fast bins中有七個chunk空閑鏈表(bin),每個bin的chunk大小依次為16B、24B、32B、40B.....64B。fast bins中的chunk並不改變它的使用標志P,這樣就無法合並他們。當需要給用戶分配小於或者是等於max_fast大小的內存時,ptmalloc會首先在fast bins中查找,然后才會去Bins中查找空閑的chunk。有時候ptmalloc也會遍歷fast bins中的chunk,將相鄰的空閑的chunk進行合並,並將合並后的chunk加入到unsorted bin中,然后將unsorted bin中的chunk加入到bin中。

​(2)unsorted bin:相當於small  bins和large bins的一個緩沖區,unsorted bin是bins數組中的第一個,用雙向鏈表管理chunk,空閑的chunk不排序,所有的chunk在回收時都要先放到unsorted bin中。進行malloc操作時,如果fast_bins中沒有找到合適的chunk,則ptmalloc就會在unsorted bin中查找空閑的chunk,如果unsorted bin中沒有合適的chunk,就會把unsorted bin中所有chunk分別加入到所屬的bin中,然后再在bin中查找合適的chunk。Bins數組中元素bin[1]用來存儲unsorted bin的鏈表頭。

​(3)small bins:數組中從2號下標開始的到64號下標的62個bin稱為small bins。同一個small bin中的chunk具有相同的大小,兩個相鄰的small bin中的chunk大小在SIZE_SZ為4B的平台上相差 8bytes,在SIZE_SZ為8B的平台上相差 16bytes。small bin 中chunk按照最近使用順序進行排列,最后釋放的chunk被連接到鏈表的頭部,而申請的chunk是從鏈表尾部開始的。

(4)large bins:在SIZE_SZ為4B的平台上,大於等於512B的空閑的chunk由large bin管理。large bins一共包含63個bin,每個bin中的chunk大小不是一個固定的等差數列,而是分成6組bin,每組bin是一個固定的等差數列。每組bin數量依次為32、16、8、4、2、1,公差依次為64B、512B、4096B、32768B、262144B等。

​(5)top chunk:並不是所有的chunk都會被放到bins上。top chunk相當於分配區的頂部空閑內存,當bins上都不能滿足內存分配要求的時候,就會來top chunk上分配。

 ​    <1>對於非主分配區。非主分配區會預先從mmap區域分配一塊較大的空閑內存模擬sub_heap,通過管理sub_heap來響應用戶的需求,因為內存是按照從低地址到高地址進行分配的,在空閑內存的最高處,必然存在着一塊空閑的chunk,叫做top chunk。

           top chunk的使用:當bins和fast bins都不能滿足分配需求的時候,ptmalloc會設法在top chunk中分配一塊內存給用戶,如果top chunk 本身不夠大,分配程序會重新分配一個sub_heap,並將top chunk遷移到新的sub_heap上,新的sub_heap與已有的sub_heap用單向鏈表連接起來,然后在新的top chunk中分配所需要的內存以滿足分配的需要。top chunk的大小會隨着分配和回收不停變化。​

     <2>對於主分配區。主分配區是唯一能夠映射進程heap區域的分配區,它可以通過sbrk()來增大和收縮進程heap的大小。ptmalloc在開始的時候會預先分配一塊較大的空閑內存。主分配區的top chunk在第一次調用malloc時會分配一塊空間作為初始化的heap。

(6)mmaped chunk:當需要分配的chunk​足夠大,fast bins和bins都不能滿足要求,甚至top chunk都不能滿足分配需求時,ptmalloc會使用mmap來直接使用頁映射機制來將頁映射到進程空間。這樣分配的chunk在被free時將直接解除映射,於是就將內存歸還給操作系統,再次對這樣的內存區的引用將導致錯誤。

五、ptmalloc響應用戶內存分配步驟

(1)​獲取分配區的鎖。目的是為了防止多個線程同時訪問同一個區域,在進行分配之前需要取得分配區域的鎖。

(2)​將用戶的請求大小轉換成實際需要分配的chunk空間的大小。

(3)判斷所需分配的chunk的大小是否滿足chunk_size<= max_size,如果是則轉到第4步,否則,轉第5步。

(4)首先嘗試在fast bins中取一個所需大小的chunk分配給用戶。如果可以找到,則分配結束,否則,轉到下一步。

(5)判斷所需要的大小是否處在small bins 中,如果在small bins中則轉下一步,否則,轉第7步。

(6)根據所需要分配的​chunk的大小,找到具體所在的某個small bin,從該bin的尾部摘取一個恰好滿足大小的chunk。若成功,則分配結束,否則,轉下一步。

(7)到了這一步說明需要分配的是一塊大內存,或者是small bin中找不到合適的chunk,於是,ptmalloc會遍歷所有的fast bins中的chunk,將相鄰的chunk進行合並,並連接到unsorted bin中,然后遍歷unsorted bin中的chunk。​如果unsorted bin中只有一個chunk,並且這個chunk大於等於需要分配的大小,這種情況下就直接將該chunk切割,分配結束。否則將根據chunk的空間大小將其放入到相應的small bins 或者 large bins 中。否則,進行下一步。

(8)到了這一步說明分配的是一塊很大的內存,或者是在unsorted bin和small bins中都沒有找到合適的chunk,fast bins​和unsorted bin中所有的chunk都清除干凈了,從large bins中找到一個合適的chunk,從中划分一塊所需大小的chunk,並將剩下的部分連接回bins中,如果操作成功就結束分配,否則,轉下一步。

(9)如果搜索bins都沒有找到合適的chunk,那么需要操作top chunk來進行分配了。判斷top chunk大小是否滿足所需要的chunk的大小,如果是,則從top chunk中分出一塊來。

(10)​到了這一步說明top chunk也不能滿足分配需求。所以有兩個選擇,如果是主分配區,調用sbrk(),增加top chunk 的大小,如果是非主分配區,調用mmap()來分配一個新的sub_heap,增加top chunk大小,或者是使用mmap()來直接分配。需要根據chunk的大小來決定使用哪種方法。如果所需要分配的chunk大小大於等於mmap分配閥值,則轉下一步使用mmap分配原則,否則轉12步。

(11)使用mmap系統調用為程序的內存空間映射一塊chunk_size align  4KB大小的空間。然后將內存指針返回給用戶。

(12)判斷是否是第一次調用malloc,若是主分配區,則需要進行一次初始化工作,分配一塊大小為(chunk_size + 128kb)slign大小的空間作為初始化的heap。若已經初始化過了,主分配區則調用sbrk()增加heap空間,非主分配區則在top chunk 中切割一個chunk,使之滿足分配需求,並將用戶指針返回給用戶。​

六、ptmalloc響應用戶內存回收步驟​

​free()函數接受一個指向分配區域的指針作為參數,釋放指針指向需要釋放的chunk。

(1)free()函數首先需要獲取分配區的鎖來保證線程安全。

(2)判斷傳入的指針是否為0,如果為0,則什么都不做,直接return。否則轉下一步。

(3)判斷所需釋放的chunk是否為mmaped chunk,如果是,則調用munmap()釋放解除空間映射,該空間不再有效。

(4)​判斷chunk的大小和所處的位置,若chunk_size<= max_fast,並且chunk並不處於heap的頂部,也就是說不與top chunk相鄰,則轉到下一步,否則轉到第6步。

(5)​將chunk方到fast bins中,chunk放入到fast bins中時,並不修改該chunk使用狀態位P,也不與相鄰的chunk進行合並。只是放進去。這一步做完之后釋放就結束了,程序從free()函數中返回。

(6)判斷前一個chunk是否正在使用中,如果前一個塊也是空閑塊,則合並。並轉下一步。

(7)判斷當前釋放的chunk的下一個塊是否為top chunk,如果是,則轉第9步,否則轉下一步。

(8)判斷下一個chunk是否處於使用中,如果下一個chunk也是空閑的,則合並,並將合並后的chunk放到unsorted bin中。注意,這里在合並過程中,要更新chunk的大小,以反映合並后的chunk的大小。並轉到第10步。

(9)如果執行到這一步,說明釋放了一個與top chunk相鄰的chunk。則無論它有多大,都將它和top chunk合並,並更新top chunk的大小等信息。轉下一步。

(10)判斷合並后的chunk的大小是否會大於max_fast(默認是64kb)​,如果是的話,則會觸發進行fast bins的合並操作,fast bins中的chunk將被遍歷,並與相鄰的chunk進行合並,合並后的chunk會被放到unsorted bin中。fast bins將變為空,操作完成后進入到下一步。

(11)​判斷 top chunk的大小是否大於mmap收縮閥值(默認是128kb),如果是的話,對於主分配區,則會試圖歸還top chunk中的一部分給操作系統。但是最先分配的128KB空間是不會歸還給操作系統的,ptmalloc會一直管理這部分內存,用來響應用戶的分配請求。如果是非主分配區,會進行sub_heap收縮,將top chunk的一部分返回給操作系統,如果 top chunk是整個sub_heap,會將整個sub_heap歸還給操作系統。做完這一步后,釋放結束,從free()函數退出。

七、配置選項概述

以下提供的配置選項可通過mallopt()進行設置。​

1、M_MXFAST:用於設置fast bins中保存的chunk的最大大小,默認值為64B。最大80B。

2、M_TRIM_THRESHOLD:用於設置mmap收縮閾值,默認值為128KB。

3、M_MMAP_THRESHOLD:M_MMAP_THRESHOLD用於設置mmap分配閾值,默認值為128KB。當用戶需要分配的內存大於mmap分配閾值,ptmalloc的malloc()函數其實相當於mmap()的簡單封裝,free函數相當於munmap()的簡單封裝。

4、M_MMAP_MAX:M_MMAP_MAX用於設置進程中用mmap分配的內存塊的地址段數量,默認值為65536。

5、M_TOP_PAD:該參數決定了,當libc內存管理器調用brk釋放內存時,堆頂還需要保留的空閑內存數量。該值缺省為 0。​

四、怎樣防止Glibc內存暴增​?

1、后分配的內存先釋放。因為ptmalloc收縮內存是從top chunk開始的,如果 top chunk相鄰的chunk不能釋放,top chunk以下的都無法釋放。

2、ptmalloc不合適用於管理長聲明周期的內存,特別是持續不定期分配和釋放​長生命周期的內存,這將導致ptmalloc內存暴增。

​3. 多線程分階段執行的程序不適合用ptmalloc,這種程序的內存更適合用內存池管理。

4. 盡量減少程序的線程數量和避免頻繁分配/釋放內存。頻繁分配,會導致鎖的競爭,最終導致非主分配區增加,內存碎片增高,並且性能降低。

5. 防止內存泄露,ptmalloc對內存泄露是相當敏感的,根據它的內存收縮機制,如果與top chunk相鄰的那個chunk沒有回收,將導致top chunk一下很多的空閑內存都無法返回給操作系統。

6. 防止程序分配過多內存,或是由於Glibc內存暴增,導致系統內存耗盡,程序因OOM被系統殺掉。預估程序可以使用的最大物理內存大小,配置系統的/proc/sys/vm/overcommit_memory,/proc/sys/vm/overcommit_ratio,以及使用ulimt –v限制程序能使用虛擬內存空間大小,防止程序因OOM被殺掉。

 

參考:Glibc內存管理

http://www.valleytalk.org/wp-content/uploads/2015/02/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%901.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM