转载地址:http://capacity.blog.163.com/blog/static/208664131201252951117485/
lwip源码分析
Lwip,lightweight ip协议,是一个轻量级的网络协议实现,前段时间基本上搞定了,写点东西,就当是对osi
模型的进一步了解吧。
我将以参照源码的方式由下向上解释lwip的工作原理,有什么错误欢迎指正。
首先将从lwip的内存机制说起,lwip提供了三种内存分配管理机制,一种是c
语言自带的内存机制,只要编译的时候将编译选项设置MEM_LIBC_MALLOC=1
就行,这个不多说。另外两种是Pool和Heap,字面意思就是内存池跟内存堆。
先说Heap
方式吧,这种方式先要在内存中分配一块大的连续的区域用作堆,来看一下它的数据结构
struct mem {
/** index (-> ram[next]) of the next struct */
mem_size_t next;
/** index (-> ram[next]) of the next struct */
mem_size_t prev;
/** 1: this area is used; 0: this area is unused */
u8_t used;
};
这就是内存非配的管理结构体。
#define MIN_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MIN_SIZE) //12
// LWIP_MEM_ALIGN_SIZE是一个宏,用来实现编译器的字对齐,默认的是一字节对齐
#define SIZEOF_STRUCT_MEM LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
#define MEM_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MEM_SIZE)//1600
/*下面定义了堆的大小,翻译出来默认的大小大约是1612+2*sizeof(struct mem)*/
static u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];
/*指向ram_heap头地址,为了字节对齐,使用的是u8_t的指针 */
static u8_t *ram;
/*末尾的指针,总是不被使用的,用来作为一个结束判定标志 */
static struct mem *ram_end;
/*指向最低的没有被使用的内存地址,所以分配内存是从低地址向高地址分配的*/
static struct mem *lfree;
先来看这个堆管理方式内存的初始化函数,那些debug跟assert,还有sys
信号量语句就不要了
Void mem_init(void)
{
struct mem *mem;
ram = LWIP_MEM_ALIGN(ram_heap);//将堆的内存对齐
/* 下面是初始化堆的头部,ram是全局变量*/
mem = (struct mem *)ram;//这里头部的prev是空指针,next是MEM_SIZE_ALIGNED(
1600),这是因为第一次使用堆,因为没有分配区域,所以所有的在mem
以后的区域都可以作为这个结构体的数据区,在往后看就清楚了,这里可能说不明白
mem->next = MEM_SIZE_ALIGNED;
mem->prev = 0;
mem->used = 0;
/* 下面是初始化堆的尾部 */
ram_end = (struct mem *)&ram[MEM_SIZE_ALIGNED];//ram_end
指向堆的最后,直接设置used=0,next跟prev都是指向同一个位置,作为一个high-mark
使用。
ram_end->used = 1;
ram_end->next = MEM_SIZE_ALIGNED;
ram_end->prev = MEM_SIZE_ALIGNED;
/*下面初始lfree,就是指向ram */
lfree = (struct mem *)ram;
}
初始化完了,就来看看怎么分配空间了,看一下下面的代码,里面很多的#if.. #endif,
还有assert,debug等都省略了,只挑核心的一部分
void *mem_malloc(mem_size_t size)
{
mem_size_t ptr, ptr2;
struct mem *mem, *mem2;
u8_t local_mem_free_count = 0;
if (size == 0) {//如果size=0,啥都不用干
return NULL;
}
size = LWIP_MEM_ALIGN_SIZE(size);//内存对齐
if(size < MIN_SIZE_ALIGNED) {//分配的最小内存块必须大于MIN_SIZE_ALIGNED(12)
size = MIN_SIZE_ALIGNED;
}
if (size > MEM_SIZE_ALIGNED) {//不能大于堆的大小(1600)
return NULL;
}
do {
for (ptr = (u8_t *)lfree - ram; ptr < MEM_SIZE_ALIGNED - size;
ptr = ((struct mem *)&ram[ptr])->next) //ptr取到偏移量
{
mem = (struct mem *)&ram[ptr];
if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {
//找到第一个满足要求的block
if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size +
SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) {
//并不是只要满足就分配,要检查是不是可以分割,即如果mem跟mem->next
之间的内存是不是大于size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED
,如果这样的话就要把这之间的内存分成两块,代码如下,还以比较易懂的,就是涉及到?
思父鲋刚肜嘈妥缓椭刚氲母摹?
ptr2 = ptr + SIZEOF_STRUCT_MEM + size;//这里分配的内存包含了结构体
mem的大小
mem2 = (struct mem *)&ram[ptr2];
mem2->used = 0;
mem2->next = mem->next;
mem2->prev = ptr;
mem->next = ptr2;
mem->used = 1;
if (mem2->next != MEM_SIZE_ALIGNED) {
((struct mem *)&ram[mem2->next])->prev = ptr2;
}
} else {//内存不够分割,那就直接分配
mem->used = 1;
}//end if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size +
SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED))
if (mem == lfree) {//调整lfree指针
while (lfree->used && lfree != ram_end) {
lfree = (struct mem *)&ram[lfree->next];
}
}
return (u8_t *)mem + SIZEOF_STRUCT_MEM;//注意这里返回的是mem +
SIZEOF_STRUCT_MEM
,这就说明了当操作系统给我们分配内存的时候并不只是所申请的大小,实际要大几个字?
冢蛭郊右徊糠止芾斫峁固宓拇郏苁褂玫哪诖媸谴觤em + SIZEOF_STRUCT_MEM
开始的。
}//end for (ptr = (u8_t *)lfree - ram; ptr < MEM_SIZE_ALIGNED - size;
ptr = ((struct mem *)&ram[ptr])->next)
}
} while(local_mem_free_count != 0); //end do
return NULL;//失败了,就返回NULL
}
既然知道怎么分配了那回收肯定就是分配的逆操作了,看一下这个函数,也是删去了不必?
膁ebug,assert等
Void mem_free(void *rmem)
{
struct mem *mem;
if (rmem == NULL) {
return;
}
if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
return;
}//上面的都是一个越界判断
mem = (struct mem *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);
//直接就取到对应此块内存的结构体,
mem->used = 0;
if (mem < lfree) {
lfree = mem;
}
plug_holes(mem);
}
下面来看一个plug_holes
这个函数,它的作用就是看看回收的内存能否跟前面的内存合并,就是消除空隙,这个跟?
诖婊厥蘸芟嗨?
static void plug_holes(struct mem *mem)
{
struct mem *nmem;
struct mem *pmem;
nmem = (struct mem *)&ram[mem->next];//先看看跟后一块内存mem的next能不能合并
if (mem != nmem && nmem->used == 0 && (u8_t *)nmem != (u8_t *)ram_end) {
if (lfree == nmem) {能合并,修改lfree指针
lfree = mem;
}
mem->next = nmem->next;/./能合并,修改next指针
((struct mem *)&ram[nmem->next])->prev = (u8_t *)mem - ram;//能合并修改
prev指针,这里prev跟next都是偏移量,即相对于ram的偏移量
}
pmem = (struct mem *)&ram[mem->prev];
//再看看跟前一块内存块能不能合并,跟上面相似
if (pmem != mem && pmem->used == 0) {
if (lfree == mem) {
lfree = pmem;
}
pmem->next = mem->next;
((struct mem *)&ram[mem->next])->prev = (u8_t *)pmem - ram;
}、
到此回收内存就完成了。
还有realloc函数,alloc函数,都相似的说。先写这些,过几天再把Pool
类型的实现补充上来