本篇文章开始讲解Linux的内存管理,深入了解内存管理有助于我们深入Linux底层逻辑,加强对Linux的学习和了解。
下面是一个32位的Linux进程空间内存排布具体的情况:
内核空间(1GB):
内核空间是操作系统核心代码和数据的所在区域。这部分空间由操作系统内核独占,用于执行操作系统的任务,如操作系统的调度、内存管理、驱动程序和系统调用等。在内核空间中,所有的内核模块和驱动程序都运行,并可以直接访问硬件资源。
用户空间(3GB):
用户空间是给用户程序运行的区域,其中包含了应用程序的代码、数据和堆栈等。用户空间是被操作系统管理和分配的,用户程序在这个空间中运行,并通过系统调用与内核进行交互。用户空间中的进程无法直接访问内核空间中的数据和功能,而必须通过系统调用接口来请求内核的服务。
在用户空间中,可以进一步细分不同的段:
1.代码段:存储可执行程序的指令,即程序的二进制代码。
2.数据段:存储全局变量和静态变量等数据。
3.堆区:用于动态分配内存,通过调用malloc()、new等函数实现。
4.栈区:用于存储函数调用时的局部变量和函数调用的上下文。
这种地址空间的分配方式可以提供对操作系统和应用程序的良好隔离,确保系统的稳定性和安全性。操作系统内核在内核空间中运行,可以对硬件资源进行直接访问和管理,而用户程序在用户空间中运行,通过系统调用接口与内核进行通信,实现各种操作和功能。
当使用malloc函数时会调用到brk系统调用。
brk系统调用用于改变进程的数据段的结束地址,从而调整进程的堆空间大小。当调用malloc函数时,它会向操作系统请求一块指定大小的内存空间。操作系统会检查当前的堆空间大小,并根据需要调整堆的结束地址,以供新的内存分配。
malloc函数会调用brk系统调用来增加堆空间的大小,以适应所需分配的内存大小。如果请求的内存大小超过了当前堆空间的剩余大小,则操作系统会扩展堆空间,使其满足请求。反之,如果请求的内存大小较小,当前剩余的堆空间可能会被重新分割,并返回相应大小的内存块给malloc函数。
调用brk系统调用将会调用到下面的函数:
这个函数的主要作用是根据用户空间的请求调整进程的数据段结束地址,从而动态调整进程的堆空间大小。
SYSCALL_DEFINE1(brk, unsigned long, brk)
{
unsigned long retval;
unsigned long newbrk, oldbrk;
struct mm_struct *mm = current->mm;
struct vm_area_struct *next;
unsigned long min_brk;
bool populate;
if (down_write_killable(&mm->mmap_sem))
return -EINTR;
#ifdef CONFIG_COMPAT_BRK
/*
* CONFIG_COMPAT_BRK can still be overridden by setting
* randomize_va_space to 2, which will still cause mm->start_brk
* to be arbitrarily shifted
*/
if (current->brk_randomized)
min_brk = mm->start_brk;
else
min_brk = mm->end_data;
#else
min_brk = mm->start_brk;
#endif
if (brk < min_brk)
goto out;
/*
* Check against rlimit here. If this check is done later after the test
* of oldbrk with newbrk then it can escape the test and let the data
* segment grow beyond its set limit the in case where the limit is
* not page aligned -Ram Gupta
*/
if (check_data_rlimit(rlimit(RLIMIT_DATA), brk, mm->start_brk,
mm->end_data, mm->start_data))
goto out;
newbrk = PAGE_ALIGN(brk);
oldbrk = PAGE_ALIGN(mm->brk);
if (oldbrk == newbrk)
goto set_brk;
/* Always allow shrinking brk. */
if (brk <= mm->brk) {
if (!do_munmap(mm, newbrk, oldbrk-newbrk))
goto set_brk;
goto out;
}
/* Check against existing mmap mappings. */
next = find_vma(mm, oldbrk);
if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
goto out;
/* Ok, looks good - let it rip. */
if (do_brk(oldbrk, newbrk-oldbrk) < 0)
goto out;
set_brk:
mm->brk = brk;
populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
up_write(&mm->mmap_sem);
if (populate)
mm_populate(oldbrk, newbrk - oldbrk);
return brk;
out:
retval = mm->brk;
up_write(&mm->mmap_sem);
return retval;
}
物理内存是指计算机中实际存在的硬件内存。它是由RAM(Random Access Memory,随机存取内存)或其他类似的硬件组成的,用于存储正在执行的程序和数据。物理内存是直接与计算机的中央处理器(CPU)相连,提供快速、随机访问数据的能力。
虚拟内存是操作系统提供的一种抽象概念。它扩展了可用的内存空间,使得程序可以使用比物理内存更大的地址空间。虚拟内存将程序所需的内存分为连续的地址空间块,称为虚拟地址空间。每个程序都有自己的虚拟地址空间,这使得每个程序能够以独立的方式执行,而不会互相干扰。
虚拟内存的工作原理是将部分程序和数据存储在物理内存中,而将不常用的部分保存在磁盘上的交换文件中。当程序需要访问虚拟内存中的某个地址时,操作系统会根据某种映射机制将虚拟地址转换为物理地址。这个过程被称为虚拟内存管理。通过这种方式,系统可以运行更多的程序,即使物理内存有限。
虚拟内存的主要优势之一是提供了更大的地址空间,以支持大型程序和多任务操作系统。它还可以通过将不常用的数据存储在磁盘上来节省物理内存的使用,并提供更好的内存管理和数据保护机制。
总结而言,物理内存是计算机实际的硬件内存,而虚拟内存是通过操作系统提供的抽象层面扩展的内存概念,使得程序能够使用比物理内存更大的地址空间,并提供更好的内存管理和保护机制。
物理内存和磁盘是不同的存储介质,它们在计算机系统中扮演不同的角色和功能。物理内存用于临时存储当前正在执行的程序和数据,而磁盘用于长期存储文件和持久性数据。
在 Linux 操作系统中,“页”(Page)是内存管理中的一个重要概念,尤其是在虚拟内存管理中。操作系统通过页来管理物理内存和虚拟内存之间的映射,使得每个进程能够独立地使用其虚拟地址空间,而不直接操作物理内存。这种分页管理有助于实现内存保护、内存共享、内存映射文件等机制。
虚拟内存与物理内存:
页的基本定义:
页表(Page Table):
虚拟地址空间划分:
虚拟内存被划分成多个大小相等的页。每个进程的虚拟内存都由操作系统划分成若干个虚拟页,而每个虚拟页与物理内存中的页帧通过页表建立映射。
页面映射:
在一个分页系统中,每个虚拟地址空间的页都与物理内存的页帧相对应。操作系统使用页表来记录这种映射关系。
页替换:
由于物理内存的有限性,可能会发生内存不足的情况。这时,操作系统需要将某些不常用的虚拟页面交换到磁盘上的交换空间(Swap Space)中,将内存中的页面替换出来以腾出空间。当需要这些被替换的页面时,它们会重新加载回内存。
大页(Huge Pages):
对于某些需要大量内存的应用程序(如数据库、高性能计算等),操作系统允许使用大页(如 2MB 或 1GB 页),以减少页表的开销。大页可以减少页表项的数量,提高内存访问效率。
页对齐:
在虚拟内存系统中,页是按照一定的对齐方式来管理的,通常是 4KB。页对齐确保了虚拟地址空间和物理内存的页边界一致性。
页面保护与权限:
每个页都有一定的访问权限,这些权限决定了该页面的访问行为:
这些权限通过页表项设置,并在进程访问该页面时由硬件控制。
在 Linux 操作系统中,虚拟内存管理采用了分页机制。具体来说,Linux 中有以下几个关键组件与分页管理密切相关:
内存管理单元(MMU):
页表(Page Table):
进程虚拟地址空间:
缺页异常处理(Page Fault Handling):
交换空间(Swap Space):
分页和虚拟内存技术使得操作系统能够更高效、安全地管理内存,提高了系统的可伸缩性和稳定性。
伙伴算法(Buddy Allocation System)是一种内存分配算法,主要用于动态内存管理,特别是在操作系统中用来管理物理内存的分配和释放。它通过将内存块分成具有2的幂大小的区域,并通过将相邻的空闲块配对来实现高效的内存分配。
内存分配单位:
分区:
伙伴:
合并与分裂:
分配内存:
size
的内存时,伙伴算法会在空闲内存块中寻找足够大的块。size
精确匹配,则从更大的块开始分配,直到找到一个合适的块。对于大于请求的空闲块,系统会将其分裂成两个相同大小的伙伴,继续进行分配。释放内存:
快速分配和释放:
避免外部碎片:
内存合并:
内部碎片:
固定块大小:
管理复杂度:
在实现伙伴算法时,通常使用一个位图(Bitmap)或者链表来管理每个内存块的空闲和占用状态。常见的实现方式如下:
位图管理:
链表管理:
分配策略:
合并策略:
假设内存的最小分配块为 16 字节,并且内存总量为 64 字节。在内存管理开始时,内存将被划分为多个 16 字节的块,形成一个空闲块链表。假设用户请求了 24 字节的内存:
伙伴算法是一个高效的内存分配和释放算法,通过将内存块分成 2 的幂大小的块,并通过合并相邻的空闲块来减少外部碎片。尽管它存在一定的内部碎片和管理复杂度,但在许多操作系统和内存管理系统中,它仍然是一种常用的内存分配策略。