malloc的一点点东西

提问:malloc是不是线程安全的?

提问:malloc是如何实现的?

线程安全:需要解决多个线程调用函数时访问共享资源的冲突
可重入:需要不再函数内部使用静态或全局数据,不反悔静态或全局数据,也不调用不可重入函数
malloc函数线程安全但不可重入
  malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。为了做到线程安全,加锁保护。递归锁,如果当程序调用malloc函数时收到信号,在信号处理函数里调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行)。
  按上面的场景,不能保证其可重入性,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程调用malloc函数进入内核时,必须等到返回用户空间前才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题

  malloc函数的实质体现在,有一个将可用的内存块连接为一个长长的空闲链表原型:
extern void* malloc(unsigned int num_bytes);用法:#include/#include
  调用malloc函数时。它会沿着空闲链表寻找一个大到足以满足用户请求所需要的内存块,然后将该内存块一分为二,一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节。将分配给用户的那块内存传给用户,剩下的返回到空闲链表。调用free时,将释放的内存块连接到空闲链表上。最后的最后,空闲链表会被切成很多的小内存,如果这时申请一个大的内存谝,那么空闲链表上可能没有可以满足用户要求的片段了,malloc请求延时,并开始在空闲链表上检查各内存片段,进行整理合并,我觉得像是伙伴算法

在操作系统中
  Unix环境高级编程:大多数实现所分配的存储空间比所要求的要稍大一下,额外的空间用来记录管理信息–分配块的长度,指向下一个分配快的指针等等,这酒意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现,将指向分配块的指针向后移动也可能会改写本块的管理信息malloc:

struct mem_control_block
{
	int is_available;
	int size;
};

  malloc_init初始化内存分配层序的函数:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者当前中断点。sbrk(0)根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点,使用参数0只是返回当前中断点

void malloc_init()
{
	last_valid_address=sbrk(0);
	managed_memory_start=last_valid_address;
	has_initalized=1;
}

  调用malloc的程序不必知道这个结构,在返回指针之前,会将其移动到这个结构之后,把它隐藏起来,使得返回的指针指向没有用于任何其他用于的内存,从调用者的角度来看,它们所得到的都是空的内存。任何通过free将该指针传递回来时,只需要倒退几个字节就可以再次找到这个结构

void free(void*  firstbyte)
{
	struct mem_control_block* mcb;
	mcb=firstbyte-sizeof(struct men_control_block);
	mcb->is_available=1;
	return ;
}

  glibc维护了不只一个不定长的内存块链表,而是好几个,每一个这种链表负责一个大小范围,这种做法有效减少了分配大内存时的遍历开销
  glibc另外的策略就是不知维护一类空闲链表,而是另外再维护一个缓冲链表和一个高速缓冲链表
  在分配的时候首先在高速缓存中查找,失败之后再在空闲链表查找,如果找到的内存块比较大,那么将切割之后的剩余内存块插入到缓存链表如果空闲链表查找失败那么就往缓存链表中查找,如果还是没有合适的空闲块,就向内存申请比请求数更好的内存块,然后把剩下的内存放入链表中

  Linux维护一个break指针,这个指针指向堆空间的某个地址,从堆起始地址到break之间的地址空间为映射好的,可以供进程访问,从break往上,是未映射的地址空间,如果访问这段空间则程序会报错
    要增加一个进程实际的可用堆大小,就需要将break指针向高地址移动,Linux通过brk和sbrk系统调用操作break指针
    int brk(void* addr);
    void* sbrk(intptr_t increment);
  brk将break指针直接设置为某个地址,而sbrk将break从当前位置移动increment所指定的增量。brk在执行成功时返回0,否则返回-1并设置errno为ENOMEN,sbrk成功时返回break移动之前所指向的地址,否则返回(void *)-1
  另外,由于Linux是按页进行内存映射的,所以如果break被设置为没有按页大小对齐,则系统实际上还在最后映射一个完整的页,从而实际已映射的内存空间比break指向的地方要大一些,但是使用break之后的地址是很危险的,经也许break之后确实有一小块可用内存地址

你可能感兴趣的:(c++)