goahead源码分析---块内存分配--balloc.c文件

到目前为止,个人发现的goahead的一个特点:
1、函数执行过程中需要的缓冲区都不是由外部提供的,而是什么时候需要什么时候申请,用完后释放,如果申请的内存存放了调用者需要的信息,则将这块内存的地址通过参          数 传出去,同时由调用者负责释放。

2、在HTTP请求等地方频繁的进行了内存的申请和分配。


也许系统的malloc函数有种种缺点,goahead提供了自己的块内存分配函数,其核心文件为balloc.c文件。其设计思想如下:

1.该模块不是用来替代整个系统的内存的管理,他只用来管理程序中指定的一块内存Mem,这块内存可以是动态分配的,也可以是一个大的数组.
2.该模块以块为单位来管理内存,块的大小为16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536.分配时不时你申请多次就分配多少,而是分配一个满足你       申请的大小的最小的块.
3.这些以块为单位的内存并不是一开始就存在的,而是你申请时从Mem中划分出来,只是在你释放的时候并不返还给Mem,返还给管理模块,逐步建立起来不同的块.

4.对于大于最大块的内存,管理模块根据配置来决定是动态分配还是返回分配失败


基于上面所述,要解决如下的问题:


1.必须跟踪Mem的使用情况.系统定义了如下变量来跟踪
static char				*bFreeBuf;				/* Pointer to free memory */
static char				*bFreeNext;				/* Pointer to next free mem */
static int				bFreeSize;				/* Size of free memory */
static int				bFreeLeft;				/* Size of free left for use */


bFreeBuf在初始化的时候指向Mem,bFreeSize记录Mem的大小,bFreeLeft永远指向剩余内存的起始地址,bFreeLeft记录剩余内存的大小.


2.如何描述块内存,同时如何区分已分配的一块内存是来自管理模块分配的块还是动态分配的.系统定义了如下结构体

typedef struct {
	union {
		void	*next;							/* Pointer to next in q */
		int		size;							/* Actual requested size */
	} u;
	int			flags;							/* Per block allocation flags */
} bType;



size用来记录该块的大小(不包括管理块用的内存)
flags用来记录该块的特性,动态分配的还是块内存等.
next用来形成链表.


3.如何管理这些块
这个比较简单,将相同大小的块用next域形成链表,再定义一个数组指向不同块大小的链表的第一个元素.
static bType			*bQhead[B_MAX_CLASS];	/* Per class block q head */

bQhead就是那个数组,整体结构如下图






该模块不适用于多线程,同时假定新定义的变量初值为0.



1.模块打开函数


//参数说明:
//buf:用户指定的一块缓冲区,如果为NULL,则函数根据flags标志来决定是否自己分配
//bufsize:传入的缓冲区大小
//flags:程序中使用的为B_USE_MALLOC,允许分配内存
int bopen(void *buf, int bufsize, int flags)
{
	bFlags = flags;


	//初始化一个随机数产生器函数 不知道做什么用的
	#ifdef BASTARD_TESTING
	srand(time(0L));
	#endif /* BASTARD_TESTING */


/*
 * If bopen already called by a shared process, just increment the count
 * and return;
 */
	if (++bopenCount > 1) {
	return 0;
	}
	//如果传入的为NULL且大小为0则函数自己分配一个默认大小的内存块
	if (buf == NULL) {
		if (bufsize == 0) {
			bufsize = B_DEFAULT_MEM;
		}
//IRIX是一个操作系统 其内存要求4字节对齐
#ifdef IRIX
		bufsize = ROUNDUP4(bufsize);
#endif
		if ((buf = malloc(bufsize)) == NULL) {
         /* resetting bopenCount lets client code decide to attempt to call
          * bopen() again with a smaller memory request, should it desire to.
          * Fix suggested by Simon Byholm.
          */
         		--bopenCount;
			return -1;
		}
//记录动态分配内存的总大小,用于统计内存时使用
#ifdef B_STATS
		bStatsMemMalloc += bufsize;
#endif
	} else {
		bFlags |= B_USER_BUF;
	}


	//对于要管理的内存有总大小记录,剩余大小记录
	bFreeSize = bFreeLeft = bufsize;
	//bFreeBuf为要管理的内存块的起始地址 bFreeNext 为剩余内存的起始地址
	bFreeBuf = bFreeNext = buf;
	//对相同大小的块构成一个链表,bQhead中的每一个元素指向相应大小的可用链表的第一个块
	memset(bQhead, 0, sizeof(bQhead));
	//为了调试,可以填充分配的缓冲区为指定值
#if (defined (B_FILL) || defined (B_VERIFY_CAUSES_SEVERE_OVERHEAD))
	bFillBlock(buf, bufsize);
#endif
//不知道用处
#ifdef B_STATS
	bStackStart = &buf;
#endif
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
	//wzf? 此处调用此函数会有问题
	verifyFreeBlock(buf, bufsize);
#endif
	return 0;
}


2.该模块的关闭操作,当应用程序退出时调用

void bclose()
{
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
	verifyBallocSpace();
#endif
	if (--bopenCount <= 0 && !(bFlags & B_USER_BUF)) {
		free(bFreeBuf);
		bopenCount = 0;
	}
}


3.内存的分配
先放一张流程图





//参数说明:
//B_ARGS_DEC:该宏展开后为一个字符指针,一个整形参数,用来传入调用者所在的文件和行号
//size:请求内存的大小
//对比流程如图,比较容易理解
void *balloc(B_ARGS_DEC, int size)
{
	bType	*bp;
	int		q, memSize;

/*
 *	Call bopen with default values if the application has not yet done so
 */
	//如果没有调用过bopen函数,则在第一次使用时调用
	if (bFreeBuf == NULL) {
		if (bopen(NULL, B_DEFAULT_MEM, 0) < 0) {
			return NULL;
		}
	}
	//验证分配的内存是否有问题
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
	verifyBallocSpace();
#endif
	//检查请求的内存大小是否合法
	if (size < 0) {
		return NULL;
	}

	//应该是用来模拟分配失败的情况
#ifdef BASTARD_TESTING
	if (rand() == 0x7fff) {
		return NULL;
	}
#endif /* BASTARD_TESTING */

	//计算满足请求大小的最小的内存块大小和加上管理开销后的总内存大小
	memSize = ballocGetSize(size, &q);

	//如果块的大小大于最大管理的块大小则根据配置来决定是否动态分配内存
	if (q >= B_MAX_CLASS) {
/*
 *		Size if bigger than the maximum class. Malloc if use has been okayed
 */
		//允许动态分配内存
		if (bFlags & B_USE_MALLOC) {
#ifdef B_STATS
			bstats(0, NULL);
#endif
#ifdef IRIX
			memSize = ROUNDUP4(memSize);
#endif
			bp = (bType*) malloc(memSize);
			if (bp == NULL) {
				traceRaw(T("B: malloc failed\n"));
				return NULL;
			}
#ifdef B_STATS
			bStatsMemMalloc += memSize;
#endif
#if (defined (B_FILL) || defined (B_VERIFY_CAUSES_SEVERE_OVERHEAD))
			bFillBlock(bp, memSize);
#endif

		} else {
			//不允许动态分配则直接返回失败
			traceRaw(T("B: malloc failed\n"));
			return NULL;
		}

/*
 *		the u.size is the actual size allocated for data
 */
		bp->u.size = memSize - sizeof(bType);
		bp->flags = B_MALLOCED;

	} else if ((bp = bQhead[q]) != NULL) {
		//需要的内存在管理的范围之内且有可用块
/*
 *		Take first block off the relevant q if non-empty
 */
		bQhead[q] = bp->u.next;
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
		verifyFreeBlock(bp, q);
#endif
#if (defined (B_FILL) || defined (B_VERIFY_CAUSES_SEVERE_OVERHEAD))
		bFillBlock(bp, memSize);
#endif
		bp->u.size = memSize - sizeof(bType);
		bp->flags = 0;

	} else {
		//需要的块大小在管理的范围内但是当前没有可用的块,如果剩余的可分配内存足够则从剩余内存中划分
		if (bFreeLeft > memSize) {
/*
 *			The q was empty, and the free list has spare memory so 
 *			create a new block out of the primary free block
 */
			bp = (bType*) bFreeNext;
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
			verifyFreeBlock(bp, q);
#endif
			bFreeNext += memSize;
			bFreeLeft -= memSize;
#if (defined (B_FILL) || defined (B_VERIFY_CAUSES_SEVERE_OVERHEAD))
			bFillBlock(bp, memSize);
#endif
			bp->u.size = memSize - sizeof(bType);
			bp->flags = 0;

		} else if (bFlags & B_USE_MALLOC) {
			//如果剩余内存不够但允许动态分配则动态分配
#ifdef B_STATS
			static int once = 0;
			if (once++ == 0) {
				bstats(0, NULL);
			}
#endif
/*
 *			Nothing left on the primary free list, so malloc a new block
 */
#ifdef IRIX
			memSize = ROUNDUP4(memSize);
#endif
			if ((bp = (bType*) malloc(memSize)) == NULL) {
				traceRaw(T("B: malloc failed\n"));
				return NULL;
			}
#ifdef B_STATS
			bStatsMemMalloc += memSize;
#endif
#if (defined (B_FILL) || defined (B_VERIFY_CAUSES_SEVERE_OVERHEAD))
			bFillBlock(bp, memSize);
#endif
			bp->u.size = memSize - sizeof(bType);
			bp->flags = B_MALLOCED;

		} else {
			traceRaw(T("B: malloc failed\n"));
			return NULL;
		}
	}

#ifdef B_STATS
	bStatsAlloc(B_ARGS, bp, q, memSize);
#endif
	bp->flags |= B_INTEGRITY;

/*
 *	The following is a good place to put a breakpoint when trying to reduce
 *	determine and reduce maximum memory use.
 */
#if 0
#ifdef B_STATS
	if (bStatsBallocInUse == bStatsBallocMax) {
		bstats(0, NULL);
	}
#endif
#endif
	return (void*) ((char*) bp + sizeof(bType));
}



4.释放一个不再使用的内存块给管理模块

	a_assert((bp->flags & B_INTEGRITY_MASK) == B_INTEGRITY);

	if ((bp->flags & B_INTEGRITY_MASK) != B_INTEGRITY) {
		return;
	}

	memSize = ballocGetSize(bp->u.size, &q);

#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
	verifyUsedBlock(bp,q);
#endif
#ifdef B_STATS
	bStatsFree(B_ARGS, bp, q, bp->u.size+sizeof(bType));
#endif
	if (bp->flags & B_MALLOCED) {
		free(bp);
		return;
	}
		
#ifdef B_VERIFY_CAUSES_SEVERE_OVERHEAD
	bFillBlock(bp, memSize);
#endif

/*
 *	Simply link onto the head of the relevant q
 */
	bp->u.next = bQhead[q];
	bQhead[q] = bp;

	bp->flags = B_FILL_WORD;
}

5.安全释放函数

这个没什么要说的,主要是检查了下指针是否为空,不为空则调用释放函数

void bfreeSafe(B_ARGS_DEC, void *mp)
{
	if (mp) {
		bfree(B_ARGS, mp);
	}
}

6.在UNICODE编码下复制一个字符串

char *bstrdupA(B_ARGS_DEC, char *s)
{
	char	*cp;
	int		len;

	if (s == NULL) {
		s = "";
	}
	len = strlen(s) + 1;
	//分配一块空间,拷贝字符串
	if (cp = balloc(B_ARGS, len)) {
		strcpy(cp, s);
	}
	return cp;
}

7.重新分配一块内存

重新分配一块较大的内存,并将原来内存中的数据拷贝到新内存中.

要注意的是模块是按块分配的,有可能已分配的内存也满足新请求的大小,此时直接返回原来的内存就可以

void *brealloc(B_ARGS_DEC, void *mp, int newsize)
{
	bType	*bp;
	void	*newbuf;

	//如果是空指针,直接分配返回
	if (mp == NULL) {
		return balloc(B_ARGS, newsize);
	}
	//找到管理数据所在的位置
	bp = (bType*) ((char*) mp - sizeof(bType));
	a_assert((bp->flags & B_INTEGRITY_MASK) == B_INTEGRITY);

/*
 *	If the allocated memory already has enough room just return the previously
 *	allocated address.
 */
	//如果当前块已经满足则返回
	if (bp->u.size >= newsize) {
		return mp;
	}
	//分配新内存,然后拷贝数据
	if ((newbuf = balloc(B_ARGS, newsize)) != NULL) {
		memcpy(newbuf, mp, bp->u.size);
		bfree(B_ARGS, mp);
	}
	return newbuf;
}

8.由块大小计算出其包含管理数据的大小,和该块在bQhead数组中的位置

static int ballocGetSize(int size, int *q)
{
	int	mask;

	//计算
	mask = (size == 0) ? 0 : (size-1) >> B_SHIFT;//B_SHIFT为4  宏定义
	for (*q = 0; mask; mask >>= 1) {
		*q = *q + 1;
	}
	return ((1 << (B_SHIFT + *q)) + sizeof(bType));
}

9.将内存填充为固定值,方便调试和检查

static void bFillBlock(void *buf, int bufsize)
{
	memset(buf, B_FILL_CHAR, bufsize);
}


还差一点内存使用统计和一些小函数
---------未完待续------


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值