内存管理学习笔记——自定义malloc()与内存池的实现(未完)

最近在巩固算法,这个先做笔记了,有空再精细研究。



知道可以动态申请堆空间,堆空间是系统维护的内存块链表,用户申请时,系统找到合适大小的堆给用户,系统还会把剩下的空间再切割备用,所以会出碎片。

blabla。。。。。。

一直以来困惑一个问题。系统怎么知道那一块大小,送一个指针过去它就释放和malloc对应的空间?大小什么的都记录在哪?


看了一个自实现,想明白一些了:

struct mem_control_block {
	int is_available;
	int size;
};
这些内存块,是有内存控制块的,是否可用,应该就是分配和释放时候用的。分配的时候,查查是否可用,分配成功之后改成不可用,释放的时候,把标记再改为不可用。另外一个size就是分配空间的大小了,这样的话释放空间的时候就知道释放多大了,相应的,这个也应该是分配的时候赋值的,因为用户指定大小。


然后就困惑一个问题,怎么映射?这个结构体,怎么映射到对应空间上?没个指针什么的?

第一反应是协议包的封装,我们封装协议包时,本就是一块连续空间,把包头放在前边,包的内容放在后边而已。其实仔细想想,协议包又何尝不是叫做协议控制块呢,和内存控制块有什么区别么?

所以这样就说通了,真实的内存块可能是如下模样:

+----------+---------------------------------------------------+

| MCB   | MEM_FOR_USER                         |

+----------+---------------------------------------------------+

^           ^

|            |

sys        user

有系统和用户两个指针,给用户第二个指针,就隐藏了内部实现了,然后系统指针在前边就能看到内存控制块。

你猜怎着?下文真是这样说的,这样你free()回传一个指针的时候,我系统直接回滚一个控制块的大小就好了。所以学东西要多思考,多疑问,你想的,可能就是正确答案,这样还能加深印象。

不过,问题也来了,如果真是按这种结构来存,那如果用户自己乱改指针,改到前边的MCB,不就糟了么?也就是假设这个MCB并不是对用户不可见的,只是没给用户直接访问途径。这样一来,一个误操作就能导致最后内存的释放不正确!可能释放少,也可能释放多。前者可能会导致内存”丢失“?,后者可能会导致地址”重合“?这会不会出什么大乱子?!系统有没有什么容错机制?(其实这篇帖子就是自己实现malloc的,看看他有什么高招)

先做个基于原生malloc()的破坏性试验试试?没想好怎么测试,因为释放的内存也不是把内容全都置零,指针传一次给free()再传一次也不会提示错,所以怎么验证释放成功与失败也是个问题。

或者强行提取内存内容,看看有没有数值和申请的内存大小一致。

void*指针不让提取,用个int*指针试试看


上图可以看到,往前退一个int地址(4字节)后,打印出了105(再往前或者往后都是0),不知是不是巧合100+5?,100个地址给用户,5个地址存状态?当然,要指出一点,上文给出的实现,其实浪费空间了,想知道一个内存块是否分配,是不需要一个int型的,一个char型足矣,一个char型加一个int型的话,就刚好是5地址。


malloc()换个大小的再试试:

分配50大小,打印出的前两格的值是57,多7个,所以上边的假设也不成立。

分配10大小,打印17

分配200,打印209

毫无规律,当然也不排除提取地址不对的可能性,但是整体数量级差距不大,只是具体数值是多几的问题上,所以更像是细节上的问题。

另外一个问题也先提出来吧,对这个疑问可能有些帮助,分配给你的空间,一定是准确的你指定的大小么?多余的都完整截去当碎片重新分配了?或者分配出来本来就有零头?

找到这样一句话(但这说的是sbrk(),而自己的实现没处理,不确定原生malloc()也是这样不做处理。):

  • 由于 sbrk() 可能会交回比我们请求的更多的内存,所以在堆(heap)的末端 会遗漏一些内存。


另外,free()操作时:

传array释放之后,指向前4字节位置的ptr的内容也从近似内存片段大小片成一个野数,比如135169。

前8字节位置的0还是0,没变化。


另外一个问题就是:所谓系统维护一个”链表“,通过链表来找到一个又一个的内存块,那这个链表什么样?其实应该一个非常简单的链表就好了,因为所需的信息被储存在内存块的前半部分了,不用在链表结构多下工夫。


anyway,不在这个细节上浪费时间了,再看看那个malloc实现。

=========================================================================================================================


所以,这就是自实现的free()函数

void free(void *firstbyte) {
	struct mem_control_block *mcb;
	/* Backup from the given pointer to find the
	 * mem_control_block
	 */
	mcb = firstbyte - sizeof(struct mem_control_block);
	/* Mark the block as being available */
	mcb->is_available = 1;
	/* That's It!  We're done. */
	return;
}
回退一个结构体,赋值指针,然后给成员is_available设置成可用,这里并没有去管那个size,和上边推测的free()有出入,前提是上边的推测是正确的,不过也可能实现不太一样,位置不一样(不过再不一样也不能那么巧合的只在数值差几了~)。


malloc的实现例子(缺点多多,只是一个参考):

void *malloc(long numbytes) {
	/* Holds where we are looking in memory */
	void *current_location;
	/* This is the same as current_location, but cast to a
	 * memory_control_block
	 */
	struct mem_control_block *current_location_mcb;
	/* This is the memory location we will return.  It will
	 * be set to 0 until we find something suitable
	 */
	void *memory_location;
	/* Initialize if we haven't already done so */
	if(! has_initialized) 	{
		malloc_init();
	}
	/* The memory we search for has to include the memory
	 * control block, but the users of malloc don't need
	 * to know this, so we'll just add it in for them.
	 */
	numbytes = numbytes + sizeof(struct mem_control_block);
	/* Set memory_location to 0 until we find a suitable
	 * location
	 */
	memory_location = 0;
	/* Begin searching at the start of managed memory */
	current_location = managed_memory_start;
	/* Keep going until we have searched all allocated space */
	while(current_location != last_valid_address)
	{
		/* current_location and current_location_mcb point
		 * to the same address.  However, current_location_mcb
		 * is of the correct type, so we can use it as a struct.
		 * current_location is a void pointer so we can use it
		 * to calculate addresses.
		 */
		current_location_mcb =
			(struct mem_control_block *)current_location;
		if(current_location_mcb->is_available)
		{
			if(current_location_mcb->size >= numbytes)
			{
				/* Woohoo!  We've found an open,
				 * appropriately-size location.
				 */
				/* It is no longer available */
				current_location_mcb->is_available = 0;
				/* We own it */
				memory_location = current_location;
				/* Leave the loop */
				break;
			}
		}
		/* If we made it here, it's because the Current memory
		 * block not suitable; move to the next one
		 */
		current_location = current_location +
			current_location_mcb->size;
	}
	/* If we still don't have a valid location, we'll
	 * have to ask the operating system for more memory
	 */
	if(! memory_location)
	{
		/* Move the program break numbytes further */
		sbrk(numbytes);
		/* The new memory will be where the last valid
		 * address left off
		 */
		memory_location = last_valid_address;
		/* We'll move the last valid address forward
		 * numbytes
		 */
		last_valid_address = last_valid_address + numbytes;
		/* We need to initialize the mem_control_block */
		current_location_mcb = memory_location;
		current_location_mcb->is_available = 0;
		current_location_mcb->size = numbytes;
	}
	/* Now, no matter what (well, except for error conditions),
	 * memory_location has the address of the memory, including
	 * the mem_control_block
	 */
	/* Move the pointer past the mem_control_block */
	memory_location = memory_location + sizeof(struct mem_control_block);
	/* Return the pointer */
	return memory_location;
 }
这是原文:https://www.ibm.com/developerworks/cn/linux/l-memory/index.html



小结:

这些东西各有优缺点,是根据使用场景进行的优化选择,有Doug Lea Malloc、BSD Malloc与Hoard等。

直接重写malloc(),也算是一种实现内存管理的方式。

malloc()这种形式管理内存会遇到很多麻烦,比如到底是申请者还是使用者来释放,生存期太长,控制不好会不会爆。


===========================================================================================================


内存池:

内存池可以分阶段划分,多个阶段用多个不同的池子,过了阶段直接抹掉。还可以注册清除函数来完成析构。



后边的眼晕,先不看了




另外,看一个例子,malloc8个字节,使用时候不小心超了,然后再free就会错。

http://www.cnblogs.com/chuyuhuashi/p/4480117.html








猜你喜欢

转载自blog.csdn.net/huqinweI987/article/details/50857897