色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于FreeRTOS的STM32F103系統—Heap_4內存管理機制介紹

冬至子 ? 來源:月月望歸鳥 ? 作者:K.Fire ? 2023-11-10 11:08 ? 次閱讀

1

Heap_4內存管理機制詳解

首先介紹一下用到的重要的結構體-標記內存塊,在每個存放數據的內存塊前都會有一個這樣的標記結構體。

typedef struct A_BLOCK_LINK
{
  struct A_BLOCK_LINK *pxNextFreeBlock;  /*< < The next free block in the list. */
  size_t xBlockSize;            /*< < The size of the free block. */
} BlockLink_t;

里面有兩個變量,pxNextFreeBlock指向下一個內存塊,xBlockSize用來表示它所標記的內存塊大小。

還有一些全局變量,都寫了注釋很好理解,就不多解釋。

//內存堆大小,并字節對齊
static const size_t xHeapStructSize  = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );

/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, *pxEnd = NULL;      //內存堆頭尾

/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = 0U;        //內存堆剩余大小
static size_t xMinimumEverFreeBytesRemaining = 0U;  //歷史剩余大小的最小值

/* Gets set to the top bit of an size_t type.  When this bit in the xBlockSize
member of an BlockLink_t structure is set then the block belongs to the
application.  When the bit is free the block is still part of the free heap
space. */
static size_t xBlockAllocatedBit = 0;      //1這個塊被申請;0這個塊空閑

2

內存堆初始化

首先定義一些臨時變量

BlockLink_t *pxFirstFreeBlock;                  //整個空閑內存塊之前的標記結構體
uint8_t *pucAlignedHeap;                    //字節對齊后的起始地址
size_t uxAddress;                        //相當于臨時變量
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;          //拋棄不可用內存塊后總的大小

經過一系列的操作,使初始化后,空閑內存表的起始地址為字節對齊,這里和heap_2不同的地方是,使用了臨時變量uxAddress存儲中間計算出來的一些地址,這里uxAddress存儲的是字節對齊后的初始地址,然后賦值給pucAlignedHeap變量中。

/* Ensure the heap starts on a correctly aligned boundary. */
  /*確保字節對齊后的起始地址正確*/
  uxAddress = ( size_t ) ucHeap;                //獲得內存堆的大小放到uxAddress中

  if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )      //如果內存堆大小不為0且不在掩模中
  {
    uxAddress += ( portBYTE_ALIGNMENT - 1 );        //portBYTE_ALIGNMENT = 7
    uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );  
    xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;    //拋棄不可用內存塊后總的大小
  }

  pucAlignedHeap = ( uint8_t * ) uxAddress;          //字節對齊后的起始地址

這部分代碼用來初始化空閑內存表的頭和尾,使頭的下一個內存塊指向字節對齊后的首地址,大小初始化為0;這里的uxAddress變量經過一系列操作以及變成了內存塊的末地址,然后使尾的首地址指向末地址(pxEnd=(void *)uxAddress),大小初始化為0,尾的下一個內存塊為NULL。

/*初始化鏈表頭和尾*/
  xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;      //下一個頭指向字節對齊后的起始地址
  xStart.xBlockSize = ( size_t ) 0;              //大小初始化為0              

  /* pxEnd is used to mark the end of the list of free blocks and is inserted
  at the end of the heap space. */
  uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;  //這里的uxAddress已經變成了末尾的地址
  uxAddress -= xHeapStructSize;                //減去一個標志結構體的大小
  uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );    //字節對齊
  pxEnd = ( void * ) uxAddress;                //這里的uxAddress已經變成了內存堆尾-一個標志結構體然后字節對齊后的地址
  pxEnd- >xBlockSize = 0;                    //末尾的內存塊大小初始化為0
  pxEnd- >pxNextFreeBlock = NULL;                //下一個指向NULL

在申請內存的最開始,把整個內存堆都看成一個整體,作為一個大內存塊,這個內存塊之前也需要有一個標記結構體,也就是pxFirstFreeBlock結構體,這里對這個結構體進行初始化,它的首地址就是字節對齊后的地址,大小是尾地址uxAddess-內存塊字節對齊后的首地址,下一個內存塊指向pxEnd。

/*開始的時候將內存堆整個可用空間看成一個空閑內存塊*/
  pxFirstFreeBlock = ( void * ) pucAlignedHeap;              //空閑內存塊之前的標記結構體地址    
  pxFirstFreeBlock- >xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;  //標記結構體記錄內存塊大小為末地址-初地址
  pxFirstFreeBlock- >pxNextFreeBlock = pxEnd;                //下一個空閑內存塊為末尾內存塊指針

最后這里就是更新一下全局變量,并標記一下塊被占用。

/*只有一個內存塊,而且這個內存塊擁有內存堆的整個可用空間*/
  xMinimumEverFreeBytesRemaining = pxFirstFreeBlock- >xBlockSize;      //記錄最小的空閑內存塊大小
  xFreeBytesRemaining = pxFirstFreeBlock- >xBlockSize;            //記錄歷史最小的空閑內存塊大小

  /* Work out the position of the top bit in a size_t variable. */
  xBlockAllocatedBit = ( ( size_t ) 1 ) < < ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );    //初始化靜態變量,初始化完成以后此變量值為 0X80000000
  //在 heap_4 中其最高位表示此內存塊是否被使用,如果為 1 的話就表示被使用了,所以在 heap_4 中一個內存塊最大只能為 0x7FFFFFFF

借用一下原子手冊的圖解:

圖片

3

插入空閑內存表函數

先定義兩個用到的局部變量,pxIterator相當于C++中容器的迭代器,puc就是個臨時變量。

BlockLink_t *pxIterator;    //相當于查找合適位置的迭代器
uint8_t *puc;          //相當于臨時變量

這里就是使用迭代器一次次循環,知道找到空閑內存表中滿足內存要求(pxIterator->pxNextFreeBlock < pxBlockToInsert)的內存塊地址。

//遍歷空閑內存塊鏈表,找出內存塊插入點,內存塊按照地址從低到高連接在一起(迭代器思想)
  for( pxIterator = &xStart; pxIterator- >pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator- >pxNextFreeBlock )
  {
    /* Nothing to do here, just iterate to the right position. */
  }

這里是判斷要插入的這塊內存和前一塊內存是否相鄰,如果相鄰就合并成一塊,判斷是否相鄰的條件是puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert,插入點地址+這塊內存的大小==要插入塊首地址;即上一塊末地址==要插入塊起始地址

//插入內存塊,如果要插入的內存塊可以和前一個內存塊合并的話就
  //合并兩個內存塊
  puc = ( uint8_t * ) pxIterator;                //找到的合適的插入點的地址
  if( ( puc + pxIterator- >xBlockSize ) == ( uint8_t * ) pxBlockToInsert ) //插入點地址+這塊內存的大小==要插入塊首地址;即上一塊末地址==要插入塊起始地址
  {
    pxIterator- >xBlockSize += pxBlockToInsert- >xBlockSize;  //大小合并
    pxBlockToInsert = pxIterator;              //合并后內存首地址不變
  }
  else
  {
    mtCOVERAGE_TEST_MARKER();
  }

再借用一下原子的圖:

圖片

這一部分代碼是檢查要插入的內存塊是否和后一塊內存相鄰,如果相鄰就合并起來,判斷條件是puc + pxBlockToInsert->xBlockSize == ( uint8_t * ) ( pxIterator->pxNextFreeBlock ),要插入塊首地址+這塊內存的大小==下一塊首地址;即要插入塊末地址==下一塊起始地址

//檢查是否可以和后面的內存塊合并,可以的話就合并
  puc = ( uint8_t * ) pxBlockToInsert;      //要插入的內存塊的首地址
  if( ( puc + pxBlockToInsert- >xBlockSize ) == ( uint8_t * ) pxIterator- >pxNextFreeBlock )  要插入塊首地址+這塊內存的大小==下一塊首地址;即要插入塊末地址==下一塊起始地址
  {
    if( pxIterator- >pxNextFreeBlock != pxEnd )    //下一塊不是表尾
    {
      /* Form one big block from the two blocks. */
      //將兩個內存塊組合成一個大的內存塊時
      pxBlockToInsert- >xBlockSize += pxIterator- >pxNextFreeBlock- >xBlockSize;      內存塊大小合并
      pxBlockToInsert- >pxNextFreeBlock = pxIterator- >pxNextFreeBlock- >pxNextFreeBlock;//合并起來之后下下快變成了下一塊
    }
    else
    {
      pxBlockToInsert- >pxNextFreeBlock = pxEnd;  //要插入的變成表尾
    }
  }
  else
  {
    pxBlockToInsert- >pxNextFreeBlock = pxIterator- >pxNextFreeBlock;
  }

最后借用一下原子的圖:

圖片

如果和前后都不相鄰,則使用最簡單的插入方法:

//在內存塊插入的過程中沒有進行過一次內存合并,使用最簡單的插入方法
  if( pxIterator != pxBlockToInsert )
  {
    pxIterator- >pxNextFreeBlock = pxBlockToInsert;
  }
  else
  {
    mtCOVERAGE_TEST_MARKER();
  }

4

內存申請函數

先初始化一下內存堆:

//第一次調用,初始化內存堆
    if( pxEnd == NULL )
    {
      prvHeapInit();
    }
    else
    {
      mtCOVERAGE_TEST_MARKER();
    }

判斷一下想要插入數據的內存塊是否被使用,就是和xBlockAllocateBit變量做一次與運算,如果結果不是1,則說明沒被使用;在確保要插入的大小大于0之后,需要附加上標記結構體的大小(8字節)后,再進行字節對齊。

//需要申請的內存塊大小的最高位不能為 1,因為最高位用來表示內存塊有沒有被使用
    if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
    {
      /* The wanted size is increased so it can contain a BlockLink_t
      structure in addition to the requested amount of bytes. */
      if( xWantedSize > 0 )
      {
        xWantedSize += xHeapStructSize;    //要申請的大小加上標記結構體的大小
        /* Ensure that blocks are always aligned to the required number
        of bytes. */
        if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
        {
          /* Byte alignment required. */
          /*要插入的內存塊字節對齊*/
          xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
          configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
        }
        else
        {
          mtCOVERAGE_TEST_MARKER();
        }
      }
      else
      {
        mtCOVERAGE_TEST_MARKER();
      }

當我們想要插入的內存塊小于剩余內存大小時,就開始查找滿足要求的內存塊。

if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
      {
        /* Traverse the list from the start  (lowest address) block until
        one  of adequate size is found. */
        //從 xStart(內存塊最小)開始,查找大小滿足所需要內存的內存塊
        pxPreviousBlock = &xStart;        //上一個內存塊
        pxBlock = xStart.pxNextFreeBlock;    //滿足要求的內存塊(下一塊)
        while( ( pxBlock- >xBlockSize < xWantedSize ) && ( pxBlock- >pxNextFreeBlock != NULL ) )
        {
          pxPreviousBlock = pxBlock;
          pxBlock = pxBlock- >pxNextFreeBlock;
        }

如果找到的是pxEnd表示沒有內存可以分配,否則就將內存首地址保存在 pvReturn 中,函數返回的時候返回此值,然后將這塊內存從空閑內存表中刪除

//如果找到的內存塊是 pxEnd 的話就表示沒有內存可以分配
        if( pxBlock != pxEnd )
        {
          /* Return the memory space pointed to - jumping over the
          BlockLink_t structure at its start. */
          //找到內存塊以后就將內存首地址保存在 pvReturn 中,函數返回的時候返回此值
          //找到的內存塊讓出一個標志結構體的大小
          pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock- >pxNextFreeBlock ) + xHeapStructSize );

          /* This block is being returned for use so must be taken out
          of the list of free blocks. */
          //將申請到的內存塊從空閑內存鏈表中移除
          pxPreviousBlock- >pxNextFreeBlock = pxBlock- >pxNextFreeBlock;  //把滿足要求的pxBlock塊的下一塊拼接到上一塊

申請的內存大小小于空閑的一大塊內存的大小,則將其分割,剩下的留著,相當于給空閑內存塊的首地址做一個地址偏移:新的空閑內存塊=滿足要求的內存塊首地址+需要的內存塊首地址,然后更新新的空閑內存塊的大小,并將其插入到空閑內存表。

//如果申請到的內存塊大于所需的內存,就將其分成兩塊
          if( ( pxBlock- >xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
          {
            /* This block is to be split into two.  Create a new
            block following the number of bytes requested. The void
            cast is used to prevent byte alignment warnings from the
            compiler. */
            pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );  //新的空閑內存塊=滿足要求的內存塊首地址+需要的內存塊首地址 
            configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );

            /* Calculate the sizes of two blocks split from the
            single block. */
            pxNewBlockLink- >xBlockSize = pxBlock- >xBlockSize - xWantedSize;  //更新新的空閑內存塊的大小
            pxBlock- >xBlockSize = xWantedSize;                //滿足要求的內存塊的大小

            /* Insert the new block into the list of free blocks. */
            prvInsertBlockIntoFreeList( pxNewBlockLink );          //插入新的空閑內存塊
          }
          else
          {
            mtCOVERAGE_TEST_MARKER();
          }

最后就是更新一下全局變量

xFreeBytesRemaining -= pxBlock- >xBlockSize;              //更新最小內存塊大小

          if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )      //更新歷史最小內存塊大小
          {
            xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
          }
          else
          {
            mtCOVERAGE_TEST_MARKER();
          }

          /* The block is being returned - it is allocated and owned
          by the application and has no "next" block. */
          //內存塊申請成功,標記此內存塊已經被使用
          pxBlock- >xBlockSize |= xBlockAllocatedBit;  //將pxBlock最高位置1
          pxBlock- >pxNextFreeBlock = NULL;      //滿足要求的內存塊下一塊指向NULL

后面還可以配置內存申請失敗時的鉤子函數,需要把configUSE_MALLOC_FAILED_HOOK宏打開

#if( configUSE_MALLOC_FAILED_HOOK == 1 )
  {
    if( pvReturn == NULL )
    {
      extern void vApplicationMallocFailedHook( void );
      vApplicationMallocFailedHook();
    }
    else
    {
      mtCOVERAGE_TEST_MARKER();
    }
  }
  #endif

5

內存釋放函數

先定義一些用到的局部變量:

uint8_t *puc = ( uint8_t * ) pv;  //傳入要釋放內存的地址
BlockLink_t *pxLink;        //包含了標志結構體后的首地址

傳入的數據地址沒包含標志結構體,需要先做減法,進行地址移位,然后將包含了標志結構體的首地址保存在pxLink中

puc -= xHeapStructSize;          //釋放的部分包括上標志結構體大小
/* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc;        //防止編譯器報錯

如果要釋放的內存真的被使用,就開始釋放操作,先把首位變0,表示變成空閑,然后更新空閑內存大小,將這塊內存插入回空閑內存表中,要注意:釋放和申請內存,并不是把這塊內存從一個鏈表中拿出來了,只是做了一些標記,讓程序知道這部分被占用,有數據,在釋放內存之前我們將數據刪除,然后把標志位改為空閑狀態就行,這就是釋放的本質。

if( ( pxLink- >xBlockSize & xBlockAllocatedBit ) != 0 )    //判斷是否真被使用
    {
      if( pxLink- >pxNextFreeBlock == NULL )
      {
        /* The block is being returned to the heap - it is no longer
        allocated. */
        pxLink- >xBlockSize &= ~xBlockAllocatedBit;      //首位變0,表示未被使用

        vTaskSuspendAll();
        {
          /* Add this block to the list of free blocks. */
          /*將內存塊插到空閑內存鏈表中*/
          xFreeBytesRemaining += pxLink- >xBlockSize;          //更新最小內存塊大小
          traceFREE( pv, pxLink- >xBlockSize );            
          prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );  //將被釋放的內存塊插入空閑內存鏈表中
        }
        ( void ) xTaskResumeAll();
      }
      else
      {
        mtCOVERAGE_TEST_MARKER();
      }
    }
    else
    {
      mtCOVERAGE_TEST_MARKER();
    }

6

總結

其他的函數主要就是直接返回我們之前更新的全局變量,終于把基礎知識都鋪墊完了,下面結合具體項目程序談談怎么優化了。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • FreeRTOS
    +關注

    關注

    12

    文章

    492

    瀏覽量

    63835
  • STM32F103
    +關注

    關注

    33

    文章

    482

    瀏覽量

    65140
  • 內存管理
    +關注

    關注

    0

    文章

    168

    瀏覽量

    14496
  • 迭代器
    +關注

    關注

    0

    文章

    45

    瀏覽量

    4443
收藏 0人收藏

    評論

    相關推薦
    熱點推薦

    第28章 FreeRTOS動態內存管理

    教程配套的例子含Cortex-M3內核的STM32F103和Cortex-M4內核的STM32F407以及F429。28.1 動態內存
    發表于 09-11 07:15

    FreeRTOS vPortFree 內存釋放異常怎么辦

    函數因為需要使用到一個 4k 的buf做數據暫存;為了使用方便引用了FreeRTOSheap_4 內存管理方式;pvPortMallo
    發表于 07-13 10:36

    基于FreeRTOS內存管理Heap_4.c的實現方法

    一下,哈哈。 既然是在FreeRTOS內存管理Heap_4.c的基礎上稍稍修改的,那還是先介紹一下它的實現方法吧:以下為轉載內容,原文鏈接:
    發表于 07-15 21:46

    linux內存管理機制淺析

    本內容介紹了arm linux內存管理機制,詳細說明了linux內核內存管理,linux虛擬內存
    發表于 12-19 14:09 ?73次下載
    linux<b class='flag-5'>內存</b><b class='flag-5'>管理機制</b>淺析

    基于STM32F103的振動監測系統設計

    基于STM32F103的振動監測系統設計。
    發表于 11-09 17:49 ?46次下載

    FreeRTOS代碼剖析之4內存管理Heap

    FreeRTOS8.0.1內存管理的最后一個堆模型Heap_4,貌似是在這一個版本才有的。所以找到的說明幾乎沒有。代碼的開頭注釋也只是簡單地說了一下實現了pvPortMalloc
    發表于 02-09 02:52 ?464次閱讀

    FreeRTOS代碼剖析之1:內存管理Heap

    內存管理是一個操作系統的重要組成部分之一,所有應用程序都離不開操作系統內存管理。因此,在剖析
    發表于 02-09 05:25 ?1117次閱讀
    <b class='flag-5'>FreeRTOS</b>代碼剖析之1:<b class='flag-5'>內存</b><b class='flag-5'>管理</b><b class='flag-5'>Heap</b>

    FreeRTOS代碼剖析之3:內存管理Heap

    STM32F103中,FreeRTOS管理的堆就定義在啟動文件startup_stm32f10x_xd.s中。 不過,就算是直接引用了標準
    發表于 02-09 05:30 ?528次閱讀

    STM32F103的振動監測系統設計

    STM32F103的振動監測系統設計
    發表于 09-28 14:45 ?49次下載
    <b class='flag-5'>STM32F103</b>的振動監測<b class='flag-5'>系統</b>設計

    嵌入式系統內存管理機制詳解

    操作系統內存管理功能用于向操作系統提供一致的地址映射功能和內存頁面的申請、釋放操作。在嵌入式實時系統
    發表于 11-18 09:41 ?4704次閱讀

    STM32F103芯片資料介紹

    只是STM32F103芯片資料的簡單介紹,文章由(逆向開發技術網)編輯整理。下次我們將具體介紹一下”STM32F103芯片解密方法”
    發表于 01-08 08:00 ?190次下載
    <b class='flag-5'>STM32F103</b>芯片資料<b class='flag-5'>介紹</b>

    淺析物理內存與虛擬內存的關系及其管理機制

    本文主要介紹內存管理機制:物理內存與虛擬內存的關系,Linux內存
    的頭像 發表于 04-12 09:55 ?5931次閱讀
    淺析物理<b class='flag-5'>內存</b>與虛擬<b class='flag-5'>內存</b>的關系及其<b class='flag-5'>管理機制</b>

    GD32F103STM32F103的區別 2021.6.2

    GD32F103STM32F103區別介紹關鍵詞Key words:GD32F103STM32F103摘要Abstract:本文主要是G
    發表于 12-08 11:06 ?83次下載
    GD32<b class='flag-5'>F103</b>與<b class='flag-5'>STM32F103</b>的區別 2021.6.2

    heap_4內存分配方法介紹

    heap_4 內存分配方法 heap_4 提供了一個最優的匹配算法,不像 heap_2,heap_4 會將
    的頭像 發表于 07-30 10:42 ?1282次閱讀

    FreeRTOS heap_5內存分配方法介紹

    heap_5 內存分配方法 heap_5 使用了和 heap_4 相同的合并算法,內存管理實現起
    的頭像 發表于 07-30 10:47 ?1355次閱讀
    主站蜘蛛池模板: 彭丹吃奶门| 国产二级一片内射视频播放 | 亚洲日韩天堂在线中文字幕 | 视频成人永久免费下载 | 捆绑调教网站 | 九九在线精品亚洲国产 | 欧美亚洲日韩国产在线在线 | 亚洲精品一区二区在线看片 | 抽插H浊水H嫩B父皇 虫族bl文全肉高h | 亚洲精品一二三区-久久 | 女性性纵欲派对 | 美女靠逼漫画 | 一二三四在线高清中文版免费观看电影 | 欧美阿v在线免播播放 | 在线高清视频不卡无码 | china男士同性视频tv | 少妇连续高潮抽搐痉挛昏厥 | 少爷不要别揉了高H | 亚洲人成无码久久久AAA片 | 欧洲精品不卡1卡2卡三卡四卡 | 美女打开双腿扒开屁股男生 | 香蕉久久av一区二区三区 | 国产原创中文视频 | 性欧美videos俄罗斯 | 亚洲精品国产熟女久久久 | 精品夜夜澡人妻无码AV蜜桃 | 爱豆剧果冻传媒在线播放 | 高清国语自产拍在线 | 久久精品国产久精国产果冻传媒 | 国产九九熟女在线视频 | 亚洲AV电影天堂男人的天堂 | 学生精品国产在线视频 | 久久这里都是精品 | 九九99热久久999精品 | 国产成人免费a在线视频app | 青草视频久久 | 亚洲青青草 | 色欲蜜臀AV免费视频 | 一级性生活毛片 | 狠狠操天天操夜夜操 | 老司机福利视频一区在线播放 |

    電子發燒友

    中國電子工程師最喜歡的網站

    • 2931785位工程師會員交流學習
    • 獲取您個性化的科技前沿技術信息
    • 參加活動獲取豐厚的禮品