RT_Thread內核源碼分析(五)——內存管理@小堆內存管理算法

目錄

1、內存堆控制

? ? ? ? ?1.1 內存堆控制器? ?

1.2 內存塊節點

1.3 內存堆管理

2、內存堆初始化

2.1 初始化接口

2.2 初始化示例

2.3 源碼分析

3、內存堆操作

3.1 內存塊申請

3.1.1?相關接口

3.1.2 原理分析

3.1.3 示例分析

3.1.4 代碼分析

3.2 內存塊伸縮

3.2.1 相關接口

3.2.2 原理分析

3.2.3 示例分析

3.2.4 源碼分析

3.3 內存快釋放

3.3.1 相關接口

3.3.2 原理分析

3.3.3 示例分析

3.3.4 源碼分析


???本章講解RT_Thread Nano實時操作系統的動態內存(內存堆)部分,RT-Thread提供三種動態內存管理算法:小堆內存管理算法、堆內存管理方法、SLAB內存管理算法。小堆內存管理模塊主要針對系統資源比較少,一般用于小于2M內存空間的系統;堆內存可以應用于多塊不連續的內存堆,SLAB內存管理模塊則主要是在系統資源比較豐富時,提供了一種近似多內存池管理算法的快速算法。三種內存管理模塊在系統運行時只能選擇其中之一或者完全不使用動態堆內存管理器。

? ? ? ? 三種管理模塊提供的API接口完全相同,小堆內存管理算法接口在mem.c,堆內存管理方法在memheap.c文件中定義,SLAB內存管理模塊在slab.c文件中定義。

內存管理方法適用場景宏定義設置接口定義位置
小堆內存小于2M內存空間

RT_USING_HEAP

RT_USING_SMALL_MEM

mem.c
堆內存多塊不連續的內存堆

RT_USING_MEMHEAP_AS_HEAP

RT_USING_MEMTRACE

memheap.c
SLAB系統資源比較豐富

RT_USING_HEAP

RT_USING_SLAB

slab.c

? ? ? ? 本章主要講解靜態小堆內存管理算法,其特點是內存利用率高,適用于小存儲空間。

本章基于RT_Thread Nano V3.1.5版本分析

1、內存堆控制

? 1.1 內存堆控制器? ?

????????內存堆控制器由一些全局變量定義,如下所示:

static rt_uint8_t *heap_ptr;         // 內存堆首地址
static struct heap_mem *heap_end;    // 內存堆尾部節點
static struct heap_mem *lfree;       // 指向最低地址空閑塊節點,用于管理空閑內存塊
static struct rt_semaphore heap_sem; // 內存信號,用于臨界資源權限保護
static rt_size_t mem_size_aligned;   // 對齊后小堆內存總大小
static rt_size_t used_mem;           // 記錄已使用字節數
static rt_size_t max_mem;            // 記錄已使用字節數的最大值

1.2 內存塊節點

?????????內存堆鏈表節點如下所示,宏定義ARCH_CPU_64BIT表示系統內核為64位。

struct heap_mem
{rt_uint16_t magic;     /*內存塊標志字,默認0x1ea0*/rt_uint16_t used;      /*=0表示內存塊空閑,=1表示內存塊被申請*/#ifdef ARCH_CPU_64BITrt_uint32_t resv;#endifrt_size_t next, prev;  /*next表示上個內存塊節點的相對位置,prev表示下個內存塊節點的相對位置*//*內存監視使能*/#ifdef RT_USING_MEMTRACE#ifdef ARCH_CPU_64BITrt_uint8_t thread[8];   /* 記錄線程名稱*/#elsert_uint8_t thread[4];   /* 記錄線程名稱*/#endif#endif
};

1.3 內存堆管理

? ? ? ? 小堆內存中通過一個雙向鏈表管理內存塊,每個內存塊首定義一個節點,節點按照地址從小到大順序雙向連接。

? ? ? ? 內存節點參數next, prev非指針變量,而是表示節點地址相對于堆首地址heap_ptr的偏移,通過偏移定位和連接上下內存塊節點位置,例如一內存節點指針struct heap_mem *mem,其下個相鄰節點位置為heap_ptr[mem->next],上個相鄰節點位置為heap_ptr[mem->prev],同時,可計算出其內存塊大小為

size=(&heap_ptr(mem->next)-(char*)mem-sizeof(struct heap_mem));

? ? ? ? 空閑內存塊指針*lfree指向地址最低的空閑內存塊,內存申請時,從lfree指向位置開始向高地址遍歷所有內存塊,直至找到合適的空閑內存塊。當內存塊釋放后,需要重新判斷和定位lfree位置。

????????系統內核為64位時,最小內存堆默認24字節,否則默認12字節。最小長度可調。

#ifdef ARCH_CPU_64BIT
#define MIN_SIZE 24
#else
#define MIN_SIZE 12
#endif

2、內存堆初始化

? ? ? ? 小內存堆通過全局變量管理,將內存區對齊后,首尾各定義一個內存塊節點,堆首內存節點為空閑內存塊節點,堆尾節點屬性是已使用,將2個節點雙向連接,并將空閑內存塊指針指向堆首節點。

2.1 初始化接口

/*** 系統堆初始化.* @param begin_addr 系統堆起始地址* @param end_addr 系統堆結束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)

2.2 初始化示例

????????代碼示例:

unsigned char heap[1024*1024];rt_system_heap_init(heap,heap+sizeof(heap));

? ? ? ? 內存結構圖示:初始化完成后lfree指向首節點,heap_end指向尾部節點,收尾節單向鏈接。已使用字節數used_mem為0,已使用字節數的最大值max_mem為0。

2.3 源碼分析

/*** 系統堆初始化.* @param begin_addr 系統堆起始地址* @param end_addr 系統堆結束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
{struct heap_mem *mem;/*內存堆首尾地址對齊,默認4字節對齊*/ rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);rt_ubase_t end_align   = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);RT_DEBUG_NOT_IN_INTERRUPT;/*內存堆大小檢驗*/if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align)){/* 有效內存大小 */mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;}else{rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",(rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);return;}/* 內存頭 */heap_ptr = (rt_uint8_t *)begin_align;/* 調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",(rt_ubase_t)heap_ptr, mem_size_aligned));/* 初始化內存頭節點,next鏈接尾部節點位置 */mem        = (struct heap_mem *)heap_ptr;mem->magic = HEAP_MAGIC;mem->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;// 指向內存相對位置(數組下標),非指針mem->prev  = 0;mem->used  = 0;/*記錄線程名稱"INIT"*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem, "INIT");
#endif/*初始化內存堆尾部節點*/heap_end    = (struct heap_mem *)&heap_ptr[mem->next];heap_end->magic = HEAP_MAGIC;heap_end->used  = 1;heap_end->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;/*與尾部鏈表連接*/heap_end->prev  = mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACErt_mem_setname(heap_end, "INIT");
#endif/*初始化內存權限信號*/rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);/* 初始化空閑塊指針,指向堆首節點 */lfree = (struct heap_mem *)heap_ptr;
}

3、內存堆操作

????????內存堆操作可以分為內存申請、內存伸縮、內存釋放。

3.1 內存塊申請

3.1.1?相關接口

????????接口rt_malloc僅申請一定大小的內存,并不進行初始化,所以申請的內存數據值不確定。接口rt_calloc審請內存后,會初始化為0。可以類比于C庫的malloc、calloc接口。

/*動態內存申請接口,size--申請內存大小,申請成功后返回內存塊首地址,否則返回Null*/
void *rt_malloc(rt_size_t size)/*動態內存申請接口,count--申請內存單元個數,size--單個單元大小
內存申請完成后初始化為0,申請成功后返回內存塊首地址,否則返回NULL*/
void *rt_calloc(rt_size_t count, rt_size_t size)

3.1.2 原理分析

????????內存控制塊申請,是從最低地址空閑內存塊節點lfree指向的位置開始向高地址遍歷所有內存塊,直至找到滿足申請大小的空閑內存塊,將該內存塊標記為已使用,如果該內存塊比較大,從該內存塊中切割出所需的內存,剩余部分形成新的內存塊,并定義新的內存塊節點,插入到內存塊雙向鏈表中。

3.1.3 示例分析

unsigned char heap[1024*1024];
// 初始化調用
rt_system_heap_init(heap,heap+sizeof(heap));
// 線程1調用,假設線程名稱"Led1"
void Thread1()
{char *p1=rt_malloc(1200);
}
// 線程2調用,假設線程名稱"Led2"
void Thread1()
{char *p2=rt_malloc(1000);
}

3.1.4 代碼分析

/*動態內存申請基礎接口*/
void *rt_malloc(rt_size_t size)
{rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;if (size == 0) return RT_NULL;RT_DEBUG_NOT_IN_INTERRUPT;/*調試代碼,長度是否對齊判斷, */if (size != RT_ALIGN(size, RT_ALIGN_SIZE))RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d, but align to %d\n",size, RT_ALIGN(size, RT_ALIGN_SIZE)));elseRT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d\n", size));/*長度對齊,默認4字節 */size = RT_ALIGN(size, RT_ALIGN_SIZE);/*分配長度size超過可用內存長度,返回失敗*/if (size > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));return RT_NULL;}/* 數據塊長度容錯 */if (size < MIN_SIZE_ALIGNED)size = MIN_SIZE_ALIGNED;/* 獲取信號,阻塞方式為永久阻塞 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*根據相對位置,遍歷空閑鏈表,即從空閑表頭開始遍歷,一直找到符合大小的控制塊*/for (ptr = (rt_uint8_t *)lfree - heap_ptr;ptr < mem_size_aligned - size;ptr = ((struct heap_mem *)&heap_ptr[ptr])->next){/*取節點*/mem = (struct heap_mem *)&heap_ptr[ptr];/*判斷內存塊屬性,未使用,且大小滿足size*/if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size){/* 判斷是否需要分割內存塊 */if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >=(size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)){/*插入新節點*/ptr2 = ptr + SIZEOF_STRUCT_MEM + size;/* 設置新節點 create mem2 struct */mem2       = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;#ifdef RT_USING_MEMTRACE/*內存監視使能*/rt_mem_setname(mem2, "    ");#endif/*按照地址順序 插入節點and insert it between mem and mem->next */mem->next = ptr2;mem->used = 1;/**/if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;}
#ifdef RT_MEM_STATS/*記錄當前使用內存和最大使用內存*/used_mem += (size + SIZEOF_STRUCT_MEM);if (max_mem < used_mem)max_mem = used_mem;
#endif}/* 不需要分割內存塊 */else{/*設置為已使用*/mem->used = 1;
#ifdef RT_MEM_STATS/*記錄當前使用內存和最大使用內存*/used_mem += mem->next - ((rt_uint8_t *)mem - heap_ptr);if (max_mem < used_mem)     max_mem = used_mem;
#endif}/* 設置新節點標志字 set memory block magic */mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*內存監視,記錄線程名稱*/if (rt_thread_self())rt_mem_setname(mem, rt_thread_self()->name);elsert_mem_setname(mem, "NONE");
#endif/*lfree指向的節點已被分配,調整空閑鏈表指針*/if (mem == lfree){/* 指向下一個未被分配的內存塊 */while (lfree->used && lfree != heap_end)lfree = (struct heap_mem *)&heap_ptr[lfree->next];/*斷言*/RT_ASSERT(((lfree == heap_end) || (!lfree->used)));}/*釋放信號*/rt_sem_release(&heap_sem);/*斷言*/RT_ASSERT((rt_ubase_t)mem + SIZEOF_STRUCT_MEM + size <= (rt_ubase_t)heap_end);RT_ASSERT((rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM) % RT_ALIGN_SIZE == 0);RT_ASSERT((((rt_ubase_t)mem) & (RT_ALIGN_SIZE - 1)) == 0);/*調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM,("allocate memory at 0x%x, size: %d\n",(rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM),(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));RT_OBJECT_HOOK_CALL(rt_malloc_hook,(((void *)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM)), size));/* 返回塊地址 */return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;}}rt_sem_release(&heap_sem);return RT_NULL;
}
void *rt_calloc(rt_size_t count, rt_size_t size)
{void *p;/* 申請內存,大小size */p = rt_malloc(count * size);/* 將申請的內存初始化為0 */if (p)rt_memset(p, 0, count * size);return p;
}

3.2 內存塊伸縮

3.2.1 相關接口

????????接口rt_realloc可以將已申請的內存大小進行調整或新申請一塊內存。可以類比于C庫的realloc接口。????????

/*動態內存長度伸縮,rmem--內存塊首地址,newsize--內存塊新長度,申請成功后返回內存塊首地址,否則返回NULL*/
void *rt_realloc(void *rmem, rt_size_t newsize)

3.2.2 原理分析

? ? ? ? 使用rt_realloc分配或調整內存塊分以下幾種情況,newsize先進行對齊:

(1)newsize對齊后為0,直接釋放內存塊。

(2)內存塊rmem未被分配,直接按照newsize對齊后大小分配內存,等同于rt_malloc(newsize)。

(3)rmem已被分配,當newsize對齊后小于等于原內存塊大小時,可以對原內存塊進行部分釋放:若newsize與原內存塊差不足以劃分一個新內存塊時,維持原內存結構不變。若足以劃分一個新內存塊時,則劃分出新的空閑內存塊進行釋放。

(4)rmem已被分配,當newsize對齊后大于原內存塊大小時,則根據newsize重新申請內存,并將原內存的數據復制到新內存塊,并釋放原內存塊。

3.2.3 示例分析

3.2.4 源碼分析

void *void *rt_realloc(void *rmem, rt_size_t newsize)
{rt_size_t size;rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;void *nmem;RT_DEBUG_NOT_IN_INTERRUPT;/*新尺寸對齊,默認4字節 */newsize = RT_ALIGN(newsize, RT_ALIGN_SIZE);/*新尺寸過大,返回失敗*/if (newsize > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("realloc: out of memory\n"));return RT_NULL;}/*新尺寸為0,釋放內存塊*/else if (newsize == 0){rt_free(rmem);return RT_NULL;}/* rmem 未被分配,直接按照newsize大小分配內存 */if (rmem == RT_NULL)return rt_malloc(newsize);/*獲取信號權限*/rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*內存塊容錯*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){/* 釋放信號 */rt_sem_release(&heap_sem);return rmem;}/*獲取當前內存塊節點*/mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);/*新舊大小相同,直接釋放信號后返回原內存塊首地址*/ptr = (rt_uint8_t *)mem - heap_ptr;size = mem->next - ptr - SIZEOF_STRUCT_MEM;if (size == newsize){/*釋放信號*/rt_sem_release(&heap_sem);return rmem;}/*新長度小于原長度,并且滿足劃分新塊的條件*/if (newsize + SIZEOF_STRUCT_MEM + MIN_SIZE < size){/* 劃分新內存塊 */
#ifdef RT_MEM_STATSused_mem -= (size - newsize);
#endif/*設置新內存塊節點*/ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;mem2 = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;/*設置線程名稱*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem2, "    ");
#endifmem->next = ptr2;if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;}/*調整空閑內存塊指針*/if (mem2 < lfree){/* the splited struct is now the lowest */lfree = mem2;}/**/plug_holes(mem2);/*釋放信號*/rt_sem_release(&heap_sem);return rmem;}/*新長度大于原長度*//*釋放信號*/ rt_sem_release(&heap_sem);/* 重新分配內存 */nmem = rt_malloc(newsize);if (nmem != RT_NULL) /* check memory */{/*原內存區數據復制到新內存區,然后釋放原內存*/rt_memcpy(nmem, rmem, size < newsize ? size : newsize);rt_free(rmem);}return nmem;
}

3.3 內存快釋放

3.3.1 相關接口

/** 釋放內存塊,rmem指向內存塊首地址*/
void rt_free(void *rmem)

3.3.2 原理分析

????????使用rt_free釋放內存塊:

(1)設置內存塊節點為空閑狀態。

(2)如果下個相鄰內存塊為空閑狀態,則合并內存塊,將下個內存塊的節點從鏈表刪除。

(3)如果上個相鄰內存塊為空閑狀態,則合并內存塊,將當前內存塊的節點從鏈表刪除。

3.3.3 示例分析

3.3.4 源碼分析

/** 釋放內存塊*/
void rt_free(void *rmem)
{struct heap_mem *mem;if (rmem == RT_NULL)return;RT_DEBUG_NOT_IN_INTERRUPT;/*斷言*/RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)heap_ptr &&(rt_uint8_t *)rmem < (rt_uint8_t *)heap_end);/*執行鉤子*/RT_OBJECT_HOOK_CALL(rt_free_hook, (rmem));/*地址檢查*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){RT_DEBUG_LOG(RT_DEBUG_MEM, ("illegal memory\n"));return;}/* 獲取當前內存塊節點*/mem = (struct heap_mem *)((rt_uint8_t *)rmem-SIZEOF_STRUCT_MEM);/*調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM,("release memory 0x%x, size: %d\n",(rt_ubase_t)rmem,(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));/* 獲取內存信號權限 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/* 檢查內存塊節點*/if (!mem->used || mem->magic != HEAP_MAGIC){rt_kprintf("to free a bad data block:\n");rt_kprintf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);}/*斷言,檢查內存塊節點*/RT_ASSERT(mem->used);RT_ASSERT(mem->magic == HEAP_MAGIC);/*修改節點(修改為未使用)*/mem->used  = 0;mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*復歸線程名稱為空格*/rt_mem_setname(mem, "    ");
#endif/*調整空閑頭部lfree,指向地址最低的空閑控制塊*/if (mem < lfree){lfree = mem;}#ifdef RT_MEM_STATS/*計算已使用內存*/used_mem -= (mem->next - ((rt_uint8_t *)mem - heap_ptr));
#endif/* 合并內存塊,前后內存塊空閑判斷*/plug_holes(mem);/*釋放信號*/rt_sem_release(&heap_sem);
}/* 合并內存塊,前后內存塊空閑判斷*/
static void plug_holes(struct heap_mem *mem)
{struct heap_mem *nmem;struct heap_mem *pmem;/*斷言*/RT_ASSERT((rt_uint8_t *)mem >= heap_ptr);RT_ASSERT((rt_uint8_t *)mem < (rt_uint8_t *)heap_end);RT_ASSERT(mem->used == 0);/* 判斷下一個內存塊,如果是空閑塊,則合并 */nmem = (struct heap_mem *)&heap_ptr[mem->next];if (mem != nmem &&nmem->used == 0 &&(rt_uint8_t *)nmem != (rt_uint8_t *)heap_end){/*糾正空閑節點lfree,lfree指向地址最低的空閑內存塊*/ if (lfree == nmem){lfree = mem;}/*刪除下一個內存塊節點*/mem->next = nmem->next;((struct heap_mem *)&heap_ptr[nmem->next])->prev = (rt_uint8_t *)mem - heap_ptr;}/* 判斷上一個內存塊,如果是空閑塊,則合并 */pmem = (struct heap_mem *)&heap_ptr[mem->prev];if (pmem != mem && pmem->used == 0){/* 糾正空閑節點lfree,lfree指向地址最低的空閑內存塊 */if (lfree == mem){lfree = pmem;}/*刪除當前節點*/pmem->next = mem->next;((struct heap_mem *)&heap_ptr[mem->next])->prev = (rt_uint8_t *)pmem - heap_ptr;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84712.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84712.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84712.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MyBatis-Plus 混合使用 XML 和注解

mybatisplus代碼生成器&#xff1a; 版本匹配是個比較麻煩的問題&#xff0c;這是我的配置&#xff1a; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>…

基于ssm的教學質量評估系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業六年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了六年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

【STM32】G030單片機開啟超過8個ADC通道的方法

如圖所示通道數量已經超過8個&#xff0c;按照之前博客的辦法已經行不通了 CubeMX配置STM32F103C8T6多路ADC配合DMA采集_stm32f103c8t6的adc采樣率-CSDN博客 這里筆者開了10個channel&#xff0c;注意切換為不完全配置&#xff0c;否則的話最多只有8個rank 開DMA&#xff0c;…

不同網絡I/O模型的原理

目錄 1、I/O的介紹 1.1、I/O 操作分類 1.2、I/O操作流程階段 1.3、I/O分類 2、同步I/O 2.1、阻塞I/O 2.2、非阻塞I/O 2.3、I/O復用 2.4、信號驅動式I/O 3、異步I/O 前言 在網絡I/O之中&#xff0c;I/O操作往往會涉及到兩個系統對象&#xff0c;一個是用戶空間調用I/O…

在正則表達式中語法 (?P<名字>內容)

&#x1f3af; 重點解釋&#xff1a;?P<xxx> 是什么語法&#xff1f; 這一整段&#xff1a; (?P<xxx>...)是 Python 正則表達式中 “命名捕獲組” 的語法。 咱們現在一個字一個字來解釋&#xff1a; ? (?...) 是干啥的&#xff1f; 這是一個捕獲組&#xff…

中興B860AV1.1_MSO9280_降級后開ADB-免刷機破解教程(非刷機)

中興B860AV1.1江蘇移動-自動降級包 關于中興b860av1.1頑固盒子降級教程終極版 將附件解壓好以后&#xff0c;準備一個8G以下的U盤重新格式化為FAT32格式后&#xff0c;并插入電腦 將以下文件及文件夾一同復制到優盤主目錄下&#xff08;見下圖&#xff09; 全選并復制到U盤主目…

2025-06-13【視頻處理】基于視頻內容轉場進行分割

問題&#xff1a;從網上下載的視頻文件&#xff0c;是由很多個各種不同的場景視頻片段合并而成。現在要求精確的把各個視頻片段從大視頻里分割出來。 效果如圖&#xff1a;已分割出來的小片段 思考過程 難點在于檢測場景變化。為什么呢&#xff1f;因為不同的視頻情況各異&am…

ReentrantLock和RLock

文章目錄 前言一、 ReentrantLock&#xff08;單機鎖&#xff0c;Java 內置&#xff09;示例&#xff1a;方法詳解 二、RLock&#xff08;分布式鎖&#xff0c;Redisson 提供&#xff09;示例:方法詳解 三、 對比總結:四、 如何選擇&#xff1f; 前言 ReentrantLock 和 RLock 都…

thinkphp ThinkPHP3.2.3完全開發手冊

慣例配置 應用設定 APP_USE_NAMESPACE > true, // 應用類庫是否使用命名空間 3.2.1新增 APP_SUB_DOMAIN_DEPLOY > false, // 是否開啟子域名部署 APP_SUB_DOMAIN_RULES > array(), // 子域名部署規則 APP_DOMAIN_SUFFIX > , // 域名后綴 如果是…

Python Day50 學習(仍為日志Day19的內容復習)

補充&#xff1a;梳理超參數調整流程&#xff08;邏輯&#xff09; 超參數調節的流程邏輯可以總結為以下幾個步驟&#xff1a; 1. 明確目標 確定你要優化的模型和評估指標&#xff08;如準確率、F1值、AUC等&#xff09;。 2. 選擇要調節的超參數 列出模型中影響較大的超參數…

公司網絡變差的解決方法(固定IP地址沖突)

問題描述 最近公司網絡變差&#xff0c;不知道為什么。&#xff08;別的同事反饋的&#xff0c;本人沒有感覺變差&#xff0c;也是比較奇怪的現象&#xff09; 現象有視頻會議變卡等。 調查過程 1.領導給網絡公司打電話溝通&#xff0c;對面遠程看了下&#xff0c;不是設備問…

使用Prometheus+Grafana+Alertmanager+Webhook-dingtalk搭建監控平臺

一、監控平臺介紹 1.監控平臺簡述普羅米修斯四件套,分別為Prometheus、Grafana、Alertmanager、Webhook-DingTalk。Prometheus一套開源的監控&報警&時間序列數據庫的組合,由SoundCloud公司開發,廣泛用于云原生環境和容器化應用的監控和性能分析。其提供了通用的數據…

UR機器人解鎖關節扭矩控制:利用英偉達Isaac Lab框架,推動裝配自動化的Sim2Real遷移

在工業制造領域&#xff0c;機器人裝配長期依賴固定自動化模式&#xff0c;面臨部署成本高、適配性差等挑戰。多部件裝配是制造業、汽車及航空航天等行業中的核心環節。傳統裝配系統通常針對特定任務設計&#xff0c;依賴大量人工工程部署&#xff0c;靈活性不足&#xff0c;難…

ABB 605系列

系列概述 ABB Relion605系列是專為配電網設計的保護繼電器產品系列&#xff0c;代表了中低壓電力系統保護領域的技術基準。基于ABB在電力保護領域數十年的經驗&#xff0c;該系列集成了最新的數字信號處理技術和網絡通信能力&#xff0c;為變電站自動化提供了完整的解決方案。…

Python|GIF 解析與構建(6):手搓 tk 錄制工具

目錄 Python&#xff5c;GIF 解析與構建&#xff08;6&#xff09;&#xff1a;手搓 tk 錄制工具 一、工具功能概覽 二、核心架構設計 1. 幀率控制模塊 2. 屏幕捕獲模塊 3. 主應用模塊 三、關鍵技術解析 1. 屏幕捕獲技術 2. 幀率控制原理 3. 透明窗口實現 四、使用指…

在VBA中,提取word表格的文本時,通常有什么干擾符號,需要清除

標題 在VBA中&#xff0c;提取word表格的文本時&#xff0c;通常有什么干擾符號,需要清除 正文 解決問題提取word表格的文本時&#xff0c;通常有什么干擾符號,需要清除 在VBA中提取Word表格文本時&#xff0c;常見的干擾符號及其清除方法如下&#xff1a; ?? 一、主要干擾符…

C++基礎學習:深入理解類中的構造函數、析構函數、this指針與new關鍵字

前言 在C面向對象編程中&#xff0c;類是構建復雜程序的基本單元。今天&#xff0c;我們將深入探討類中的幾個核心概念&#xff1a;構造函數、析構函數、this指針以及new關鍵字。這些概念對于理解C對象生命周期和內存管理至關重要。 1. 構造函數 構造函數是類的一個特殊成員…

2025 高考游記/總結

坐標GD 新課標一卷選手 前言 思緒有點亂&#xff0c;想想從哪里說起 沒想到這個博客已經三年沒發過東西了&#xff0c;上次發還是初三準備特長生的時候&#xff0c;一瞬間就已經高考結束了&#xff0c;有種不真實感 對于高中的三年&#xff0c;有很多話、很多感悟想說&#xff…

Python基礎之函數(1/3)

函數(基礎) [函數后續還會更新兩次] 一.認識函數的作用 函數就是將一段具有獨立功能的代碼塊整合到一個整體并命名&#xff0c;在需要的位置&#xff0c;調用這個名稱即可完成對應的需求 函數在開發過程中&#xff0c;可以更高效的實現代碼重用 二.函數的使用步驟 1定義函…

AWS CloudFormation實戰:構建可復用的ECS服務部署模板

一、前言 在云原生時代,基礎設施即代碼(IaC)已成為DevOps實踐的核心組件。AWS CloudFormation作為AWS原生的IaC服務,允許開發人員和系統管理員以聲明式方式定義和部署AWS資源。本文將深入探討如何構建一個通用的CloudFormation模板,用于在AWS ECS(Elastic Container Servic…