前言:
前一章我们讨论了内存池的分配和回收的一些内幕,这一节我们将来讨论一下lwip的mem内存堆机制,那有的人就很好奇,既然有了内存池的管理机制了,为什么还要多此一举搞个内存堆管理呢?二者有什么区别,又或者各有什么优缺点呢? 这些疑惑将在这一节揭晓。

1、memp相关宏以及变量的解释
【1】宏定义解释

1、MEM_USE_POOLS         //使用内存池分配内存堆  
2、MEM_LIBC_MALLOC       //使用标准c函数库分配  
3、MIN_SIZE                //最小内存池大小  
4、LWIP_RAM_HEAP_POINTER    //定义的内存池的头部
6、MEM_USE_POOLS_TRY_BIGGER_POOL  //如果当前内存池枯竭,将尝试其他大的内存池

【2】数据结构

struct memp_malloc_helper  
{  
   memp_t poolnr;  
}; //当使用MEM_USE_POOLS时,mem内存用于区分哪一类型内存池  
 
struct mem {  
  /** index (-> ram[next]) of the next struct */  
  mem_size_t next; 
  /** index (-> ram[prev]) of the previous struct */  
  mem_size_t prev;  
  /** 1: this area is used; 0: this area is unused */  
  u8_t used;  
};  //当不使用MEM_USE_POOLS是内存管理结构

以上就是内存堆管理最重要的两个基本结构单元

【3】变量

u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];   //实际定义的物理内存堆
static u8_t *ram;   //始终指向内存堆的首部
static struct mem *ram_end;   //始终指向内存堆的尾部
static struct mem *lfree;  //始终指向内存堆中最低空闲内存块的地址

有人就要问了,就上面几个变量吗?你可不要耍我啊,对你没有看错,在你眼中如此冗杂的内存管理仅仅使用上图这么几个简答的变量,由此可以看出其编码的巧妙。这也告诉我们,看起来冗杂的东西不要害怕,maybe只是虚胖,哈哈哈。。。

2、Mem的内存机制原理

这里,为了让大家更加直观的认识,我觉得从上而下开始讲解。其实MEM提供几种分配机制。

1、使用使用LWIP自己的内存分配机制

2、使用系统的库(malloc和free)来分配

而决定上面的分配方式是通过MEM_LIBC_MALLOC来实现的,当为1时采用c中的malloc和free来实现的。这里我们着重分析LWIP自己的内存分配机制。好了,现在LWIP是通过自己的内存分配机制来实现内存堆了,那么就结束了吗??? 不不不,怎么可能,LWIP在此基础上又分了两种构造机制。

  1. 使用MEMP机制来分配内存堆
  2. 使用自定义的物理内存来分配内存堆

由此,才将所谓的内存申请动作真正映射到物理内存,故一般我将第二个宏的作用定义为决定内存的来源。

本着有简到难的观点出发,我们首先来看一下USE POOLS。

上图为使用内存池来分配内存堆的图示,由图我们可以看出,内存申请后,lwip在申请的内存前加了一个poolnr的结构,这个正是我们前面提到的struct memp_malloc_helper  结构,它专门用来管理使用内存池分配内存堆的。

有的人可能就要问了,为什么要引入这样的结构呢?按照我们以往的经验来说,不应该是分配好内存,到释放的时候直接使用这个地址来释放就好了吗? 是的,你说的没错,但是你忽略了一点,那就是不同的内存池的大小不一样,也就是在释放的时候必须还要指定具体是哪一个尺寸的内存池给你分配的内存,因此,这个结构主要就是记录这个信息。

有的人又要问,那我使用一个外部变量来管理不就可以了,确实如此,但是假象一下,当你申请了100个内存块,此时如果你自己来管理将是多磨的冗杂,并且容易出错,因此lwip为我们很好的解决了这个问题。

讲完了mem使用内存池的申请,我们接下来看看内存堆自己对申请和释放是如何管理的。首先来看一下内存堆初始的模型

由图可以看出,在初始化完成,其实内存堆被分为了两个块,一块是真正的内存卡,及ram_heap和ram_end之间的内存,另一个是ram_end之后的内存。至于为什么其后面存在多余的空闲的内存,主要是考虑的内存对齐的开销,因此增加了适量的内存。

上图所示为mem在初始化完成申请的第一个内存,可以看出Ifree会随着分配的进行指向最低空闲内存块,同是由struct mem结构来连接新形成的内存块,使其形成一个链式的结构,使用used域来标识该内存块是否使用。

上图表示申请了n次后的一个内存状况,可以看出,经过n次的内存申请和释放后,内存趋于分散化,此时将形成内存碎片,而不是像memp一样0碎片化。而对于如何优化和处理碎片也是众多内存分配和释放算法的一个重要的区别。对于这个小的碎片,我们需要在适时对其进行合并,以达到一些大的内存申请需要。Lwip也提供了这样的机制,我们将在源码分析中对其进行讲解。我们可以通过下图加以理解。

由于图片太大,貌似截图都不太清晰,文章后边将附上这些图的下载链接供读者自行下载。

3、Mem的源码分析
【1】内存池的初始化

这里需要注意一点,对于内存池分配方式而言,是不需要mem_init()的初始化的,原因在于mem_init主要是为内存堆准备起始环境,而如果使用内存池分配不需要该环境,而是需要初始化memp_init()内存池环境。

void  
mem_init(void)  
{  
  struct mem *mem;  
  
  LWIP_ASSERT("Sanity check alignment",  
  (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);  
  
  /* align the heap */  
  ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);  
  /* initialize the start of the heap */  
  mem = (struct mem *)(void *)ram;  
  mem->next = MEM_SIZE_ALIGNED;  
  mem->prev = 0;  
  mem->used = 0;  
  /* initialize the end of the heap */  
  ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED];  
  ram_end->used = 1;  
  ram_end->next = MEM_SIZE_ALIGNED;  
  ram_end->prev = MEM_SIZE_ALIGNED;  
  
  /* initialize the lowest-free pointer to the start of the heap */  
  lfree = (struct mem *)(void *)ram;  
      
  MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);  
      
  if(sys_mutex_new(&mem_mutex) != ERR_OK) {  
    LWIP_ASSERT("failed to create mem_mutex", 0);  
  }  
}
由上面可以看出,内存堆初始化主要做了这几件事。

1、初始化内存管理的基本结构,即mem结构。

2、初始化尾部ram_end及Ifree。

【2】内存池的分配

1、使用内存池分配

void *  
mem_malloc(mem_size_t size)  
{  
  void *ret;  
  struct memp_malloc_helper *element;  
  memp_t poolnr;  
  mem_size_t required_size = size + LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));  //计算实际需要的内存
      
for (poolnr = MEMP_POOL_FIRST; poolnr <= MEMP_POOL_LAST; poolnr = (memp_t)(poolnr + 1)) {  
#if MEM_USE_POOLS_TRY_BIGGER_POOL  
 again:  
#endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */  
    /* is this pool big enough to hold an element of the required size 
       plus a struct memp_malloc_helper that saves the pool this element came from? */  
   if (required_size <= memp_sizes[poolnr]) {  
      break;  //适配到需要的大小相当的内存池
    }  
  }  
   if (poolnr > MEMP_POOL_LAST) {  
    LWIP_ASSERT("mem_malloc(): no pool is that big!", 0);  
    return NULL;  
  }  
  element = (struct memp_malloc_helper*)memp_malloc(poolnr);  //从该内存池的链表上进行内存申请,但是此时不一样链表上存在内存
  if (element == NULL) {  
    /* No need to DEBUGF or ASSERT: This error is already 
       taken care of in memp.c */  
#if MEM_USE_POOLS_TRY_BIGGER_POOL  //如果定义了 可以查找其他的内存池链
    /** Try a bigger pool if this one is empty! */  
    if (poolnr < MEMP_POOL_LAST) {  //遍历所有的内存池
      poolnr++;  
      goto again;  
    }  
#endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */  
    return NULL;  //内存枯竭  申请失败
  }  
  
  /* save the pool number this element came from */  
  element->poolnr = poolnr;  
  /* and return a pointer to the memory directly after the struct memp_malloc_helper */  
  ret = (u8_t*)element + LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));  
      
  return ret;  
}

内存池的分配方式高效并且不会产生碎片,这在memp内存池这一章已经讲过了,mem的主要优点在于可以对不同内存大小的分配更加的灵活。以上的分配的策略主要是:

  1. 查找当前内存是否存在大于需求的内存池。存在将返回内存池的label
  2. 通过label查找该内存池的空闲块看是否存在空闲内存,不存在将分配失败。

当然,如果 使能了MEM_USE_POOLS_TRY_BIGGER_POOL宏的话,将再次搜索下一个大于需求内存的label,直到找到或者全遍历。

2、使用内存堆分配

mem_malloc(mem_size_t size)  
{  
  mem_size_t ptr, ptr2;  
  struct mem *mem, *mem2;  
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
  u8_t local_mem_free_count = 0;  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
  LWIP_MEM_ALLOC_DECL_PROTECT();  
  
  if (size == 0) {  
    return NULL;  
  }  
  
  /* Expand the size of the allocated memory region so that we can 
     adjust for alignment. */  
  size = LWIP_MEM_ALIGN_SIZE(size);  
  
  if(size < MIN_SIZE_ALIGNED) {  
    /* every data block must be at least MIN_SIZE_ALIGNED long */  
    size = MIN_SIZE_ALIGNED;  //保证最小的内存空间,避免过小的内存碎片化
  }  
      
  if (size > MEM_SIZE_ALIGNED) {  
    return NULL;  
  }  
      
  /* protect the heap from concurrent access */  
  sys_mutex_lock(&mem_mutex);  
  LWIP_MEM_ALLOC_PROTECT();  
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
  /* run as long as a mem_free disturbed mem_malloc or mem_trim */  
  do {  
    local_mem_free_count = 0;  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
      
    /* Scan through the heap searching for a free block that is big enough, 
     * beginning with the lowest free block. 
     */ 
    //搜索空闲内存 
    for (ptr = (mem_size_t)((u8_t *)lfree - ram); ptr < MEM_SIZE_ALIGNED - size;  ptr = ((struct mem *)(void *)&ram[ptr])->next) {  
      mem = (struct mem *)(void *)&ram[ptr];  //获取内存块首地址
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
      mem_free_count = 0;  
      LWIP_MEM_ALLOC_UNPROTECT();  
      /* allow mem_free or mem_trim to run */  
      LWIP_MEM_ALLOC_PROTECT();  
      if (mem_free_count != 0) {  
        /* If mem_free or mem_trim have run, we have to restart since they 
           could have altered our current struct mem. */  
        local_mem_free_count = 1;  
        break;  
      }  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
       //查找空闲内存是否大于需求的内存
      if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {
        /* mem is not used and at least perfect fit is possible: 
         * mem->next - (ptr + SIZEOF_STRUCT_MEM) gives us the 'user data size' of mem */  
        if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) {  //查看内存后边的空闲内存是否可以继续维持一个空闲内存块
          /* (in addition to the above, we test if another struct mem (SIZEOF_STRUCT_MEM) containing 
           * at least MIN_SIZE_ALIGNED of data also fits in the 'user data space' of 'mem') 
           * -> split large block, create empty remainder, 
           * remainder must be large enough to contain MIN_SIZE_ALIGNED data: if 
           * mem->next - (ptr + (2*SIZEOF_STRUCT_MEM)) == size, 
           * struct mem would fit in but no data between mem2 and mem2->next 
           * @todo we could leave out MIN_SIZE_ALIGNED. We would create an empty 
           *       region that couldn't hold data, but when mem->next gets freed, 
           *       the 2 regions would be combined, resulting in more free memory 
           */  
          ptr2 = ptr + SIZEOF_STRUCT_MEM + size; //分配内存后边的内存地址  
          /* create mem2 struct */  
          mem2 = (struct mem *)(void *)&ram[ptr2]; //后边内存形成一个新的内存块  
          mem2->used = 0;  
          mem2->next = mem->next;  
          mem2->prev = ptr;  
          /* and insert it between mem and mem->next */  
          mem->next = ptr2;  
          mem->used = 1;  
      
          if (mem2->next != MEM_SIZE_ALIGNED) {  //不是尾部结束块需要将其指向前面的内存块,形成双向链表
            ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;  
          }  
          MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));  
        }   
        else {   //后边内存过小,出现碎片化
          /* (a mem2 struct does no fit into the user data space of mem and mem->next will always 
           * be used at this point: if not we have 2 unused structs in a row, plug_holes should have 
           * take care of this). 
           * -> near fit or excact fit: do not split, no mem2 creation 
           * also can't move mem->next directly behind mem, since mem->next 
           * will always be used at this point! 
           */  
          mem->used = 1;  
          MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));  
        }  
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
mem_malloc_adjust_lfree:  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
        if (mem == lfree) { //Ifree更新到新的空闲内存地址  
          struct mem *cur = lfree;  
          /* Find next free block after mem and update lowest free pointer */  
          while (cur->used && cur != ram_end) {  
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
            mem_free_count = 0;  
            LWIP_MEM_ALLOC_UNPROTECT();  
            /* prevent high interrupt latency... */  
            LWIP_MEM_ALLOC_PROTECT();  
            if (mem_free_count != 0) {  
              /* If mem_free or mem_trim have run, we have to restart since they 
                 could have altered our current struct mem or lfree. */  
              goto mem_malloc_adjust_lfree;  
            }  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
            cur = (struct mem *)(void *)&ram[cur->next];  
          }  
          lfree = cur;  
          LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));  
        }  
        LWIP_MEM_ALLOC_UNPROTECT();  
        sys_mutex_unlock(&mem_mutex);  
        LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",  
         (mem_ptr_t)mem + SIZEOF_STRUCT_MEM + size <= (mem_ptr_t)ram_end);  
        LWIP_ASSERT("mem_malloc: allocated memory properly aligned.",  
         ((mem_ptr_t)mem + SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT == 0);  
        LWIP_ASSERT("mem_malloc: sanity check alignment",  
          (((mem_ptr_t)mem) & (MEM_ALIGNMENT-1)) == 0);  
      
        return (u8_t *)mem + SIZEOF_STRUCT_MEM; //返回申请的内存地址 
      }  
    }  
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
    /* if we got interrupted by a mem_free, try again */  
  } while(local_mem_free_count != 0);  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
  LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mem_malloc: could not allocate %"S16_F" bytes\n", (s16_t)size));  
  MEM_STATS_INC(err);  
  LWIP_MEM_ALLOC_UNPROTECT();  
  sys_mutex_unlock(&mem_mutex);  
  return NULL;  //申请失败

 由上边的源码分析可知,内存堆的分配只要是通过struct mem结构来形成一个双向的链表进行管理的,而管理整个内存链的过程中,如何区分已使用的内存和空闲的内存块呢? 于是引入used字段,因此,在这个内存链中,used字段将至关重要。还有一点大家需要明白,mem分配和memp分配有一个较大的差异,memp分配会将以使用的内存从空闲内存链中去除,而mem中,整个内存是一个完整的内存链,因此即使内存已经分配出去还是存在于系统的内存堆链中。

【3】内存的释放

内存的申请存在两种方式,那么必然对应着两种释放方式。

1、内存池分配方式的释放

void  
mem_free(void *rmem)  
{  
  struct memp_malloc_helper *hmem;  
 
  LWIP_ASSERT("rmem != NULL", (rmem != NULL));  
  LWIP_ASSERT("rmem == MEM_ALIGN(rmem)", (rmem == LWIP_MEM_ALIGN(rmem)));  
  
  /* get the original struct memp_malloc_helper */  
  hmem = (struct memp_malloc_helper*)(void*)((u8_t*)rmem - LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper)));  //找到分配的真正首地址
      
  LWIP_ASSERT("hmem != NULL", (hmem != NULL));  
  LWIP_ASSERT("hmem == MEM_ALIGN(hmem)", (hmem == LWIP_MEM_ALIGN(hmem)));  
  LWIP_ASSERT("hmem->poolnr < MEMP_MAX", (hmem->poolnr < MEMP_MAX));  
      
  /* and put it in the pool we saved earlier */  
  memp_free(hmem->poolnr, hmem);  //通过struct memp_malloc_helper结构得到pool类型,hmem得到首地址,从而进行释放

其实,对于内存池的申请比较简单,这里就不讲了,读者可以通过我们的上一篇关于memp的文章来学习,这里带过。

2、内存堆分配方式的释放

void  
mem_free(void *rmem)  
{  
  struct mem *mem;  
  LWIP_MEM_FREE_DECL_PROTECT();  
  
  if (rmem == NULL) { //合法性检查
    LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("mem_free(p == NULL) was called.\n"));  
   return;  
  }  
  LWIP_ASSERT("mem_free: sanity check alignment", (((mem_ptr_t)rmem) & (MEM_ALIGNMENT-1)) == 0);  
      
  LWIP_ASSERT("mem_free: legal memory", (u8_t *)rmem >= (u8_t *)ram &&  
    (u8_t *)rmem < (u8_t *)ram_end);  
      //合法性检查
      if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {  
       SYS_ARCH_DECL_PROTECT(lev);  
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory\n"));  
        /* protect mem stats from concurrent access */  
        SYS_ARCH_PROTECT(lev);  
        MEM_STATS_INC(illegal);  
        SYS_ARCH_UNPROTECT(lev);  
        return;  
  }  
  /* protect the heap from concurrent access */  
  LWIP_MEM_FREE_PROTECT();  
  /* Get the corresponding struct mem ... */  
  mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);  //获取真实的内存首地址
  /* ... which has to be in a used state ... */  
  LWIP_ASSERT("mem_free: mem->used", mem->used);  
  /* ... and is now unused. */  
  mem->used = 0;  //释放内存
      
  if (mem < lfree) { //ifree指针的更新 
    /* the newly freed struct is now the lowest */  
    lfree = mem;  
  }  
      
  MEM_STATS_DEC_USED(used, mem->next - (mem_size_t)(((u8_t *)mem - ram)));  
      
  /* finally, see if prev or next are free also */  
  plug_holes(mem);  //内存合并
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT  
  mem_free_count = 1;  
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */  
  LWIP_MEM_FREE_UNPROTECT();  

由上可以,内存堆的释放同样比较简单,主要是两个工作。

  1. 释放内存,及将used字段清0。
  2. 检查释放内存的前后,如果存在空闲内存将进行内存的合并。

当然,mem中还存在其他的一些功能函数,如void *mem_trim(void *rmem, mem_size_t newsize),进行内存的压缩等等。读者可根据兴趣自行阅读和分析。

最后在指出一点,就是当系统使用了默认的c分配函数,如malloc和free是,将存在如下的内存分配函数。

void *mem_calloc(mem_size_t count, mem_size_t size)  
{  
  void *p;  
  
  /* allocate 'count' objects of size 'size' */  
  p = mem_malloc(count * size);  
  if (p) {  
    /* zero the memory */  
    memset(p, 0, count * size);  
  }  
  return p;  
}  
其同样完成两个工作。

  1. 内存的分配
  2. 对分配的内存进行清0操作,maybe这样的清0操作将使得数据更安全。

#ifndef mem_free  
#define mem_free free  
#endif  
#ifndef mem_malloc  
#define mem_malloc malloc  
#endif  
#ifndef mem_calloc  
#define mem_calloc calloc  
#endif  
这是使用c系统函数分配的声明和定义,这里我就不一一讲解了,读者可以自行在mem.h函数中查找,这也就是模块化编程的最大优点,模块间相互区里,耦合性较低。

关于mem内存堆的相关问题的分析就到这里

LWIP之Mem原理分析相关推荐

  1. PCI Express解析——系列文章【5】:PCIe原理分析之——PCI Express 配置解析(BDF、BAR)、 MEM Read举例

    PCI Express解析--系列文章[5]:PCIe原理分析之--PCI Express 配置解析BDF.BAR. MEM Read举例 2.5 Memory Read举例 (1)事务层TLP包准备 ...

  2. Go Slice实现原理分析

    Go Slice实现原理分析 认识 Slice 一种可变长度的数 操作 make :创建Slice,可以提前分配内存, append:往Slice添加元素 package main import (& ...

  3. PCI Express解析——系列文章【2】:PCIe原理分析之——PCI Express线路基础

    PCI Express解析--系列文章[2]:PCIe原理分析之--PCI Express线路基础 前文我们了解了一些概述的基本PCIe总线的简单知识点,下面本文从一些基本原理做一些分析 2 PCIe ...

  4. Android手机一键Root原理分析(作者:非虫,文章来自:《黑客防线》2012年7月)

    之前几天都在做Android漏洞分析的项目,万幸发现了这篇文章.废话不多说,上文章! <Android手机一键Root原理分析> (作者:非虫,文章来自:<黑客防线>2012年 ...

  5. 嵌入式操作系统多任务调度原理分析与RUST参考实现

    操作系统多任务调度原理分析与RUST参考实现 作为一名在软件领域工程师,在职业生涯的尽头能有幸接触到一部分硬件产品是我莫大的荣幸.秉承我一贯刨根问底,不搞清楚问题本质不罢休的作风和态度,结合基本的计算 ...

  6. PCI Express解析——系列文章【4】:PCIe原理分析之——PCI Express系统模块、PCIe体系结构

    PCI Express解析--系列文章[4]:PCIe原理分析之--PCI Express系统模块.PCIe体系结构 2.3 PCI Express系统模块图 2.4 PCI Express体系结构 ...

  7. PCI Express解析——系列文章【3】:PCIe原理分析之——PCI Express拓扑结构

    PCI Express解析--系列文章[3]:PCIe原理分析之--PCI Express拓扑结构 前文我们了解了一些概述的基本PCIe线路基础包括简单的特性,如果需要详细可以逐点查看一下具体协议资料 ...

  8. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  9. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

最新文章

  1. OpenStack-Zun 使用
  2. Github上Fork部署应用程序
  3. JAVA微信开源项目(jeewx)百度BAE 部署文档
  4. SAP License:值字段更改注意事项
  5. anaconda怎么运行python程序_怎么用cmd运行python
  6. Wi-Fi 爆重大安全漏洞,Android、iOS、Windows 等所有无线设备都不安全了
  7. 20210627:力扣第247周周赛(上)
  8. 创业挑战杯获奖作品范例_挑战杯创业计划大赛金奖作品1——【挑战杯获奖作品】...
  9. 摄影测量前方交会编程实现
  10. leetcode总结无止境系列之链表
  11. 怎样推断server为虚拟机还是物理真机?
  12. OpenStack实践(十一):Instance Live Migrate and Evacuate
  13. 佛祖保佑永无BUG 神兽护体 代码注释(各种版本)
  14. 【P2P网络】DHT协议基础1:Kademlia翻译稿
  15. 博客中国2004中文非主流网站100强
  16. WIFI菠萝 strip-n-inject插件安装与使用
  17. SpringBoot集成alipay-easysdk
  18. 如何在计算机上注销一个用户登录,如何取消Microsoft账户登录电脑
  19. java和office二级证书用处_计算机二级证书有什么作用
  20. python通讯录管理系统 tk_通讯录管理系统

热门文章

  1. luogu P4752
  2. U盘插入电脑不显示解决办法
  3. CSS3的resize、渐变、多列设置
  4. 计算机学院教师老带新总结,教师老带新工作总结
  5. Mixly+点灯科技+小爱同学智能插座
  6. 地理实体三维重建技术,只读这一篇就够了
  7. 深聊性能测试,从入门到放弃之:通过这几点获取性能需求,BOSS再也不担心用户投诉了。
  8. MAC/Linux 压缩/解压缩命令大全整理 gzip / tar / zip
  9. 加工工艺(二)钣金加工、型材板材、加强筋、塑料产品材料
  10. 约瑟环问题 Java解法