操作系统开发:(2) 链表与内存管理
本文实现了一个环形双向链表和内存管理模块用于构建RTOS系统。链表模块(list.h/c)包含节点(Node)和链表(List)结构体,提供初始化、插入、删除等操作,采用哨兵节点设计确保循环性,并通过完整性校验增强安全性。内存管理模块(heap.h/c)实现了malloc/free功能,使用首次适应算法管理16KB静态内存池,支持8字节对齐和块合并优化,包含内存统计功能。两个模块均通过完整测试验证
·
在之前已经学习了 cpu 的上电启动流程和裸机下的链接和启动文件,现在参考 FreeRTOS ,我们自底向上手动重新实现该系统,并确保其能够成功应用。
1. 环形双向链表
1.1 代码
源码中为 list.h 及 list.c 文件,其中使用了大量的宏定义,我们暂时不考虑 FreeRTOS.h 、FreeRTOSConfig.h 中的配置以及代码中的宏定义,在之后需要时考虑,为求易用,采用与源码不同的命名风格。
// list.h
#ifndef LIST_H
#define LIST_H
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <limits.h>
#define INTEGRITY_CHECK_VALUE 0x12345678U // 完整性校验值
#define MAX_NODE_VAL UINT32_MAX // 节点最大值
// 前向声明
struct List;
typedef struct Node Node;
// 链表节点
struct Node {
uint32_t integrity1; // 完整性校验字段 1
uint32_t val; // 值
Node* nxt;
Node* pre;
void* owner; // 指向拥有此节点的对象(如任务控制块)
struct List* container; // 所属链表
uint32_t integrity2; // 完整性校验字段 2
};
// 链表结构
typedef struct List {
uint32_t listIntegrity1;
size_t nodeCount; // 链表中实际节点数量(不含哨兵)
Node dummy; // 哨兵节点
uint32_t listIntegrity2;
} List;
// ———————— 公共 API ————————
void list_init(List* list);
void list_initNode(Node* node);
void list_insertEnd(List* list, Node* newNode);
void list_insertSorted(List* list, Node* newNode);
size_t list_removeNode(Node* node);
// 其他
static inline int list_isEmpty(const List* list) {
return (list->nodeCount == 0) ? 1 : 0;
}
static inline size_t list_length(const List* list) {
return list->nodeCount;
}
static inline void* list_getOwnerOfHeadEntry(const List* list) {
return ((Node* )&(list->dummy))->nxt->owner;
}
static inline uint32_t list_getHeadValue(const List* list) {
return ((Node* )&(list->dummy))->nxt->val;
}
static inline int list_isNodeInList(const List* list, const Node* node) {
return (node->container == list) ? 1 : 0;
}
static inline List* list_nodeGetContainer(const Node* node) {
return node->container;
}
#endif
// list.c
#include "list.h"
#include <string.h>
#include <stdio.h>
// ———————— 内联辅助函数————————
// 只在 list.c 中使用 ,如果在其他文件中使用应放在头文件
// 1. inline 只是建议,编译器可以选择是否内联
// 2. C++中 inline 全局变量/函数为外部链接性,且允许重定义
// static inline 不推荐,函数地址在不同 .cpp 文件中不相等
// 3. C 中 inline 全局变量/函数为外部链接性,但不允许重定义
// 使用static 后为内部链接,每个包含该头文件的 .c 文件都会生成一个本地副本
static inline void setNodeIntegrity(Node* node) {
node->integrity1 = INTEGRITY_CHECK_VALUE;
node->integrity2 = INTEGRITY_CHECK_VALUE;
}
static inline void setListIntegrity(List* list) {
list->listIntegrity1 = INTEGRITY_CHECK_VALUE;
list->listIntegrity2 = INTEGRITY_CHECK_VALUE;
}
static inline void checkNodeIntegrity(const Node* node) {
assert(node->integrity1 == INTEGRITY_CHECK_VALUE);
assert(node->integrity2 == INTEGRITY_CHECK_VALUE);
}
static inline void checkListIntegrity(const List* list) {
assert(list->listIntegrity1 == INTEGRITY_CHECK_VALUE);
assert(list->listIntegrity2 == INTEGRITY_CHECK_VALUE);
}
void list_init(List* list) {
// 初始化哨兵节点,自环
Node* dummy = &(list->dummy);
dummy->val = MAX_NODE_VAL;
dummy->nxt = (Node* )dummy;
dummy->pre = (Node* )dummy;
setNodeIntegrity(dummy);
list->nodeCount = 0;
setListIntegrity(list);
}
void list_initNode(Node* node) {
node->container = NULL;
setNodeIntegrity(node);
}
void list_insertEnd(List* list, Node* newNode) {
checkListIntegrity(list);
checkNodeIntegrity(newNode);
Node* dummy = &(list->dummy);
newNode->nxt = dummy;
newNode->pre = dummy->pre;
dummy->pre->nxt = newNode;
dummy->pre = newNode;
newNode->container = list;
list->nodeCount++;
}
void list_insertSorted(List* list, Node* newNode) {
checkListIntegrity(list);
checkNodeIntegrity(newNode);
uint32_t value = newNode->val;
Node* iter;
if (value == MAX_NODE_VAL) {
// 插入到哨兵前(即逻辑末尾)
iter = (Node* )&(list->dummy);
iter = iter->pre;
} else {
// 从哨兵开始向前找第一个 val > value 的位置
iter = (Node* )&(list->dummy);
while (iter->nxt->val <= value) {
iter = iter->nxt;
}
}
// 插入在 iter 之后
newNode->nxt = iter->nxt;
newNode->pre = iter;
iter->nxt->pre = newNode;
iter->nxt = newNode;
newNode->container = list;
list->nodeCount++;
}
size_t list_removeNode(Node* node) {
List* list = node->container;
assert(node != &(list->dummy));
node->nxt->pre = node->pre;
node->pre->nxt = node->nxt;
node->container = NULL;
list->nodeCount--;
return list->nodeCount;
}
#if 0
// 辅助函数:打印链表内容(仅用于调试)
void printList(const List* list) {
printf("List length: %zu\n", list_length(list));
if (list_isEmpty(list)) {
printf("(empty)\n");
return;
}
Node* dummy = (Node*)&(list->dummy);
Node* cur = dummy->nxt;
while (cur != dummy) {
printf("%u -> ", cur->val);
cur = cur->nxt;
}
printf("END\n");
}
int main(void) {
printf("=== Starting List Test Suite ===\n");
// Test 1: 初始化链表
List list;
list_init(&list);
assert(list_isEmpty(&list));
assert(list_length(&list) == 0);
printf("✅ Test 1 passed: list initialized correctly.\n");
// Test 2: 初始化节点并插入末尾
Node node1, node2, node3;
list_initNode(&node1); node1.val = 10; node1.owner = (void*)0x1;
list_initNode(&node2); node2.val = 20; node2.owner = (void*)0x2;
list_initNode(&node3); node3.val = 30; node3.owner = (void*)0x3;
list_insertEnd(&list, &node1);
assert(!list_isEmpty(&list));
assert(list_length(&list) == 1);
assert(list_getHeadValue(&list) == 10);
assert(list_getOwnerOfHeadEntry(&list) == (void*)0x1);
assert(list_isNodeInList(&list, &node1));
assert(list_nodeGetContainer(&node1) == &list);
printf("✅ Test 2 passed: insertEnd and basic accessors.\n");
list_insertEnd(&list, &node2);
assert(list_length(&list) == 2);
// 验证顺序:10 -> 20
Node* dummy = (Node*)&(list.dummy);
assert(dummy->nxt->val == 10);
assert(dummy->nxt->nxt->val == 20);
printf("✅ Test 3 passed: multiple insertEnd.\n");
// Test 4: 有序插入
List sortedList;
list_init(&sortedList);
Node s1, s2, s3, s4;
list_initNode(&s1); s1.val = 50;
list_initNode(&s2); s2.val = 10;
list_initNode(&s3); s3.val = 30;
list_initNode(&s4); s4.val = 70;
list_insertSorted(&sortedList, &s2); // 10
list_insertSorted(&sortedList, &s3); // 10,30
list_insertSorted(&sortedList, &s1); // 10,30,50
list_insertSorted(&sortedList, &s4); // 10,30,50,70
assert(list_length(&sortedList) == 4);
dummy = (Node*)&(sortedList.dummy);
assert(dummy->nxt->val == 10);
assert(dummy->nxt->nxt->val == 30);
assert(dummy->nxt->nxt->nxt->val == 50);
assert(dummy->nxt->nxt->nxt->nxt->val == 70);
printf("✅ Test 4 passed: insertSorted maintains order.\n");
// Test 5: 插入 MAX_NODE_VAL(应插在末尾)
Node maxNode;
list_initNode(&maxNode);
maxNode.val = MAX_NODE_VAL;
list_insertSorted(&sortedList, &maxNode);
assert(list_length(&sortedList) == 5);
assert(((Node*)&sortedList.dummy)->pre->val == MAX_NODE_VAL);
printf("✅ Test 5 passed: MAX_NODE_VAL inserted at end.\n");
// Test 6: 删除节点
size_t newLen = list_removeNode(&s3); // remove 30
assert(newLen == 4);
assert(!list_isNodeInList(&sortedList, &s3));
assert(s3.container == NULL);
// 验证剩余顺序:10,50,70,MAX
dummy = (Node*)&(sortedList.dummy);
assert(dummy->nxt->val == 10);
assert(dummy->nxt->nxt->val == 50);
assert(dummy->nxt->nxt->nxt->val == 70);
assert(dummy->pre->val == MAX_NODE_VAL);
printf("✅ Test 6 passed: node removal works.\n");
// Test 7: 删除头节点
list_removeNode(&s2); // remove 10
assert(list_getHeadValue(&sortedList) == 50);
printf("✅ Test 7 passed: head node removal.\n");
// Test 8: 删除尾节点(非 MAX)
list_removeNode(&s4); // remove 70
assert(((Node*)&sortedList.dummy)->pre->val == MAX_NODE_VAL);
assert(list_length(&sortedList) == 2); // 50, MAX
printf("✅ Test 8 passed: tail node removal.\n");
// Test 9: 完整性校验触发(可选:手动破坏校验值)
// 注意:这会触发 assert
// list.listIntegrity1 = 0;
// list_insertEnd(&list, &node3); // should crash
printf("\n✅ All tests passed!\n");
return 0;
}
#endif
// gcc -Wall -Wextra -std=c99 -g list.c -o test_list
// ./test_list
1.2 为何 static 不冲突?
1️⃣ 每个编译单元(.c 文件)独立编译
- a.c 和 b.c 分别编译成 a.o 和 b.o。
- 在 a.o 中,static int x; 是一个 局部符号(local symbol),绑定到 a.o 的 .bss 段偏移 0。
- 在 b.o 中,static int x; 也是一个 局部符号,绑定到 b.o 的 .bss 段偏移 0。
- 它们在各自的 .o 文件中同名,但彼此完全不知道对方的存在。
2️⃣ 链接时:局部符号不参与全局符号解析
- 链接器(如 ld)只关心 全局符号(global symbols) 是否重定义。
- static 变量是 局部符号(local / internal linkage),链接器:
- 不会检查它们是否重名;
- 不会合并它们;
- 直接把每个 .o 的 .bss 段拼接起来。
- 结果:可执行文件的 .bss 段 = a.o 的 .bss + b.o 的 .bss + ...
所以 a.c 中的 x 和 b.c 中的 x 实际上位于 .bss 段中不同的内存地址。
3️⃣ 运行时:每个函数访问的是自己编译单元内的 x
- 编译器在生成代码时,已经为 a.c 中的 x 绑定了一个固定的偏移(比如 .bss + 0)。
- 同样,b.c 中的 x 绑定的是另一个偏移(比如 .bss + 4,假设 int 占 4 字节)。
- 运行时,a.c 的函数只会读写它自己的那个地址,根本不会碰到 b.c 的 x。
1.3 测试
将 list.c 中的 #if 0 改为 1,之后执行:
gcc -Wall -Wextra -std=c99 -g list.c -o test_list
./test_list
其中 Test 9 需要取消注释后再重新编译测试一次。
如图则测试成功:


测试完毕后把 #if 1 改为 0,否则之后的应用会出错。
2. 内存管理(heap)
2.1 为何要自己实现堆管理?
为什么不能使用标准 C 的 malloc/free?
- 没有堆管理器 :malloc 的实现依赖于操作系统的 sbrk 或 mmap 系统调用。
- 如果你强行调用 malloc:
- 链接时可能失败(找不到 malloc 实现)
- 或者链接了 libc 的 stub(空实现),运行时直接崩溃或返回 NULL
2.2 代码
// heap.h
#ifndef HEAP_H
#define HEAP_H
#include <stddef.h>
#include <stdint.h>
// 配置宏
#define HEAP_TOTAL_SIZE (16 * 1024) // 堆总大小:16KB
#define HEAP_ALIGNMENT 8 // 内存对齐:8 字节
#define HEAP_CLEAR_ON_FREE 0 // free 时是否清零内存
#define HEAP_CHECK_VALUE 0xABCDEF99U // 完整性校验魔数
// 前向声明
typedef struct BlockHead BlockHead;
// ———————— 内部类型定义 ————————
struct BlockHead {
uint32_t integrity1; // 完整性校验字段 1
BlockHead* nxtFree; // 指向下一个空闲块(仅空闲时有效)
size_t blockSize; // 物理块大小(含dummyHead),最高位为分配标志,不是实际可用大小
uint32_t integrity2; // 完整性校验字段 2
};
// ———————— 辅助宏 ————————
// 对齐宏:将 x 对齐到最近的 align 的倍数
// C 标准规定:malloc() 返回的指针必须适合任何基本类型的对齐要求。
// 在 64 位系统中,最大基本类型通常是 8 字节(如 double、void*),所以 8 字节对齐是安全且通用的选择
// 且许多 32 位系统的 malloc 实现仍然会返回 8 字节对齐的地址
#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1)) // 向上对齐
#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1)) // 向下对齐
// 将数字 1 左移到最高位,使用 size_t 最高位作为已分配标志
#define BLOCK_ALLOCATED_BIT ((size_t)1 << (sizeof(size_t) * 8 - 1)) // 32 位系统上,BLOCK_ALLOCATED_BIT是 0x80000000
#define IS_ALLOCATED(blk) (((blk)->blockSize & BLOCK_ALLOCATED_BIT) != 0)
#define MARK_ALLOCATED(blk) ((blk)->blockSize |= BLOCK_ALLOCATED_BIT)
#define MARK_FREE(blk) ((blk)->blockSize &= ~BLOCK_ALLOCATED_BIT)
#define GET_USABLE_SIZE(blk) ((blk)->blockSize & ~BLOCK_ALLOCATED_BIT) // 清除最高位,只保留实际大小(用户可用 + 头部大小)
// 公共 API 声明
void* heap_malloc(size_t size);
void heap_free(void* ptr);
void* heap_calloc(size_t num, size_t size);
size_t heap_getFreeBytes(void);
size_t heap_getMinFreeBytes(void);
void heap_reset(void);
#endif // HEAP_H
// heap.c
#include "heap.h"
#include <string.h>
#include <limits.h>
#include <assert.h>
// ———————— 静态全局数据 ————————
// 在 数据段(通常是 .bss 或 .data)中预留一块连续的静态内存,作为自定义堆分配器(如 malloc/free)的底层存储空间。
// 它不是动态分配的(因为堆还没初始化),而是编译时就确定大小的全局数组。
// 所有后续的 malloc 请求,都会从这块 ucHeap 中切分出子块返回,所以不能放在头文件中。
static uint8_t ucHeap[HEAP_TOTAL_SIZE]; // 静态堆内存池
// 只在 heap.c 中使用,故放在 heap.c 中
static BlockHead dummyHead; // 起始哨兵节点,不在堆内存池中
static BlockHead* dummyTail = NULL; // 结束哨兵指针
static size_t FreeBytesRemaining = 0; // 当前空闲字节数
static size_t MinEverFreeBytes = SIZE_MAX; // 历史最小空闲字节数
// 对齐后的块头大小
static const size_t blockHeadSize = ALIGN_UP(sizeof(BlockHead), HEAP_ALIGNMENT);
// |------------------|----------------------|-----------------|
// ucHeap 对齐的偏移 first dummyTail 尾部对齐偏移
// |-----------------------------------------------------------|
// HEAP_TOTAL_SIZE
// ———————— 内部辅助函数 ————————
// 只在 heap.c 中使用,故放在 heap.c 中
// 1. inline 只是建议,编译器可以选择是否内联
// 2. C++中 inline 全局变量/函数为外部链接性,且允许重定义
// static inline 不推荐,函数地址在不同 .cpp 文件中不相等
// 3. C 中 inline 全局变量/函数为外部链接性,但不允许重定义
// 使用static 后为内部链接,每个包含该头文件的 .c 文件都会生成一个本地副本
static inline void setBlockIntegrity(BlockHead* blk) {
blk->integrity1 = HEAP_CHECK_VALUE;
blk->integrity2 = HEAP_CHECK_VALUE;
}
static inline void checkBlockIntegrity(const BlockHead* blk) {
assert(blk != NULL);
assert(blk->integrity1 == HEAP_CHECK_VALUE);
assert(blk->integrity2 == HEAP_CHECK_VALUE);
}
static void heap_init(void) {
// 计算对齐后的堆起始地址
uintptr_t startAddr = (uintptr_t)ucHeap; // 获取堆起始地址
startAddr = ALIGN_UP(startAddr, HEAP_ALIGNMENT); // 对齐起始地址
size_t offset = startAddr - (uintptr_t)ucHeap; // 起始地址与堆起始地址的偏移量(对齐损失)
size_t usableSize = HEAP_TOTAL_SIZE - offset; // 可用字节数(不含对齐损失)
// 计算结束块地址(预留一个块头)
uintptr_t endAddr = startAddr + usableSize;
endAddr = ALIGN_DOWN(endAddr - blockHeadSize, HEAP_ALIGNMENT);
dummyTail = (BlockHead*)endAddr;
// 初始化结束哨兵
dummyTail->blockSize = 0;
dummyTail->nxtFree = NULL;
setBlockIntegrity(dummyTail);
// 创建初始空闲块:从 startAddr 到 dummyTail
BlockHead* first = (BlockHead*)startAddr;
first->blockSize = (uint8_t*)dummyTail - (uint8_t*)first;
first->nxtFree = dummyTail;
setBlockIntegrity(first);
// 初始化起始哨兵
dummyHead.nxtFree = first;
dummyHead.blockSize = 0;
setBlockIntegrity(&dummyHead);
// 初始化统计信息
FreeBytesRemaining = first->blockSize;
MinEverFreeBytes = first->blockSize;
}
// 将一个刚释放的块插入空闲链表,并自动向前/向后合并相邻空闲块
static void insertBlockIntoFreeList(BlockHead* toInsert) {
checkBlockIntegrity(toInsert);
// 从起始哨兵开始,按地址升序查找插入位置
// 遍历链表,找到 iter 使得:iter < toInsert <= iter->nxtFree
BlockHead* iter = &dummyHead;
while (iter->nxtFree < toInsert && iter->nxtFree != dummyTail) {
iter = iter->nxtFree;
}
// 向前合并:若前一块紧邻当前块,[ iter 块 ][ toInsert 块 ] ← 中间没有间隔
// 如果是,扩展 iter 的大小,并让 toInsert 指向 iter
if (iter != &dummyHead) {
uint8_t* prevEnd = (uint8_t*)iter + GET_USABLE_SIZE(iter); // iter 块的结束地址
if (prevEnd == (uint8_t*)toInsert) { // 如果中间没有间隔,iter 的结束地址 == toInsert 的起始地址时
// 把 toInsert 的大小加到 iter->blockSize 上。
// 之后不再需要 toInsert 这个独立块了,让 toInsert 指针指向 iter,后续操作基于合并后的大块
iter->blockSize += GET_USABLE_SIZE(toInsert);
toInsert = iter;
}
}
// 向后合并:若当前块紧邻下一块
BlockHead* next = iter->nxtFree;
if (next != dummyTail) {
uint8_t* thisEnd = (uint8_t*)toInsert + GET_USABLE_SIZE(toInsert); // toInsert 块的结束地址
if (thisEnd == (uint8_t*)next) { // 只有当 toInsert 的结束地址 == next 的起始地址时,才能合并
toInsert->blockSize += GET_USABLE_SIZE(next);
toInsert->nxtFree = next->nxtFree;
} else {
toInsert->nxtFree = next;
}
} else {
toInsert->nxtFree = dummyTail;
}
// 更新前驱指针(除非已合并到前一块)
if (iter != toInsert) {
iter->nxtFree = toInsert;
}
setBlockIntegrity(toInsert);
}
// ———————— 公共 API 定义 ————————
// 分配一块至少 size 字节的内存,返回用户指针(不包含头部)
void* heap_malloc(size_t size) {
if (size == 0) {
return NULL;
}
// 计算所需总大小(含头部 + 对齐)
size_t totalSize = size + blockHeadSize;
if (totalSize < size) { // 溢出检查
return NULL;
}
totalSize = ALIGN_UP(totalSize, HEAP_ALIGNMENT);
// 检查是否会覆盖分配标志位
// 如果 totalSize >= BLOCK_ALLOCATED_BIT,那么当我们将它存入 blockSize 并设置分配位时,会覆盖掉实际大小信息,导致后续计算错误
if (totalSize >= BLOCK_ALLOCATED_BIT) {
return NULL;
}
// 首次调用 malloc 时才初始化堆(lazy initialization)。
// 避免在程序启动时就占用 CPU 时间。
if (dummyTail == NULL) {
heap_init();
}
// 遍历空闲链表,寻找足够大的块
BlockHead* prev = &dummyHead;
BlockHead* cur = dummyHead.nxtFree;
while (cur != dummyTail && GET_USABLE_SIZE(cur) < totalSize) {
checkBlockIntegrity(cur);
prev = cur;
cur = cur->nxtFree;
}
if (cur == dummyTail) {
return NULL; // 无足够空间
}
// 从空闲链表中移除该块
// 此时 cur 不再属于链表,即将被分配给用户
prev->nxtFree = cur->nxtFree;
// 尝试分割剩余空间
size_t remaining = GET_USABLE_SIZE(cur) - totalSize;
// 剩余空间 remaining 必须 ≥ 一个完整新块的最小尺寸
// 至少能容纳一个 BlockHead(blockHeaderSize), 加上 HEAP_ALIGNMENT 是为了保证新块地址对齐
if (remaining >= blockHeadSize + HEAP_ALIGNMENT) {
BlockHead* newBlock = (BlockHead*)((uint8_t*)cur + totalSize); // 计算新块地址
newBlock->blockSize = remaining; // 设置新块大小:remaining
setBlockIntegrity(newBlock);
insertBlockIntoFreeList(newBlock); // 将新块插入空闲链表(会自动合并邻居)
cur->blockSize = totalSize; // 缩小原块大小到 totalSize
}
// 标记为已分配
MARK_ALLOCATED(cur); // 设置 blockSize 最高位为 1
cur->nxtFree = NULL;
setBlockIntegrity(cur);
// 更新统计信息
FreeBytesRemaining -= GET_USABLE_SIZE(cur); // 当前空闲字节数
if (FreeBytesRemaining < MinEverFreeBytes) { // 历史最小值(反映最严重内存压力)
MinEverFreeBytes = FreeBytesRemaining;
}
// 返回用户可用地址(跳过头部)
return (void*)((uint8_t*)cur + blockHeadSize);
}
// 释放由 malloc 分配的内存
void heap_free(void* ptr) {
if (ptr == NULL) {
return;
}
BlockHead* block = (BlockHead*)((uint8_t*)ptr - blockHeadSize);
checkBlockIntegrity(block);
assert(IS_ALLOCATED(block));
assert(block->nxtFree == NULL); // 已分配块不应在空闲链表中
// 标记为未分配
MARK_FREE(block);
block->nxtFree = NULL;
#if HEAP_CLEAR_ON_FREE
memset(ptr, 0, GET_USABLE_SIZE(block) - blockHeadSize);
#endif
// 更新统计并插入空闲链表
FreeBytesRemaining += GET_USABLE_SIZE(block);
insertBlockIntoFreeList(block);
}
// 分配一块能容纳 num 个 size 字节元素的内存,并将所有字节初始化为 0
void* heap_calloc(size_t num, size_t size) {
if (num != 0 && size > SIZE_MAX / num) {
return NULL; // 防止乘法溢出
}
size_t total = num * size;
void* ptr = heap_malloc(total);
if (ptr != NULL) {
memset(ptr, 0, total);
}
return ptr;
}
size_t heap_getFreeBytes(void) {
return FreeBytesRemaining;
}
size_t heap_getMinFreeBytes(void) {
return MinEverFreeBytes;
}
void heap_reset(void) {
dummyTail = NULL;
FreeBytesRemaining = 0;
MinEverFreeBytes = SIZE_MAX;
}
#if 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "heap.h"
void print_stats(const char* msg) {
printf("%s: Free=%zu, MinFree=%zu\n", msg,
heap_getFreeBytes(), heap_getMinFreeBytes());
}
int main(void) {
printf("=== Heap Allocator Test ===\n");
// 初始状态
print_stats("Initial");
// Test 1: malloc(0) -> NULL
void* p0 = heap_malloc(0);
assert(p0 == NULL);
printf("Test 1 passed: malloc(0) returns NULL\n");
// Test 2: Basic allocation and free
void* p1 = heap_malloc(64);
assert(p1 != NULL);
printf("Allocated 64 bytes at %p\n", p1);
print_stats("After malloc(64)");
// Write some data to check integrity later
memset(p1, 0xAA, 64);
heap_free(p1);
print_stats("After free(p1)");
// Test 3: calloc
void* p2 = heap_calloc(10, sizeof(int)); // 40 bytes
assert(p2 != NULL);
int* arr = (int*)p2;
for (int i = 0; i < 10; i++) {
assert(arr[i] == 0);
}
printf("calloc zero-initialized correctly\n");
heap_free(p2);
print_stats("After free(calloc)");
// Test 4: Multiple allocations and fragmentation test
void* a = heap_malloc(100);
void* b = heap_malloc(200);
void* c = heap_malloc(50);
print_stats("After 3 allocations");
heap_free(b); // free middle block
print_stats("After freeing middle block");
void* d = heap_malloc(190); // should reuse b's space (with slight split)
assert(d != NULL);
printf("Reallocated ~190 bytes after freeing 200-byte block\n");
heap_free(a);
heap_free(c);
heap_free(d);
print_stats("After freeing all");
// Test 5: Alignment check (should be 8-byte aligned)
size_t sizes[] = {1, 7, 8, 9, 15, 16, 17, 100, 1000};
for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
void* ptr = heap_malloc(sizes[i]);
assert(ptr != NULL);
uintptr_t addr = (uintptr_t)ptr;
assert((addr % HEAP_ALIGNMENT) == 0);
heap_free(ptr);
}
printf("All allocations are %d-byte aligned\n", HEAP_ALIGNMENT);
// Test 6: Overflow protection
void* bad = heap_malloc(SIZE_MAX - 10);
assert(bad == NULL);
printf("Overflow protection works\n");
// Test 7: Reset heap
heap_reset();
print_stats("After heap_reset()");
void* after_reset = heap_malloc(32);
assert(after_reset != NULL);
heap_free(after_reset);
print_stats("After reset + alloc/free");
printf("\n✅ All tests passed!\n");
return 0;
}
#endif
// gcc -Wall -Wextra -std=c99 -g -o test_heap heap.c
// ./test_heap
2.3 测试
同上:

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)