avr-libc malloc/free的实现

avr-libc是AVR单片机C语言运行库,它提供了GNU Toolset的AVR版本(Binutils, GCC, GDB, etc.),它是nongnu.org下的一个项目,以Modified BSD License发布。想看源码的同学可去其网站自行下载:

Home Page:http://www.nongnu.org/avr-libc/ Detail Page:http://savannah.nongnu.org/projects/avr-libc/

当然,也可以用:

svn checkout svn://svn.sv.gnu.org/avr-libc/trunk avr-libc

check最新版本的源码。

我check了两次,都报svn: E155009错误,于是换了一个稳定的release版check:

svn checkout svn://svn.sv.gnu.org/avr-libc/tags/avr-libc-1_8_0-release avr-libc-1_8_0

这次正常,没有报错。可以用tree命令列出整个项目的结构,

我们要看的malloc位于avr-libc/libc/stdlib下面.这里和malloc过程相关的一共4个内部文件:

sectionname.h

stdlib_private.h

malloc.c

realloc.c

这些代码中给出的注释已经比较详细,这里我主要以图示的方法对各个步骤进行演示。为方便阅读,部分注释、测试代码、版权声明已被删除。版权声明请参照原始源码。

stdlib_private.h

sectioinname.h里仅有几行代码,其作用是让编译器正确安放代码对应的存储区段,和这里的主题没有多少关系,可以直接跳过;

先看stdlib_private.h里的代码:

#if !defined(__DOXYGEN__)

struct __freelist {
	size_t sz; // size
	struct __freelist *nx; // next
}; // 空闲链表 节点

#endif

extern char *__brkval;		/* first location not yet allocated */
extern struct __freelist *__flp; /* freelist pointer (head of freelist) */
extern size_t __malloc_margin;	/* user-changeable before the first malloc() */
extern char *__malloc_heap_start;
extern char *__malloc_heap_end;

extern char __heap_start;
extern char __heap_end;

/* Needed for definition of AVR_STACK_POINTER_REG. */
#include <avr/io.h>

#define STACK_POINTER() ((char *)AVR_STACK_POINTER_REG)

stdlib_private.h里定义了freelist的节点结构,以及malloc.c,realloc.c里都要访问的几个全局变量。

由freelist的节点是这样的:

malloc

malloc.c里实现了malloc和free,和前篇Keil实现版相比,二者大体思路非常相似,但又有区别。先看代码(注释比较详细):

void *
malloc(size_t len)
{
	struct __freelist *fp1, *fp2, *sfp1, *sfp2;
	char *cp;
	size_t s, avail;

	/*
	 * Our minimum chunk size is the size of a pointer (plus the
	 * size of the "sz" field, but we don't need to account for
	 * this), otherwise we could not possibly fit a freelist entry
	 * into the chunk later.
	 */ // malloc要交出的区块,至少是一个指针的大小(后面将会看到原因)
	if (len < sizeof(struct __freelist) - sizeof(size_t))
		len = sizeof(struct __freelist) - sizeof(size_t);

	/*
	 * First, walk the free list and try finding a chunk that
	 * would match exactly.  If we found one, we are done.  While
	 * walking, note down the smallest chunk we found that would
	 * still fit the request -- we need it for step 2.
	 */
	for (s = 0, fp1 = __flp, fp2 = 0;
	     fp1;                          // 走到头了,跳出for
	     fp2 = fp1, fp1 = fp1->nx) { // fp1走在fp2的前面(fp2->next==fp1)
		if (fp1->sz < len) // 不够用?继续找...
			continue;
		if (fp1->sz == len) { // case 1. 正好的区块
			/*
			 * Found it.  Disconnect the chunk from the
			 * freelist, and return it.
			 */
			if (fp2)
				fp2->nx = fp1->nx;
			else
				__flp = fp1->nx; // fp2 == 0, 此时fp1指向的是freelist head

			// 注意,这里返回的是nx域的地址
			return &(fp1->nx);
		}
		else { // 够大!
			if (s == 0 || fp1->sz < s) {
				/* this is the smallest chunk found so far */
				s = fp1->sz; // s 是当前已经找到的“够用的”chunk中最小的了
				sfp1 = fp1;
				sfp2 = fp2;
			}
		}
	}
	/*
	 * Step 2: If we found a chunk on the freelist that would fit
	 * (but was too large), look it up again and use it, since it
	 * is our closest match now.  Since the freelist entry needs
	 * to be split into two entries then, watch out that the
	 * difference between the requested size and the size of the
	 * chunk found is large enough for another freelist entry; if
	 * not, just enlarge the request size to what we have found,
	 * and use the entire chunk.
	 */
	if (s) { // freelist上有足够大的chunk
		if (s - len < sizeof(struct __freelist)) { // case 2. (当前块) 剩下的空间大小不足放一个结点
			/* Disconnect it from freelist and return it. */
			if (sfp2)
				sfp2->nx = sfp1->nx;
			else
				__flp = sfp1->nx;
			return &(sfp1->nx);
		}
		/*
		 * Split them up.  Note that we leave the first part
		 * as the new (smaller) freelist entry, and return the
		 * upper portion to the caller.  This saves us the
		 * work to fix up the freelist chain; we just need to
		 * fixup the size of the current entry, and note down
		 * the size of the new chunk before returning it to
		 * the caller.
		 */ // case 3. (当前块)剩余空间够放一个节点 则 进行切割,一分为二
		cp = (char *)sfp1;
		s -= len;
		cp += s;
		sfp2 = (struct __freelist *)cp;
		sfp2->sz = len;
		sfp1->sz = s - sizeof(size_t);
		return &(sfp2->nx);
	}
	// freelist上没有足够大的chunk了
	/*
	 * Step 3: If the request could not be satisfied from a
	 * freelist entry, just prepare a new chunk.  This means we
	 * need to obtain more memory first.  The largest address just
	 * not allocated so far is remembered in the brkval variable.
	 * Under Unix, the "break value" was the end of the data
	 * segment as dynamically requested from the operating system.
	 * Since we don't have an operating system, just make sure
	 * that we don't collide with the stack.
	 */
	if (__brkval == 0)
		__brkval = __malloc_heap_start;
	cp = __malloc_heap_end; // __malloc_heap_start, __malloc_heap_end应该由用户在malloc调用前设置好。
	if (cp == 0)
		cp = STACK_POINTER() - __malloc_margin; // 给栈空间预留 __malloc_margin 字节的内存。防止(堆、栈)碰撞!
	if (cp <= __brkval)
	  /*
	   * Memory exhausted.
	   */
	  return 0;
	avail = cp - __brkval; // 计算 剩余可用空间
	/*
	 * Both tests below are needed to catch the case len >= 0xfffe.
	 */
	if (avail >= len && avail >= len + sizeof(size_t)) {
		fp1 = (struct __freelist *)__brkval;
		__brkval += len + sizeof(size_t); // heap “增长”
		fp1->sz = len;
		return &(fp1->nx);
	}
	/*
	 * Step 4: There's no help, just fail. :-/
	 */
	return 0;
}

第一个for循环遍历链表,

如果当前chunk不够大,就继续往后找;

如果大小正好,就将这个块(chunk)从空闲链表(freelist)上取下来(删除),并返回。删除要注意——当前的chunk是不是在freelist的头部;如果是,就把freelist头指针往后移一下;这里还要注意——返回的是&(fp1->nx),连同前面的条件if(fp1->sz==len)说明freelist一个节点上的sz表示的chunk空间包括nx域的大小(这点与Keil不同)。即一个chunk如下所示:

如果大了,也继续往后,并且记录下到目前为止最小的(这样到最后就找到了最小的)。

到Step 2,已经找到了一个可用的chunk,它是所有比len(我们所需的)大的chunk中最小的一个;

接下来看看它是不是只比我们需要的大一点?如果是,即多出的空间不够放一个节点,那我们没办法将它作为一个chunk挂到freelist上,直接返回;

否则,说明可以在多出的内存上建立一个chunk(自如可以将它挂上freelist),需要将这个chunk切为两半;

完成这一工作的就是这几行代码:

		cp = (char *)sfp1;
		s -= len;
		cp += s;
		sfp2 = (struct __freelist *)cp;
		sfp2->sz = len;
		sfp1->sz = s - sizeof(size_t);
		return &(sfp2->nx);

下面以图形展示这几行代码的执行过程。这里应当明确,执行这些代码之前s为多少,sfp1、sfp2指向何处?sfp1指向那个“最合适的”(所有比len大的中最小的)sfp2紧随其后,s为这个chunk携带的内存大小:

这三行“

cp = (char *)sfp1;

s -= len;

cp += s;

”执行完之后s,cp如下(其他没变),cp处将被切开,马上就能看到,

接下来“

sfp2 = (struct __freelist *)cp;

sfp2->sz = len;

”如同刚才的cp处已经建立了一个新的freelist节点,这里记录sz的作用是为了以后free(p)之时能够知道p所属的chunk有多大。

接着“

sfp1->sz = s - sizeof(size_t);

”更新原来chunk的sz,

大功告成!可以上交了,

图中ret指示的就是malloc实际返回的地址,malloc(len)想要取得的内存已经到手!

malloc的最后还有几行是最后一种情况,即整个链表上没有一个chunk可以满足要求(第一次调用也是这种情况,因为全局变量__flp,__brkval的初值都是0);

通过注释step 3可以知道:这里要准备一个新的chunk,也就是要获取更多的内存。

这段代码和__brkval变量相关,另外和STACK_POINTER(SP)也相关,这里涉及到内存布局的问题,__brkval,SP,__heap_start,__heap_end的关系可以从下图有个大致的了解:

(图片来自http://www.nongnu.org/avr-libc/user-manual/malloc.html

从图上可以看到stack和heap有一段公用的空间,而且增长方向想对,这就有发生碰撞(collide)的可能,而__malloc_margin的作用就是用来防止发生碰撞。

通过代码“

__brkval += len + sizeof(size_t);

”可以知道__brkval实际上是heap的上界。每次freelist不能满足malloc的请求,同时“堆栈间隙”的空间够用时,heap都会增长,并更新__brkval。

free

有了malloc的一番分析,free的代码就很容易看懂了,

void
free(void *p)
{
	struct __freelist *fp1, *fp2, *fpnew;
	char *cp1, *cp2, *cpnew;

	/* ISO C says free(NULL) must be a no-op */
	if (p == 0)
		return;

	cpnew = p;
	cpnew -= sizeof(size_t);
	fpnew = (struct __freelist *)cpnew;
	fpnew->nx = 0;

	/*
	 * Trivial case first: if there's no freelist yet, our entry
	 * will be the only one on it.  If this is the last entry, we
	 * can reduce __brkval instead.
	 */
	if (__flp == 0) { // freelist 为空
		if ((char *)p + fpnew->sz == __brkval) // fpnew->sz == __brkval - (char*)p, 要free的是最后一个chunk
			__brkval = cpnew; // heap “缩小”
		else
			__flp = fpnew;
		return;
	}

	/*
	 * Now, find the position where our new entry belongs onto the
	 * freelist.  Try to aggregate the chunk with adjacent chunks
	 * if possible.
	 */
	for (fp1 = __flp, fp2 = 0;
	     fp1;
	     fp2 = fp1, fp1 = fp1->nx) { // fp1走在fp2的前面(fp2->next == fp1)
		if (fp1 < fpnew)
			continue;
		// fp1 > fpnew, fpnew > fp1
		cp1 = (char *)fp1;
		fpnew->nx = fp1;
		if ((char *)&(fpnew->nx) + fpnew->sz == cp1) {
			/* upper chunk adjacent, assimilate it */ // 和后面的chunk合并
			fpnew->sz += fp1->sz + sizeof(size_t);
			fpnew->nx = fp1->nx;
		}
		if (fp2 == 0) {
			/* new head of freelist */
			__flp = fpnew;
			return;
		}
		break;
	}
	/*
	 * Note that we get here either if we hit the "break" above,
	 * or if we fell off the end of the loop.  The latter means
	 * we've got a new topmost chunk.  Either way, try aggregating
	 * with the lower chunk if possible.
	 */
	fp2->nx = fpnew;
	cp2 = (char *)&(fp2->nx);
	if (cp2 + fp2->sz == cpnew) {// 可以和前面的节点合并
		/* lower junk adjacent, merge */ // 和前面的chunk合并
		fp2->sz += fpnew->
		sz + sizeof(size_t);fp2->
		nx = fpnew->nx;
	}
	/*
	 * If there's a new topmost chunk, lower __brkval instead.
	 */
	for (fp1 = __flp, fp2 = 0;
	     fp1->nx != 0;
	     fp2 = fp1, fp1 = fp1->nx)
		/* advance to entry just before end of list */;
		cp2 = (char *)&(fp1->nx);
	if (cp2 + fp1->sz == __brkval) {
		if (fp2 == NULL)/* Freelist is empty now. */
			__flp = NULL;
		else
			fp2->
		nx = NULL;
		__brkval = cp2 - sizeof(size_t);
	}
}

开头的几行“

cpnew = p;

cpnew -= sizeof(size_t);

fpnew = (struct __freelist *)cpnew;

”执行后,fpnew即得到了chunk的实际地址(malloc的切口),p与cpnew、fpnew关系如下,

接着,如果freelist为空,就把当前chunk加到freelist上,如果((char *)p + fpnew->sz == __brkval),由前面的分析已经知道__brkval是heap的上界,这里的__brkval = cpnew;就是下调heap的上界。

接着是一个for循环,for循环内,开头是:

if (fp1 < fpnew)

continue;

for循环内最后有个break;很明显从continue到break的代码只会执行一遍,写到for循环后面的话作用也一样。也就是:

	for (fp1 = __flp, fp2 = 0;
        fp1;
        fp2 = fp1, fp1 = fp1->nx) { // fp1走在fp2的前面(fp2->next == fp1)
		if (fp1 >= fpnew)
			break;
	}

	// fp1 > fpnew > fp2, 找到了fpnew前后的节点
	cp1 = (char *)fp1;
	fpnew->nx = fp1;
	if ((char *)&(fpnew->nx) + fpnew->sz == cp1) { // 和 后面的节点“相邻”
		/* upper chunk adjacent, assimilate it */
		fpnew->sz += fp1->sz + sizeof(size_t);
		fpnew->nx = fp1->nx;
	}
	if (fp2 == 0) {
		/* new head of freelist */
		__flp = fpnew;
		return;
	}

for循环的作用很明显,是要找当前chunk应该插入到freelist的位置;

for循环结束后有几种可能情况,处理情况类似,下面仅一其中一种,图形化展示之。

case 1 当前chunk(fpnew)可以和后面的chunk(fp1)合并

这种情况对应 (char *)&(fpnew->nx) + fpnew->sz == cp1 成立。

for循环结束时,fp1, fp2可能的情况如下:

free的任务就是将中间灰色的chunk重新“挂”到freelist上,同时还要检查是否能够合并(和fp1或fp2所指chunk相邻),如果能够合并,则将该chunk和它相邻的chunk合并起来。

接下来是:

cp1 = (char *)fp1;

fpnew->nx = fp1;

将当前chunk的nx域与后面的chunk连接起来:

其后的代码;

if ((char *)&(fpnew->nx) + fpnew->sz == cp1) 对应的状态:

接下来:

if ((char*)&(fpnew->nx)
+ fpnew->sz ==cp1) {

/*upperchunk adjacent, assimilate it */

fpnew->sz+=
fp1->
sz+ sizeof(size_t);

fpnew->nx
=fp1->nx;

}

更新fpnew->sz,对应的状态:

接着是:

if ((char*)&(fpnew->nx)
+ fpnew->sz ==cp1) {

/*upperchunk adjacent, assimilate it */

fpnew->sz
+=fp1->sz +sizeof(size_t);

fpnew->nx=
fp1->
nx;

}

更新fpnew->nx,对应的状态:

接下来是必然会执行的:

fp2->nx=fpnew;//
make a new link

对应着:

至此,能够和后面chunk合并这一情况对应的free工作完成。

(ps: 画图太累,其他几种情况就不再画图了。)

扩展阅读

这篇文章是去年我在看完Keil malloc之后写的,我的另一篇关于Keil内存管理的文章:

Pooled Allocation(池式分配)实例——Keil 内存管理

除此之外,avr-libc项目的源码可以在线浏览:

arv-libc/libc/malloc.c

另外,sdcc(Small Device C Compiler)是一个开源的单片机编译器,它也实现了malloc和free,项目首页:

http://sdcc.sourceforge.net/ 感兴趣的同学可自己下载源码。

时间: 2024-10-10 06:37:48

avr-libc malloc/free的实现的相关文章

redis应用之安装配置介绍

一.redis介绍: 1.redis定义: Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主持.redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).这些数据类型都

centos 6.8安装redis

1. 下载到redis下载页面https://redis.io/download下载对应版本的reids安装包,如:redis-${version}.tar.gz . 2. 安装redis的详细安装步骤在安装包中的README.md文件中有详细说明,请详细阅读.以安装redis-4.0.1.tar.gz为例说明. [[email protected]]# tar xvf redis-4.0.1.tar.gz [[email protected]]# cd redis-4.0.1 [[email 

SylixOS移植Redis库总结

1. Redis简介 Redis是一个开源软件项目(BSD许可),用ANSI C编写,适用于大多数的POSIX系统,是一个可用作数据库.缓存和消息代理的内存数据库.Redis是一个非关系型数据库,Redis可以存储键与五种不同数据结构类型之间的映射,这五种类型分别为:字符串.列表.集合.有序集合和散列.Redis通常将整个数据集保存在内存中,Redis通过两种不同的方式实现持久性:一种是快照,另一种是AOF(AppendOnly File).Redis支持主从复制,来自任何Redis服务器的数据

Redis 2.8.18 安装报错 error: jemalloc/jemalloc.h: No such file or directory解决方法

错误描述 安装Redis 2.8.18时报错: zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directoryzmalloc.h:55:2: error: #error "Newer version of jemalloc required"make[1]: *** [adlist.o] Error 1make[1]: Leaving directory `/data0/src/redis-2.6.2/src

jemalloc/jemalloc.h: No such file or directory

Redis 2.6.9 安装报错,提示: zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directoryzmalloc.h:55:2: error: #error "Newer version of jemalloc required"make[1]: *** [adlist.o] Error 1make[1]: Leaving directory `/data0/src/redis-2.6.2/src'ma

安装新版REDIS

http://redis.io/ # wget http://download.redis.io/redis-stable.tar.gz tar zxvf redis-stable.tar.gz -C /usr/local/ cd /usr/local/redis make  MALLOC=libc ========================================================== To force compiling against libc malloc,

redis安装手册

安装准备 环境:CentOS release 6.8 本次安装版本:redis-3.2.7 redis tar包下载 https://redis.io [[email protected] tools]# tar xf redis-3.2.7.tar.gz [[email protected] tools]# cd redis-3.2.7 [[email protected] redis-3.2.7]# less README.md   --->通过查看readme.md,了解安装方式 Sele

redis安装及redis数据类型

Redis介绍: 一.介绍 在过去的几年中,NoSQL数据库一度成为高并发.海量数据存储解决方案的代名词,与之相应的产品也呈现出雨后春笋般的生机.然而在众多产品中能够脱颖而出的却屈指可数,如Redis.MongoDB.BerkeleyDB和CouchDB等.由于每种产品所拥有的特征不同,因此它们的应用场景也存在着一定的差异,下面仅给出简单的说明: 1). BerkeleyDB是一种极为流行的开源嵌入式数据库,在更多情况下可用于存储引擎,比如BerkeleyDB在被Oracle收购之前曾作为MyS

Python之路【第十篇】Python操作Memcache、Redis、RabbitMQ、SQLAlchemy、

Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached基于一个存储键/值对的hashmap.其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信. 1.Memcached安装配置 #安装倚赖包 yum install libevent-devel #安装软件 yum -y