我们回顾一下nginx内存池的结构体
create_pool开辟指定大小的内存块。都是一块一块分配的。每个内存池都有一个头,第一个内存块的头部信息比较详细。
同一个颜色表示同一个变量的里面的具体内容。
last指向的是可用内存的起始地址
end指向的是可用内存的末尾地址
current指向的是现在内存的起始地址,表示现在这个内存块是可以分配内存的
向内存池申请内存在nginx里有3个函数:
上面2个函数的区别是传入的参数,最上面那个考虑内存对齐,上面那个不考虑内存对齐
还有一个内存申请函数:
这个函数在内存开辟完之后,会给内存清0
剖析ngx_palloc
如何从nginx内存池申请内存???
传入的参数是内存池的起始地址,想向申请申请的内存的字节数
如果申请的内存字节数<=pool->max,就调用小块内存申请函数。
否则调用大块内存申请函数。
max是小块内存的上限。
max不可能超过1个页面的。
就是这块大小size,整个内存大小减去内存头
如果这白色块的大小小于一个页面,max记录的就是这块申请的大小,如果白色块的大小大于一个页面,max不可能超过1个页面的大小。
size如果小于等于max,就进入小块内存分配的函数
小块内存分配函数分析
current指向的是哪个内存池,就从哪个内存池开始分配,current赋值给p指针
然后进行do while循环
m指向可分配内存的起始地址
如果align为1,就是考虑内存对齐。
把m指针调整成平台相关的unsigned long的整数倍(调整到4字节或者8字节的整数倍)。
看第2个if语句:
然后下面的白色块大小如果大于等于size的话,这块白色内存最大的就是max,如果超过1个页面,size只能是一个页面的大小。内存池空闲的内存空间是大于等于申请的内存空间,当然就可以分配了。
然后把m指针偏移size个字节。
相当于在内存池给应用程序分配内存了。
然后return m了(m的位置没有改变,代表是分配内存的起始地址)
我们再来看另外1种情况
现在开始分配内存,如果end-m是10字节,但是size是需要分配20字节,也就是说,在这个内存池里面不够分配了。就不进入if语句执行了。然后执行下面这句
但是现在只有这1块内存块。p->next在刚开始初始化的时候是空。
所以p为空,就退出while循环了。
内存没有分配成功,就直接进入下面的函数,再去开辟内存块:
我们进入到这个函数里面:
我们一句一句看:
开辟和我们之前开辟的内存块一样大小的内存块,所以是用end-pool
然后开始开辟了
上面的黄色字是宏,如果没有特定平台的宏,调用的就是malloc,返回值给m
然后执行:
如图所示:
然后执行
下面的这些信息只需要存储在开辟的第一个内存块就可以了,后面开辟的内存块不需要这些信息,只需要存储管理当前内存块内存分配的4个指针项就可以了。
分配size大小内存:
每一个内存块都有last和end指向空闲内存空间的起始地址和末尾地址
然后执行for循环:
p=pool->current指向的是开辟的第一个内存块
如果p->d.next不为空。
然后判断:
刚开始,failed初始化是0,0+1=1,小于4,如果是大于4,会怎么样?
现在刚开始,就只申请了2个内存块
for循环是进不去的,因为p->d.next直接为空了,for循环直接跳出去了。
执行
把这2个内存块给连接起来了!
选择假设后边还有内存块分配的话,比如说再申请个11字节的内存块。
它都是从第一个开辟的内存块(current指针)开始判断,
一分配,发现第一个内存块剩余的空间只有10字节,end-m 不够,所以就要跳到下一个内存块
然后p=p->d.next,指向了第二个内存块,做相同的操作。
第二个内存块剩余60字节,大于size(11),就分配上了
last指针向下走,给外边分配11字节的内存空间。然后return掉。
假如连续进行内存申请,申请的内存比较大,也就是说,每次去遍历已有的小块内存的时候,发现都获取不到相应大小的内存块,都是缺少一些字节 。那只能重新开辟一块内存池了。开辟完后,还要做上面所叙述的for循环,把前面都循环一下,前面的内存块的failed都要加加一下。
如果说某个内块的failed大于4了
也就是这个内存块分配了4次都没有成功过,说明当前的内存块剩余的空间非常非常小了,几乎是分配完了,当前内存块就不再使用了。就把pool的current指向下一个内存块,从下边开始遍历,根据failed的值判断下去,加加下去,以此类推。
failed记录当前内存块内存分配失败的次数。