带你用纯C实现一个内存池(图文结合)
为什么要用内存池
为什么要用内存池?首先,在7 * 24h的服务器中如果不使用内存池,而使用malloc和free,那么就非常容易产生内存碎片,早晚都会申请内存失败;并且在比较复杂的代码或者继承的屎山中,非常容易出现内存泄漏导致mmo的问题。
为了解决这两个问题,内存池就应运而生了。内存池预先分配一大块内存来做一个内存池,业务中的内存分配和释放都由这个内存池来管理,内存池内的内存不足时其内部会自己申请。所以内存碎片的问题就交由内存池的算法来优化,而内存泄漏的问题只需要遵守内存池提供的api,就非常容易避免内存泄漏了。
即使出现了内存泄漏,排查的思路也很清晰。1.检查是不是内存池的问题;2.如果不是内存池的问题,就检查是不是第三方库的内存泄漏。
内存池的使用场景
- 全局内存池
- 一个连接一个内存池(本文实现这个场景的内存池)
设计一个内存池
总体介绍
由于本文是一个连接一个内存池,所以后续介绍和代码都是以4k为分界线,大于4k的我们认为是大块内存;小于4k的我们认为是小块内存。并且注意这里的4k,并不是严格遵照4096,而是在描述上,用4k比较好描述。
在真正使用内存之前,内存池提前分配一定数量且大小相等的内存块以作备用,当真正被用户调用api分配内存的时候,直接从内存块中获取内存(指小块内存),当内存块不够用了,再有内存池取申请新的内存块。而如果是需要大块内存,则内存池直接申请大块内存再返回给用户。
内存池:就是将这些提前申请的内存块组织管理起来的数据结构,内存池实现原理主要分为分配,回收,扩容三部分。
内存池原理之小块内存:分配=> 内存池预申请一块4k的内存块,这里称为block,即block=4k内存块。当用户向内存池申请内存size小于4k时,内存池从block的空间中划分出去size空间,当再有新申请时,再划分出去。扩容=> 直到block中的剩余空间不足以分配size大小,那么此时内存池会再次申请一块block,再从新的block中划分size空间给用户。回收=> 每一次申请小内存,都会在对应的block中引用计数加1,每一次释放小内存时,都会在block中引用计数减1,只有当引用计数为零的时候,才会回收block使他重新成为空闲空间,以便重复利用空间。这样,内存池避免频繁向内核申请/释放内存,从而提高系统性能。
内存池原理之大块内存:分配=> 因为大块内存是大于4k的,所以内存池不预先申请内存,也就是用户申请的时候,内存池再申请内存,然后返回给用户。扩容=> 大块内存不存在扩容。回收=> 对于大块内存来说,回收就直接free掉即可。
上面理论讲完了,下面来介绍如何管理小块内存和大块内存。
小块内存的分配与管理
在创建内存池的时候,会预先申请一块4k的内存,并且在起始处将pool的结构体和node的结构体放进去,从last开始一直到end都是空闲内存,<last , end >中间的区域就用来存储小块内存。每一次mp_malloc,就将last指针后移,直到 e n d − l a s t < s i z e end - last < size end−last<size 时,进行扩容,将新block的last后移即可。
初始状态
分配内存
扩容
相关视频推荐
200行代码实现slab,开启内存池的内存管理(准备linux环境)
线程池、内存池、异步请求池、数据库连接池、无锁队列的ringbuffer,提升程序性能必备技术
5种内存泄漏检测方式,让你重新理解C++内存管理
免费学习地址:C/C++Linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
大块内存的分配与管理
对于大块内存,前面已经说了,用户申请的时候,内存池才申请
申请一块大内存
再申请一块大内存
内存池代码实现
向外提供的api
- mp_create_pool:创建一个线程池,其核心是创建struct mp_pool_s这个结构体,并申请4k内存,将各个指针指向上文初始状态的图一样。
- mp_destroy_pool:销毁内存池,遍历小块结构体和大块结构体,进行free释放内存
- mp_malloc:提供给用户申请内存的api
- mp_calloc:通过mp_malloc申请内存后置零,相当于calloc
- mp_free:释放由mp_malloc返回的内存
- mp_reset_pool:将block的last置为初始状态,销毁所有大块内存
- monitor_mp_poll:监控内存池状态
struct mp_pool_s *mp_create_pool(size_t size);void mp_destroy_pool(struct mp_pool_s *pool);void *mp_malloc(struct mp_pool_s *pool, size_t size);void *mp_calloc(struct mp_pool_s *pool, size_t size);void mp_free(struct mp_pool_s *pool, void *p);void mp_reset_pool(struct mp_pool_s *pool);void monitor_mp_poll(struct mp_pool_s *pool, char *tk);
相关结构体的定义
mp_pool_s 就是整个内存池的管理结构,我们做的内存池是一个连接一个内存池,所以对于整个程序而言,内存池对象是有很多个的。
可能读者会有疑问,有了head,为什么还有current,是因为如果一个block剩余空间小于size超过一定次数后,将current指向下一个block,这样就加快内存分配效率,减少遍历次数。
//每4k一block结点
struct mp_node_s {unsigned char *end;//块的结尾unsigned char *last;//使用到哪了struct mp_node_s *next;//链表int quote;//引用计数int failed;//失效次数
};struct mp_large_s {struct mp_large_s *next;//链表int size;//alloc的大小void *alloc;//大块内存的起始地址
};struct mp_pool_s {struct mp_large_s *large;struct mp_node_s *head;struct mp_node_s *current;
};
内存对齐
访问速度是内存对齐的原因之一,另外一个原因是某些平台(arm)不支持未内存对齐的访问
在4k里面划分内存,那么必然有很多地方是不对齐的,所以这里提供两个内存对齐的函数。那么为什么要内存对齐呢?其一:提高访问速度;其二:某些平台arm不支持未对其的内存访问,会出错。
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
创建与销毁内存池
创建一个线程池,其核心是创建struct mp_pool_s这个结构体,并申请4k内存,将各个指针指向上文初始状态的图一样。
销毁内存池,遍历小块结构体和大块结构体,进行free释放内存。
//创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {struct mp_pool_s *pool;if (size < PAGE_SIZE || size % PAGE_SIZE != 0) {size = PAGE_SIZE;}//分配4k以上不用malloc,用posix_memalign/*int posix_memalign (void **memptr, size_t alignment, size_t size);*/int ret = posix_memalign((void **) &pool, MP_ALIGNMENT, size); //4K + mp_pool_sif (ret) {return NULL;}pool->large = NULL;pool->current = pool->head = (unsigned char *) pool + sizeof(struct mp_pool_s);pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);pool->head->end = (unsigned char *) pool + PAGE_SIZE;pool->head->failed = 0;return pool;
}//销毁内存池
void mp_destroy_pool(struct mp_pool_s *pool) {struct mp_large_s *large;for (large = pool->large; large; large = large->next) {if (large->alloc) {free(large->alloc);}}struct mp_node_s *cur, *next;cur = pool->head->next;while (cur) {next = cur->next;free(cur);cur = next;}free(pool);
}
提供给用户的内存申请api
申请的内存以size做区分,如果大于4k就分配大块内存,小于4k就去block里面划分。
//分配内存
void *mp_malloc(struct mp_pool_s *pool, size_t size) {if (size <= 0) {return NULL;}if (size > PAGE_SIZE - sizeof(struct mp_node_s)) {//largereturn mp_malloc_large(pool, size);}else {//smallunsigned char *mem_addr = NULL;struct mp_node_s *cur = NULL;cur = pool->current;while (cur) {mem_addr = mp_align_ptr(cur->last, MP_ALIGNMENT);if (cur->end - mem_addr >= size) {cur->quote++;//引用+1cur->last = mem_addr + size;return mem_addr;}else {cur = cur->next;}}return mp_malloc_block(pool, size);// open new space}
}
void *mp_calloc(struct mp_pool_s *pool, size_t size) {void *mem_addr = mp_malloc(pool, size);if (mem_addr) {memset(mem_addr, 0, size);}return mem_addr;
}
小块内存block扩容
所有的block都 e n d − l a s t < s i z e end - last < size end−last<size 时,进行扩容,将新block的last后移即可。
//new block 4k
void *mp_malloc_block(struct mp_pool_s *pool, size_t size) {unsigned char *block;int ret = posix_memalign((void **) &block, MP_ALIGNMENT, PAGE_SIZE); //4Kif (ret) {return NULL;}struct mp_node_s *new_node = (struct mp_node_s *) block;new_node->end = block + PAGE_SIZE;new_node->next = NULL;unsigned char *ret_addr = mp_align_ptr(block + sizeof(struct mp_node_s), MP_ALIGNMENT);new_node->last = ret_addr + size;new_node->quote++;struct mp_node_s *current = pool->current;struct mp_node_s *cur = NULL;for (cur = current; cur->next; cur = cur->next) {if (cur->failed++ > 4) {current = cur->next;}}//now cur = last nodecur->next = new_node;pool->current = current;return ret_addr;
}
分配大块内存
//size>4k
void *mp_malloc_large(struct mp_pool_s *pool, size_t size) {unsigned char *big_addr;int ret = posix_memalign((void **) &big_addr, MP_ALIGNMENT, size); //sizeif (ret) {return NULL;}struct mp_large_s *large;//released struct large resumeint n = 0;for (large = pool->large; large; large = large->next) {if (large->alloc == NULL) {large->size = size;large->alloc = big_addr;return big_addr;}if (n++ > 3) {break;// 为了避免过多的遍历,限制次数}}large = mp_malloc(pool, sizeof(struct mp_large_s));if (large == NULL) {free(big_addr);return NULL;}large->size = size;large->alloc = big_addr;large->next = pool->large;pool->large = large;return big_addr;
}
释放内存
如果是大块内存,找到之后直接释放;如果是小块内存,将引用计数减1,如果引用计数为0则重置last。
//释放内存
void mp_free(struct mp_pool_s *pool, void *p) {struct mp_large_s *large;for (large = pool->large; large; large = large->next) {//大块if (p == large->alloc) {free(large->alloc);large->size = 0;large->alloc = NULL;return;}}//小块 引用-1struct mp_node_s *cur = NULL;for (cur = pool->head; cur; cur = cur->next) {
// printf("cur:%p p:%p end:%p\n", (unsigned char *) cur, (unsigned char *) p, (unsigned char *) cur->end);if ((unsigned char *) cur <= (unsigned char *) p && (unsigned char *) p <= (unsigned char *) cur->end) {cur->quote--;if (cur->quote == 0) {if (cur == pool->head) {pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);}else {cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);}cur->failed = 0;pool->current = pool->head;}return;}}
}
内存池测试
//
// Created by 68725 on 2022/7/26.
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#define PAGE_SIZE 4096
#define MP_ALIGNMENT 16
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))//每4k一block结点
struct mp_node_s {unsigned char *end;//块的结尾unsigned char *last;//使用到哪了struct mp_node_s *next;//链表int quote;//引用计数int failed;//失效次数
};struct mp_large_s {struct mp_large_s *next;//链表int size;//alloc的大小void *alloc;//大块内存的起始地址
};struct mp_pool_s {struct mp_large_s *large;struct mp_node_s *head;struct mp_node_s *current;
};struct mp_pool_s *mp_create_pool(size_t size);void mp_destroy_pool(struct mp_pool_s *pool);void *mp_malloc(struct mp_pool_s *pool, size_t size);void *mp_calloc(struct mp_pool_s *pool, size_t size);void mp_free(struct mp_pool_s *pool, void *p);void mp_reset_pool(struct mp_pool_s *pool);void monitor_mp_poll(struct mp_pool_s *pool, char *tk);void mp_reset_pool(struct mp_pool_s *pool) {struct mp_node_s *cur;struct mp_large_s *large;for (large = pool->large; large; large = large->next) {if (large->alloc) {free(large->alloc);}}pool->large = NULL;pool->current = pool->head;for (cur = pool->head; cur; cur = cur->next) {cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);cur->failed = 0;cur->quote = 0;}
}//创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {struct mp_pool_s *pool;if (size < PAGE_SIZE || size % PAGE_SIZE != 0) {size = PAGE_SIZE;}//分配4k以上不用malloc,用posix_memalign/*int posix_memalign (void **memptr, size_t alignment, size_t size);*/int ret = posix_memalign((void **) &pool, MP_ALIGNMENT, size); //4K + mp_pool_sif (ret) {return NULL;}pool->large = NULL;pool->current = pool->head = (unsigned char *) pool + sizeof(struct mp_pool_s);pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);pool->head->end = (unsigned char *) pool + PAGE_SIZE;pool->head->failed = 0;return pool;
}//销毁内存池
void mp_destroy_pool(struct mp_pool_s *pool) {struct mp_large_s *large;for (large = pool->large; large; large = large->next) {if (large->alloc) {free(large->alloc);}}struct mp_node_s *cur, *next;cur = pool->head->next;while (cur) {next = cur->next;free(cur);cur = next;}free(pool);
}//size>4k
void *mp_malloc_large(struct mp_pool_s *pool, size_t size) {unsigned char *big_addr;int ret = posix_memalign((void **) &big_addr, MP_ALIGNMENT, size); //sizeif (ret) {return NULL;}struct mp_large_s *large;//released struct large resumeint n = 0;for (large = pool->large; large; large = large->next) {if (large->alloc == NULL) {large->size = size;large->alloc = big_addr;return big_addr;}if (n++ > 3) {break;// 为了避免过多的遍历,限制次数}}large = mp_malloc(pool, sizeof(struct mp_large_s));if (large == NULL) {free(big_addr);return NULL;}large->size = size;large->alloc = big_addr;large->next = pool->large;pool->large = large;return big_addr;
}//new block 4k
void *mp_malloc_block(struct mp_pool_s *pool, size_t size) {unsigned char *block;int ret = posix_memalign((void **) &block, MP_ALIGNMENT, PAGE_SIZE); //4Kif (ret) {return NULL;}struct mp_node_s *new_node = (struct mp_node_s *) block;new_node->end = block + PAGE_SIZE;new_node->next = NULL;unsigned char *ret_addr = mp_align_ptr(block + sizeof(struct mp_node_s), MP_ALIGNMENT);new_node->last = ret_addr + size;new_node->quote++;struct mp_node_s *current = pool->current;struct mp_node_s *cur = NULL;for (cur = current; cur->next; cur = cur->next) {if (cur->failed++ > 4) {current = cur->next;}}//now cur = last nodecur->next = new_node;pool->current = current;return ret_addr;
}//分配内存
void *mp_malloc(struct mp_pool_s *pool, size_t size) {if (size <= 0) {return NULL;}if (size > PAGE_SIZE - sizeof(struct mp_node_s)) {//largereturn mp_malloc_large(pool, size);}else {//smallunsigned char *mem_addr = NULL;struct mp_node_s *cur = NULL;cur = pool->current;while (cur) {mem_addr = mp_align_ptr(cur->last, MP_ALIGNMENT);if (cur->end - mem_addr >= size) {cur->quote++;//引用+1cur->last = mem_addr + size;return mem_addr;}else {cur = cur->next;}}return mp_malloc_block(pool, size);// open new space}
}void *mp_calloc(struct mp_pool_s *pool, size_t size) {void *mem_addr = mp_malloc(pool, size);if (mem_addr) {memset(mem_addr, 0, size);}return mem_addr;
}//释放内存
void mp_free(struct mp_pool_s *pool, void *p) {struct mp_large_s *large;for (large = pool->large; large; large = large->next) {//大块if (p == large->alloc) {free(large->alloc);large->size = 0;large->alloc = NULL;return;}}//小块 引用-1struct mp_node_s *cur = NULL;for (cur = pool->head; cur; cur = cur->next) {
// printf("cur:%p p:%p end:%p\n", (unsigned char *) cur, (unsigned char *) p, (unsigned char *) cur->end);if ((unsigned char *) cur <= (unsigned char *) p && (unsigned char *) p <= (unsigned char *) cur->end) {cur->quote--;if (cur->quote == 0) {if (cur == pool->head) {pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);}else {cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);}cur->failed = 0;pool->current = pool->head;}return;}}
}void monitor_mp_poll(struct mp_pool_s *pool, char *tk) {printf("\r\n\r\n------start monitor poll------%s\r\n\r\n", tk);struct mp_node_s *head = NULL;int i = 0;for (head = pool->head; head; head = head->next) {i++;if (pool->current == head) {printf("current==>第%d块\n", i);}if (i == 1) {printf("第%02d块small block 已使用:%4ld 剩余空间:%4ld 引用:%4d failed:%4d\n", i,(unsigned char *) head->last - (unsigned char *) pool,head->end - head->last, head->quote, head->failed);}else {printf("第%02d块small block 已使用:%4ld 剩余空间:%4ld 引用:%4d failed:%4d\n", i,(unsigned char *) head->last - (unsigned char *) head,head->end - head->last, head->quote, head->failed);}}struct mp_large_s *large;i = 0;for (large = pool->large; large; large = large->next) {i++;if (large->alloc != NULL) {printf("第%d块large block size=%d\n", i, large->size);}}printf("\r\n\r\n------stop monitor poll------\r\n\r\n");
}int main() {struct mp_pool_s *p = mp_create_pool(PAGE_SIZE);monitor_mp_poll(p, "create memory pool");
#if 0printf("mp_align(5, %d): %d, mp_align(17, %d): %d\n", MP_ALIGNMENT, mp_align(5, MP_ALIGNMENT), MP_ALIGNMENT,mp_align(17, MP_ALIGNMENT));printf("mp_align_ptr(p->current, %d): %p, p->current: %p\n", MP_ALIGNMENT, mp_align_ptr(p->current, MP_ALIGNMENT),p->current);
#endifvoid *mp[30];int i;for (i = 0; i < 30; i++) {mp[i] = mp_malloc(p, 512);}monitor_mp_poll(p, "申请512字节30个");for (i = 0; i < 30; i++) {mp_free(p, mp[i]);}monitor_mp_poll(p, "销毁512字节30个");int j;for (i = 0; i < 50; i++) {char *pp = mp_calloc(p, 32);for (j = 0; j < 32; j++) {if (pp[j]) {printf("calloc wrong\n");exit(-1);}}}monitor_mp_poll(p, "申请32字节50个");for (i = 0; i < 50; i++) {char *pp = mp_malloc(p, 3);}monitor_mp_poll(p, "申请3字节50个");void *pp[10];for (i = 0; i < 10; i++) {pp[i] = mp_malloc(p, 5120);}monitor_mp_poll(p, "申请大内存5120字节10个");for (i = 0; i < 10; i++) {mp_free(p, pp[i]);}monitor_mp_poll(p, "销毁大内存5120字节10个");mp_reset_pool(p);monitor_mp_poll(p, "reset pool");for (i = 0; i < 100; i++) {void *s = mp_malloc(p, 256);}monitor_mp_poll(p, "申请256字节100个");mp_destroy_pool(p);return 0;
}
nginx内存池对比分析
相关结构体定义对比
创建内存池对比
内存申请对比
相关文章:

带你用纯C实现一个内存池(图文结合)
为什么要用内存池 为什么要用内存池?首先,在7 * 24h的服务器中如果不使用内存池,而使用malloc和free,那么就非常容易产生内存碎片,早晚都会申请内存失败;并且在比较复杂的代码或者继承的屎山中,…...

ChatGPT使用案例之图像生成
ChatGPT使用案例之图像生成 这里一节我们介绍一下ChatGPT的图像生成,这里我们使用代码来完成,也就是通过API 来完成,因为ChatGPT 本身是不能生成图片的,言外之意我们图片生成是ChatGPT通过其他方式生成的 Images API提供了三种与…...
蚁群算法优化旅行问题
%%%%%%%%%%%%蚁群算法解决 TSP 问题%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%初始化%%%%%%%%%%%%%%%%%%% clear all; %清除所有变量 close all; %清图 clc; %清屏 m 50; %蚂蚁个数 Alpha 1; %信息素重要程度参数 Beta 5; %启发式因子重要程度参数 Rho 0.1; %信息素蒸发系数 G 20…...

树数据结构
什么是树数据结构? 树数据结构是一种层次结构,用于以易于导航和搜索的方式表示和组织数据。它是由边连接的节点集合,节点之间具有层次关系。树的最顶端的节点称为根,它下面的节点称为子节点。每个节点可以有多个子节点,…...

Spring Boot整合Redis并提供多种实际场景的应用
Spring Boot整合Redis并提供多种实际场景的应用1. 整合Redis2. 场景应用2.1 缓存2.2 分布式锁2.3 计数器2.4 发布/订阅3. 总结Spring Boot是一个快速构建基于Spring框架的应用程序的工具,它提供了大量的自动化配置选项,可以轻松地集成各种不同的技术。Re…...

VR全景图片,助力VR全景制作,720全景效果图
VR全景图片是指通过全景相机或多相机组合拍摄全景画面,并进行拼接处理生成全景图像的过程。VR全景图片的应用范围广泛,包括旅游和景区、房地产、汽车、艺术和文化、电影和娱乐等领域。本文将详细介绍VR全景图片的类型、应用场景、市场前景和发展趋势。 一…...
Kali Linux20款重要软件
Kali Linux 是一个流行的网络安全测试平台,它包含了大量的工具和应用程序,以下是其中20款最常用的软件和工具: Metasploit:Metasploit 是一个广泛使用的漏洞评估工具,可以帮助安全专业人员测试系统中的漏洞。Aircrack…...
C语言测试五
windows是什么类型的系统(实时还是分时)?有什么区别? 分时操作系统。如果在单核的情况下,分时操作系统多个进程共用一个单核,该单核会将其执行时间分成相应的时间片,每个进程占用一定的时间片&a…...

【微服务~原始真解】Spring Cloud —— 访问数据库整合Druid数据源
🔎这里是【秒懂云原生】,关注我学习云原生不迷路 👍如果对你有帮助,给博主一个免费的点赞以示鼓励 欢迎各位🔎点赞👍评论收藏⭐️ 👀专栏介绍 【秒懂云原生】 目前主要更新微服务,…...

前端入门必刷题,经典算法—两数之和
优美的前⾔ 年轻的码农哟~ 你是不是⼀直在思考⾃我提升的问题~ 思来想去,决定从算法抓起(单押)~ 拿起⼜放下,经历过多少次放弃(单押 ✖ 2)~ 决定了!这次让我来帮你梳理(单押 ✖ 3&a…...

‘海外/国外‘地区微博签到shu据(正题在第二部分)
最近失眠,研究了项关于weibo爬虫的新功能,种种原因,大家可跳过第一部分的引用直接看第二部分。 内容来源:健康中国、生命时报、央视等 失眠标准一:3个“30分钟” ● 入睡困难,从躺下想睡到睡着间隔…...

Springboot——SB整合Mybatis的CURD(基于注解进行开发)
此处是根据需求实现基本操作 上面这里涉及到了条件分页查询,还有增加和批量删除员工信息,右边编辑就是先查询后更新操作,叫做查询回显,然后在原有基础上进行更新 环境准备 在下面的入门案例的整体环境下把数据库表换成empSpring…...

现在大专生转IT可行吗?
当然可行的。 大专也是人,为什么不可以选择喜欢的专业学习,现在大学生遍地都是,学历已经不是限制你发展的因素了。有的人就是不擅长理论学习,更喜欢技术。IT也只是一个普普通通的技术行业,跟其他技术行业一样…...

XC7A50T-1CSG324I、XC7A50T-2CSG324I Artix-7 FPGA可编程门阵列
Artix-7 FPGA能够在多个方面实现更高的性价比,这些方面包括逻辑、信号处理、嵌入式内存、LVDS I/O、内存接口,以及收发器。MicroBlaze CPU针对Xilinx FPGA进行了优化,是一种可高度配置的32位RISC处理器,可为微控制器、实时处理器和…...
linux安装图片处理软件ImageMagick
下载地址: wget https://download.imagemagick.org/archive/ImageMagick-7.1.1-4.tar.gz 或者 wget --no-check-certificate https://download.imagemagick.org/archive/ImageMagick-7.1.1-4.tar.gz 安装命令: tar -zxvf ImageMagick-7.1.1-4.tar.…...

【Java基础】JavaCore核心-反射技术
文章目录1.什么是反射技术2.反射-获取类对象方式3.反射-获取声明构造器4.反射-对象创建实战5.反射-方法和属性实战6.反射-属性值操作实战7.反射-invoke运行类方法1.什么是反射技术 Java的反射(reflection)机制是指在程序的运行状态中 可以构造任意一个类…...
AWGN后验估计下的均值与协方差关系(向量和标量形式)
文章目录AWGN信道向量模型后验均值与协方差的关系从实数域拓展到复数域小结AWGN信道向量模型 考虑一个随机向量x∼pX(x)\boldsymbol x \sim p_{\boldsymbol X}(\boldsymbol x)x∼pX(x),信道模型为 qxv,v∼N(0,Σ)\boldsymbol q \boldsymbol x \boldsymbol v, \…...
Linux常用命令之文件搜索命令
1、常用搜索-find 命令find英文原意find所在路径/bin/find执行权限所有用户功能描述文件搜索语法find [搜索范围] [搜索条件] (默认准确搜索)范例find /etc -name init?? 常用的搜索条件的选项包括: -name:按照文件名进行匹配查找,例&…...
ChatGPT给软件测试行业带来的可能
软件测试在软件开发过程中扮演着至关重要的角色,因为它可以确保软件的质量和可靠性。而随着人工智能技术的不断发展,ChatGPT作为一个强大的自然语言处理工具,可以在软件测试中发挥出许多重要的作用。本文将介绍ChatGPT在软件测试应用中带来的…...
Cadence Allegro 导出Properties on Nets Report报告详解
⏪《上一篇》 🏡《上级目录》 ⏩《下一篇》 目录 1,概述2,Properties on Nets Report作用3,Properties on Nets Report示例4,Properties on Nets Report导出方法4.1,方法14.2,方法2B站关注“硬小二”浏览更多演示视频...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...