内存碎片
内存在物理上的表现形式为连续的一块一块的电路,在使用上,我们当然倾向于使用相邻的电路组成的内存;但是世事难料,在使用过程中,难免出现一些比较小的内存申请;
例如内存芯片的容量为4,编号为1和2的内存被分配给了程序A,编号为3的内存分配给了程序B,此时程序A运行结束了,释放了1和2,B仍旧保持运行,占有3;
如果仅从数字容量上来看,这块内存芯片,还有3个内存可以使用(编号1,2,4),但是如果真的有一个程序需要申请3个内存,这块芯片又不能完成这个任务
为何?
因为1,2和4,被3给分开了,这就导致看起来还剩下3块,其实是生下了2+1块;这就产生了内存碎片
linux的内存分配机制
页
linux对内存的管理机制中,页是最小的单位,linux选择将内存划为4kb大小的小块进行管理,每一个小块称之为一页;页,是为了方便内存映射而存在的,它可以将不连续的物理内存映射到连续的虚拟内存上,对于用户态的程序,只要能东拼西凑够足够的内存即可;
(通常在linux下,一个页的大小是4kb,不过这个size是可以被调整的)
伙伴算法
页面机制是用来方便映射内存的,内存碎片的问题并没有被解决掉;为了尽量减少内存碎片,linux合入了伙伴算法;
伙伴:
满足以下条件的两个内存块,可以被称为伙伴
- 大小相等
- 地址相邻
- 是从同一个更大的内存块中拆分出来的
伙伴算法原理:
linux维护了一个长度为11的链表,这个链表中的每一个节点,分别管理尺寸为:1个页,2个页,4个页….2^10个页的内存块;
假设当前,有一个内存需要一块大小为4个页面的内存,首先回去array[3]中寻找,如果找到了,就分配;
如果没有找到,就去array[4]中找,如果找到了,则拆分array[4]中,大小为8的内存块,前一块分给程序使用,后一块并入array[3]
伙伴算法并没有完美的解决内存碎片的问题,反而在合并&分裂的时候,很容易产生内存碎片
内核态内存分配的问题
用户态的时候,只要拼凑足够多的页就能解决内存不连续的问题,但是存在一些特殊的程序,他们运行起来必须要求连续的物理内存(例如DMA),这种情况下,伙伴算法反而产生了反作用(由于合并&分配产生了大量的碎片)
__alloc_pages_nodemask为什么会失败?
这个函数的失败场景出现在ifconfig up中
[35088.250898] Call Trace:[35088.255869] [<8101883c>] show_stack+0x54/0x88[35088.264678] [<811e3dfc>] dump_stack+0x94/0xcc[35088.273469] [<810b0dc4>] warn_alloc_failed+0xf8/0x11c[35088.283646] [<810b3780>] __alloc_pages_nodemask+0x750/0x84c[35088.294884] [<810218d0>] mips_dma_alloc_coherent+0x168/0x20c[35088.306291] [<812b0738>] mtk_open+0x4d8/0x55c[35088.315069] [<812f9c0c>] __dev_open+0xd4/0x154[35088.324036] [<812f9f3c>] __dev_change_flags+0xc0/0x17c[35088.334373] [<812fa020>] dev_change_flags+0x28/0x70[35088.344200] [<81310e10>] dev_ifsioc+0xf8/0x33c[35088.353155] [<81311528>] dev_ioctl+0x4d4/0x5e4[35088.362117] [<810fd580>] do_vfs_ioctl+0x5d4/0x63c[35088.371590] [<810fd638>] SyS_ioctl+0x50/0x94[35088.380208] [<810078d8>] syscall_common+0x30/0x54
很明显__alloc_pages_nodemask这个函数是用来分配内存的,但是我很难相信在内存剩余几十兆的时候会出现内存分配错误,因为内存剩余数量明显是足够的;
我注意到了他的上一个调用:dma_alloc_coherent
DMA(direct memory access)需要一段完整的物理内存,但是对于linux而言,物理内存地址是没有办法被直接操作的,因此需要先将物理内存映射到虚拟内存中,这个函数就是做这个事情的;
