Linux内核定义了“零页面”(内容全为0的一个物理页,且物理地址固定),应用层的内存分配请求,如栈扩展、堆分配、静态分配等,分配线性地址后,就将页表项条目指向“零页面”(指定初始值的情况除外),这样“零页面”就被所有进程共享,当向页面执行写入操作时,内核就会新分配一个物理页,实行“写时拷贝”操作,这样就实现了物理页面的延迟分配(如果只有读没有写,则无需另分配物理页)。
动态内存通过glibc库的malloc函数分配,当现有地址空间不够时(即malloc维护的空闲链表中没有足够空间),就调用brk/sbrk扩展进程空间的相关线性区段,然后将对应页表项条目指向“零页面”,写操作时再分配新页面。这样,只要进程空间足够大,malloc操作总是成功的——即使申请的内存大于系统物理内存——失败会发生在为“写时拷贝”分配新页面的时候,当系统可分配内存不足时,且有进程要求分配新页面,内核会Kill该进程,并打印类似“Out of Memory: Killed process xx (xxx).”的信息。这就是Linux的“OOM Killer”特性。
因此不能通过检测malloc的指针是否为NULL,来判定系统能否支持申请的内存大小(即空闲内存大于申请内存)。
malloc操作示意图
malloc分配的这种机制,给应用层判断动态内存是否可分配增加了难度。因为内存不足的错误被延迟到写入操作的时候,而在这里内核没有提供相关接口给应用层(只是发送SEGV信号给临死的进程)。