LwIP源码分析(3):内存堆和内存池代码详解

news2024/11/26 18:52:38

文章目录

  • 1 内存堆
    • 1.1 mem_init
    • 1.2 mem_malloc
    • 1.3 mem_free
  • 2 内存池
    • 2.1 memp_init
    • 2.2 memp_malloc
    • 2.3 memp_free
  • 3 内存管理宏定义

在嵌入式系统中,内存池有助于快速有效地分配内存。LwIP提供了两个灵活的方式来管理和组织内存池的大小:内存堆和内存池。当然它还支持C库中的 mallocfree来申请和释放内存,但是这种分配方式可能会产生很多堆碎片,最后造成没有内存可以分配,而且运行地越久寻找新内存的时间可能也越长,这里不建议使用这种方式。下面通过代码来详细地看一下内存堆和内存池的实现

1 内存堆

来看看LwIP中实现的堆内存管理的实现:

1.1 mem_init

先来看看mem_init函数,相关结构体和宏定义写在前面:

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;
#if MEM_OVERFLOW_CHECK
  /** this keeps track of the user allocation size for guard checks */
  mem_size_t user_size;
#endif
};

#define MEM_ALIGNMENT 4
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))

/* 用户自定义的堆内存大小 */
#define MEM_SIZE (32 * 1024)
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U * SIZEOF_STRUCT_MEM));
#define LWIP_RAM_HEAP_POINTER ram_heap

static struct mem * ptr_to_mem(mem_size_t ptr)
{
  return (struct mem *)(void *)&ram[ptr];
}

static struct mem * LWIP_MEM_LFREE_VOLATILE lfree;
static u8_t *ram;
---------------------------------------------------------------
void mem_init(void)
{
    struct mem *mem;
	/* 检查struct mem是否是四字节对齐 */
    LWIP_ASSERT("Sanity check alignment",
    (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT - 1)) == 0);

    /* 将ram变量的指针指向上面声明的ram_heap数组,其大小为(MEM_SIZE+2*mem结构体大小) */
    ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);
    /* 上面ram_heap数组的大小多了两个mem结构体,分别填充在ram_heap的起始和结尾 */
    /* 初始化堆的起始 */
    mem = (struct mem *)(void *)ram;
    /* 下一个内存的偏移量为MEM_SIZE_ALIGNED,即结束 */
    mem->next = MEM_SIZE_ALIGNED;
    /* 前一个内存的偏移量 */
    mem->prev = 0;
    /* 当前内存没有被使用 */
    mem->used = 0;
    /* 初始化堆的结束:指向上一个内存的结束位置 */
    ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);
    /* 由于当前结构体为标记堆内存的结束,无实际大小,标记使用 */
    ram_end->used = 1;
    /* 标记下一个内存的偏移量:下一个还是自己表示到头了 */
    ram_end->next = MEM_SIZE_ALIGNED;
    /* 标记前一个内存的偏移量 */
    ram_end->prev = MEM_SIZE_ALIGNED;
    /* 合理性检验:一般在每次mem_free()后调用,确保堆链表结构体内内存的合法性和是否对齐 */
    MEM_SANITY();

    /* 将堆内存中第一块可用内存的指针赋值给lfree */
    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);
    }
}

初始化后ram_heap数组的内存分配如下所示:

在这里插入图片描述

1.2 mem_malloc

首先来看看内存分配的函数,它的原理是First Fit,即遍历空闲内存,找到第一个满足大小的堆内存进行分配,并将剩下的返回内存堆。

对于mem_malloc有三种方式,第一种为C库的malloc,一种为使用内存池(memory pool)方式,还有一种就是内存堆方式,这里对内存堆分配方式进行介绍(具体实现见代码注释):

  • 该函数中有一个宏定义:LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT,这是允许用户可以在中断或不允许等待信号量的上下文中释放pbuf,它的原理就是使用信号量和SYS_ARCH_PROTECT保护mem_malloc函数,使用SYS_ARCH_PROTECT保护mem_free函数。只有mem_mallocSYS_ARCH_UNPROTECTs后,mem_free才可以允许。

    • 很明显,这种方式要经常开关中断,如果在内存很少的嵌入式系统中,mem_malloc的速度会非常慢,一般都不会打开,所以这里不对相关宏定义的代码进行解析。
  • 函数中对于overflow检查的相关代码也将省略

void* mem_malloc(mem_size_t size_in)
{
    mem_size_t ptr, ptr2, size;
    struct mem *mem, *mem2;
    /* 声明一个unsigned long的变量lev用于同步 */
    LWIP_MEM_ALLOC_DECL_PROTECT();
    /* 分配的内存大小为0则直接返回 */
    if (size_in == 0)
    {
        return NULL;
    }

    /* 保证分配的内存是字节对齐的 */
    size = (mem_size_t)LWIP_MEM_ALIGN_SIZE(size_in);
    /* 分配的内存的大小必须比MIN_SIZE_ALIGNED(12)大:防止分配过多小内存造成内存碎片 */
    if (size < MIN_SIZE_ALIGNED)
    {
        /* every data block must be at least MIN_SIZE_ALIGNED long */
        size = MIN_SIZE_ALIGNED;
    }
    /* 如果size大于总的堆内存的大小或者小于待分配内存的大小,则返回NULL */
    if ((size > MEM_SIZE_ALIGNED) || (size < size_in))
    {
        return NULL;
    }

    /* 获得在mem_init中创建的互斥量,以并发地使用内存管理函数 */
    sys_mutex_lock(&mem_mutex);
    /* 在有OS的情况下:如果当前函数在中断中运行,则lev=basepri并设置优先级高于lev的中断才能打断;
       否则进入临界区 */
    LWIP_MEM_ALLOC_PROTECT();

    /* 从lfree指针开始用first fit方式寻找第一个足够大的内存 */
    for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size;
            ptr = ptr_to_mem(ptr)->next)
    {
        mem = ptr_to_mem(ptr);
        /* 如果该内存没有used且大小满足(size+内存头mem结构体)的大小 */
        if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size)
        {
            /* 如果该内存除去我们申请的内存后,剩余的大小还比(struct mem + 最小申请内存MIN_SIZE_ALIGNED)大
               则将剩下的内存返回内存堆中 */
            if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED))
            {
                ptr2 = (mem_size_t)(ptr + SIZEOF_STRUCT_MEM + size);
                LWIP_ASSERT("invalid next ptr",ptr2 != MEM_SIZE_ALIGNED);
                /* create mem2 struct */
                mem2 = ptr_to_mem(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)
                {
                    ptr_to_mem(mem2->next)->prev = ptr2;
                }
            }
            else
            {
                /* 剩下的内存不足以分配一个新的内存堆,只好浪费,标记整块内存used */
                mem->used = 1;
            }
            /* 如果分配的内存为第一块,则需要更新lfree指针(指向第一块空闲的堆的指针) */
            if (mem == lfree)
            {
                struct mem *cur = lfree;
                /* Find next free block after mem and update lowest free pointer */
                while (cur->used && cur != ram_end)
                {
                    cur = ptr_to_mem(cur->next);
                }
                lfree = cur;
                LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
            }
            /* 对应前面的protect,若在中断中则使能中断并清空basepri寄存器;
               若不在中断中,则退出临界区 */
            LWIP_MEM_ALLOC_UNPROTECT();
            /* 释放互斥锁 */
            sys_mutex_unlock(&mem_mutex);
            /* 返回申请的内存的地址,这里不打开完整性检查,即MEM_SANITY_OFFSET为0 */
            return (u8_t *)mem + SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET;
        }
    }
    /* 找不到足够大的内存,释放锁 */
    LWIP_MEM_ALLOC_UNPROTECT();
    sys_mutex_unlock(&mem_mutex);
    return NULL;
}

1.3 mem_free

mem_free函数也很好理解,具体见注释:

void mem_free(void *rmem)
{
  struct mem *mem;
  /* 和mem_malloc一样,声明一个lev变量 */
  LWIP_MEM_FREE_DECL_PROTECT();

  if (rmem == NULL) {
    return;
  }
  /* 由于分配的内存都是四字节对齐的,若不对齐则传入的参数错误 */
  if ((((mem_ptr_t)rmem) & (MEM_ALIGNMENT - 1)) != 0) {
    LWIP_MEM_ILLEGAL_FREE("mem_free: sanity check alignment");
    return;
  }

  /* 获取该段内存前面的结构体的内容 */
  mem = (struct mem *)(void *)((u8_t *)rmem - (SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET));
  /* 如果结构体的首地址不在整个内存堆的范围内则直接返回 */
  if ((u8_t *)mem < ram || (u8_t *)rmem + MIN_SIZE_ALIGNED > (u8_t *)ram_end) {
    return;
  }

  /* 痛mem_malloc中的LWIP_MEM_ALLOC_PROTECT() */
  LWIP_MEM_FREE_PROTECT();
  /* 如果mem->used为0,说明参数出错,直接返回 */
  if (!mem->used) {
    return;
  }
  /* 该函数检查mem->prev和mem->next的参数合法性 */
  if (!mem_link_valid(mem)) {
    LWIP_MEM_FREE_UNPROTECT();
    return;
  }

  /* 标记该mem为not used */
  mem->used = 0;
  /* 如果释放的mem为第一块内存,则更新lfree */
  if (mem < lfree) {
    /* the newly freed struct is now the lowest */
    lfree = mem;
  }

  /* 最后,如果释放的内存的prev和next有not used的话,则合并 */
  plug_holes(mem);
  LWIP_MEM_FREE_UNPROTECT();
}

2 内存池

在LwIP的数据段中保留了一个固定大小的静态内存,这段内存被分为多个内存池(pools),用来描述各种不同的数据结构,比如TCP、UDP、IP等报文的首部,它们的长度是固定的,这样每次分配和释放都不会产生内存碎片。比如有一个内存池用于TCP控制结构体struct tcp_pcb,另一个内存池用于UDP控制结构体struct udp_pcb。每个内存池都可以配置它可以容量的最大数量的数据结构,比如上面的TCP和UDP控制块的个数可以通过MEMP_NUM_TCP_PCBMEMP_NUM_UDP_PCB进行修改。

下面来对内存池的代码进行分析,代码中去掉了统计分析和overflow检查的相关代码。

2.1 memp_init

首先是内存池的初始化函数:

void memp_init(void)
{
  u16_t i;
  /* for every pool: */
  for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) {
    memp_init_pool(memp_pools[i]);
  }
}

可以看到内存池的初始化函数非常简单,就是对每一个定义的内存池进行初始化。现在来看看memp_pools是什么:

const struct memp_desc *const memp_pools[MEMP_MAX] = {
#define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name,
#include "lwip/priv/memp_std.h"
};

其中MEMP_MAX的定义如下:

typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
#include "lwip/priv/memp_std.h"
  MEMP_MAX
} memp_t;

可以看出来,在memp_std.h中定义了多个内存池,每个内存池的参数包括:名字(name)、个数(num)、大小(size)和描述(desc),下面来看一下这个文件:

#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]

#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
  LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
    \
  LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
    \
  static struct memp *memp_tab_ ## name; \
    \
  const struct memp_desc memp_ ## name = { \
    DECLARE_LWIP_MEMPOOL_DESC(desc) \
    LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
    LWIP_MEM_ALIGN_SIZE(size), \
    (num), \
    memp_memory_ ## name ## _base, \
    &memp_tab_ ## name \
  };
  
#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
------------
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
#endif /* LWIP_RAW */

#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB,        MEMP_NUM_UDP_PCB,         sizeof(struct udp_pcb),        "UDP_PCB")
#endif /* LWIP_UDP */

#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB,        MEMP_NUM_TCP_PCB,         sizeof(struct tcp_pcb),        "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN,  sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG,        MEMP_NUM_TCP_SEG,         sizeof(struct tcp_seg),        "TCP_SEG")
#endif /* LWIP_TCP */

#if LWIP_ALTCP && LWIP_TCP
LWIP_MEMPOOL(ALTCP_PCB,      MEMP_NUM_ALTCP_PCB,       sizeof(struct altcp_pcb),      "ALTCP_PCB")
#endif /* LWIP_ALTCP && LWIP_TCP */

#if LWIP_IPV4 && IP_REASSEMBLY
LWIP_MEMPOOL(REASSDATA,      MEMP_NUM_REASSDATA,       sizeof(struct ip_reassdata),   "REASSDATA")
#endif /* LWIP_IPV4 && IP_REASSEMBLY */
#if (IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF) || (LWIP_IPV6 && LWIP_IPV6_FRAG)
LWIP_MEMPOOL(FRAG_PBUF,      MEMP_NUM_FRAG_PBUF,       sizeof(struct pbuf_custom_ref),"FRAG_PBUF")
#endif /* IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF || (LWIP_IPV6 && LWIP_IPV6_FRAG) */

#if LWIP_NETCONN || LWIP_SOCKET
LWIP_MEMPOOL(NETBUF,         MEMP_NUM_NETBUF,          sizeof(struct netbuf),         "NETBUF")
LWIP_MEMPOOL(NETCONN,        MEMP_NUM_NETCONN,         sizeof(struct netconn),        "NETCONN")
#endif /* LWIP_NETCONN || LWIP_SOCKET */
  • 对于代码中#的使用参考文章:C语言 #和##的使用

可以看到一个宏定义LWIP_MEMPOOL就根据参数声明了一个内存堆数组,还有mempmemp_desc结构体。

现在回过头来看MEMP_MAX,前面的#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name就是暂时用一下文件中这个定义,将LWIP_MEMPOOL的宏定义再进行一层替换,作为enum的值的名称,所以MEMP_MAX就是内存堆的总个数。

memp_pools就是将在memp_std.h中用LWIP_MEMPOOL声明的各个内存池的memp_desc结构体的地址包含进来。

最后再回来看memp_init_pool函数:

struct memp {
  struct memp *next;
};

struct memp_desc {
  /** Element size */
  u16_t size;
  /** Number of elements */
  u16_t num;
  /** Base address */
  u8_t *base;
  /** First free element of each pool. Elements form a linked list. */
  struct memp **tab;
};
--------
void memp_init_pool(const struct memp_desc *desc)
{
  int i;
  struct memp *memp;

  *desc->tab = NULL;
  memp = (struct memp *)LWIP_MEM_ALIGN(desc->base);

  /* 初始化为0:如果MEMP_MEM_INIT宏定义为1的话 */
  memset(memp, 0, (size_t)desc->num * (MEMP_SIZE + desc->size));

  /* create a linked list of memp elements */
  for (i = 0; i < desc->num; ++i) {
    memp->next = *desc->tab;
    *desc->tab = memp;
    /* cast through void* to get rid of alignment warnings */
    memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size);
  }
}

其中base就是通过LWIP_MEMPOOL_DECLARE声明的第一项大小为num*(MEMP_SIZE+MEMP_ALIGN_SIZE(size))的数组。除了每个内存池的大小是固定的,它的原理和内存堆类似,内存池的大小包括内存池的实际存数据大小和内存池结构体struct memp的大小。
tab就是通过LWIP_MEMPOOL_DECLARE声明的struct *memp指针变量的地址,它表示内存池中第一个没有使用的pool。

所以这个函数就是将每个声明的内存池通过链表连接起来,其中desc->tab为每个内存池的基地址。

最后,根据memp_init_pool函数,memp_pools中的每个memp_desc初始化后的示意图如下:
在这里插入图片描述

2.2 memp_malloc

来看一下内存池分配函数:

void *memp_malloc(memp_t type)
{
	void*memp;
	LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);
	memp = do_memp_malloc_pool_fn(memp_pools[type], file, line);
	return memp;
}

参数type的定义如下:

typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
#include "lwip/priv/memp_std.h"
	MEMP_MAX
} memp_t;

memp_t就是根据LWIP_MEMPOOL定义的各个内存池的name而组成的枚举类型,我们就通过这个枚举类型来申请相应的内存池中的内存。

然后来看看do_memp_malloc_pool_fn函数:

static void* do_memp_malloc_pool(const struct memp_desc *desc)
{
	struct memp *memp;
	/* 与内存堆中的作用一样:为了执行关中断/进临界区等操作,这里不再解释 */
	SYS_ARCH_DECL_PROTECT(old_level);
	SYS_ARCH_PROTECT(old_level);
	/*  获取对于memp的tab */
	memp = *desc->tab;
	if (memp != NULL) {
		/* 指向下一个memp结构体 */
		*desc->tab = memp->next;
		SYS_ARCH_UNPROTECT(old_level);
		/* 将内存的首地址返回 */
		return ((u8_t *)memp + MEMP_SIZE);
	} else {
		SYS_ARCH_UNPROTECT(old_level);
	}
	
	return NULL;
}

2.3 memp_free

void memp_free(memp_t type, void *mem)
{
	if (mem == NULL) {
		return;
	}
	do_memp_free_pool(memp_pools[type], mem);
}

同样地来看看do_memp_free_pool函数:

static void do_memp_free_pool(const struct memp_desc *desc, void *mem)
{
	struct memp *memp;
	SYS_ARCH_DECL_PROTECT(old_level);
	/* 获得该内存前面的结构体的地址 */
	memp = (struct memp *)(void *)((u8_t *)mem - MEMP_SIZE);
	SYS_ARCH_PROTECT(old_level);
	/* 将该内存地址加入到desc->tab的最前面,表示空闲 */
	memp->next = *desc->tab;
	*desc->tab = memp;
	SYS_ARCH_UNPROTECT(old_level);
}

3 内存管理宏定义

(1)MEM_LIBC_MALLOC:使用C库中的mallocfree管理内存
(2)MEMP_MEM_MALLOC:使用内存堆方式来分配内存池的内存。上面内存池的分配中我把相关代码注释掉了,如果此宏打开,在分配和释放内存时会直接使用mem_mallocmem_free
(3)MEM_USE_POOLS:使用内存池的方式来分配内存堆的内存。同上。
若此宏打开,还需将宏MEMP_MEM_MALLOC关闭,并将MEMP_USE_CUSTOM_POOLS宏打开,表示创建一个lwippools.h文件,然后用LWIP_MALLOC_MEMPOOL来声明自己的内存。
下面给出一个lwippools.h的配置例子:

/* OPTIONAL: Pools to replace heap allocation
 * Optional: Pools can be used instead of the heap for mem_malloc. If
 * so, these should be defined here, in increasing order according to
 * the pool element size.
 *
 * LWIP_MALLOC_MEMPOOL(number_elements, element_size)
 */
#if MEM_USE_POOLS
LWIP_MALLOC_MEMPOOL_START
LWIP_MALLOC_MEMPOOL(100, 256)
LWIP_MALLOC_MEMPOOL(50, 512)
LWIP_MALLOC_MEMPOOL(20, 1024)
LWIP_MALLOC_MEMPOOL(20, 1536)
LWIP_MALLOC_MEMPOOL_END
#endif /* MEM_USE_POOLS */

/* Optional: Your custom pools can go here if you would like to use
 * lwIP's memory pools for anything else.
 */
LWIP_MEMPOOL(SYS_MBOX, 22, 100, "SYS_MBOX")
  • 除了声明内存池给内存堆分配外,可以在最后声明一个内存池供自己使用
  • 这种方式适合用在SRAM很大的情况下,以空间换取执行速度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92406.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑维修记录

记于2022年12月15日 今天把电脑修好了&#xff0c;总结这次经验&#xff1a; &#xff08;1&#xff09;无知者无畏&#xff0c;对任何事情都要抱有探索的精神&#xff0c;最遗憾的事情不是做错了&#xff0c;而是想做没去做 &#xff08;2&#xff09;将每次走错路的经历都…

Orcale数据表去重创建联合主键

分享一下最近遇到的一个问题&#xff0c;我们从一个数据表中将数据表中的数据同步到另一个数据库的表中&#xff0c;由于要同步的数据表中没有建主键&#xff0c;所以数据同步后发现同步的数据比原始数据表中的数据要多&#xff0c;有不少重复的数据。因此需要对数据表进行去重…

事业编招聘:市委社会工委北京市民政局事业单位公开招聘

市委社会工委市民政局所属事业单位 根据《北京市事业单位公开招聘工作人员实施办法》&#xff08;京人社专技发﹝2010﹞102号&#xff09;等文件精神&#xff0c;北京市委社会工委北京市民政局所属21家事业单位面向社会及应届毕业生公开招聘事业单位工作人员88名。现将具体情况…

【SpringBoot 2.x】定时任务 之- @Scheduled注解

一、概述 Scheduled注解是Spring Boot提供的用于定时任务控制的注解&#xff0c;主要用于控制任务在某个指定时间执行&#xff0c;或者每隔一段时间执行。注意需要 启动类加EnableScheduling实现类加Component方法上加ScheduledScheduled主要有以下几种配置执行时间的方式&…

Neural Discrete Representation Learning (VQ-VAE) 简介

目录VQ-VAE参考VQ-VAE VAE是一种生成模型。 Vector QuantisedVariational AutoEncoder (VQ-VAE)是VAE的变种&#xff0c;其隐含变量是离散的。离散的隐含变量对于自然语言&#xff0c;推理都比较有帮助。著名的DALL-E就使用了类似VQ-VAE的离散隐含变量来从文本生成图像&#x…

【Python】同一网络下,手机和电脑进行socket通信

同一网络下&#xff0c;手机和电脑进行socket通信 最近在学python网络编程&#xff0c;发现socket可以进行跨主机的进程通信&#xff0c;于是尝试用电脑作为服务端&#xff0c;手机作为客户端&#xff0c;来进行socket通信。 电脑端准备 1.电脑开启热点&#xff08;非必须&a…

[附源码]Python计算机毕业设计SSM基于vue的图书管理系统2022(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java递归实现多级Map集合合并(结合实际场景)

合并Map集合 合并Map集合有很多方法&#xff0c;例如Map自生提供的putAll()方法。但是这种方法只能合并一层的集合&#xff0c;如果是多层的呢&#xff1f; 场景 现在有一个yml配置文件&#xff0c;由于项目部署在多台服务器上&#xff0c;而且每台服务器上的配置有些许差异…

Ajax(四)

1.模板引擎 1.1 模板引擎的基本概念 1.2 什么是模板引擎 不需要再用字符串拼接 1. 3模板引擎的好处 1.4 art-template模板引擎 art-template 是一个简约、超快的模板引擎。 中文官网首页为 http://aui.github.io/art-template/zh-cn/index.html 1.4.1 art-template模板引擎的…

为什么Python现在这么火?

Python可以说是目前最火的网红编程语言&#xff0c;虽然它在近几年在逐渐流行起来&#xff0c;但其实它已经发展了近三十年。那么&#xff0c;为什么Python现在这么火呢&#xff1f;一方面人工智能和大数据的崛起带红了Python&#xff0c;另一方面无论是软件开发者还是非编程工…

抗肿瘤的靶向药物——艾美捷西妥昔单抗Cetuximab说明

近年来恶性肿瘤极大的治疗进展是靶向新药的开发与使用。表皮生长因子受体EGFR是一种具有酪氨酸激酶活性的跨膜受体&#xff0c;受表皮生长因子EGF和转化生长因子-α(TGF-α)的刺激。多种肿瘤细胞株过度表达EGFR&#xff0c;包括25%&#xff5e;80%的结直肠癌CRC细胞。 西妥昔单…

RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)

第四篇文章&#xff0c;来聊聊 Golang 生态中如何“遥控”浏览器&#xff0c;更简单、可靠的使用基于 CDP &#xff08;Chrome DevTools Protocol&#xff09;协议的浏览器作为容器&#xff0c;获取诸如微博、B 站 这类动态渲染内容信息&#xff0c;将它们转换为 RSS 订阅源。 …

【C语言进阶】不会处理字符串?一万三千五百字包会保姆级教程

目录 &#x1f618;前言&#x1f618;&#xff1a; 一、字符串处理函数介绍&#x1f92f;&#xff1a; 1.strlen 函数&#x1f94e;&#xff1a; 2.strcpy 函数⚾&#xff1a; 3.strcat 函数&#x1f3c0;&#xff1a; 4.strcmp 函数&#x1f3c8;&#xff1a; 5.strncpy 函数…

WSL_03 WSL2 从C盘迁移到D盘

文章目录1 动机1 查看虚拟机状态&#xff0c;并关闭要迁移的虚拟机2 迁移WSL22.1 出现的问题&#xff1a;已存在具有提供的名称的分发(已解决)3 设置启动时的默认用户&#xff0c;没有设置默认为root参考1 动机 WSL2默认安装在C盘中&#xff0c;win R运行中使用%localappdata…

python科学计算 之 Numpy库的使用详解

目录 一&#xff1a;Numpy简介 二&#xff1a;ndarray的创建 三&#xff1a;ndarray的属性 四&#xff1a;切片 索引 五&#xff1a;数组的轴(二维数组) 六&#xff1a;二维数组 切片索引 七&#xff1a;高级索引 八&#xff1a;Numpy广播 九&#xff1a;ufunc函数 算…

使用pypy来提升你的python项目性能

一、PyPy介绍 PyPy是用Python实现的Python解释器的动态编译器&#xff0c;是Armin Rigo开发的产品&#xff0c;能够提升我们python项目的运行速度。PyPy 是利用即时编译的 Python 的替代实现。背后的原理是 PyPy 开始时就像一个解释器&#xff0c;直接从源文件运行我们的 Pyth…

Revit二次开发小技巧(十五)构件的最小矩形外轮廓

前言&#xff1a;我们会经常遇到需要计算一个构件的最小外轮廓&#xff0c;一般直接取BoundingBox只有最大和最小值坐标&#xff0c;也是基于x-y坐标系下的。往往不是最小的矩形&#xff0c;所以分享下面的算法来计算最小的外轮廓&#xff0c;条件为法向量是指向Z轴的&#xff…

mqtt服务器搭建与qt下的mqtt客户端实现

一、mqtt介绍 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的&#xff0c;这些特点使它适用范围非常广泛。在很多情…

前端基础(九)_CSS的三大特征

CSS的三大特征 1、层叠性 1.样式冲突&#xff0c;遵循就近原则 2.样式不冲突&#xff0c;不会层叠&#xff0c;会叠加 1.1.样式冲突&#xff0c;遵循就近原则例子&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UT…

[附源码]Nodejs计算机毕业设计基于的服装商城系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…