前言
内存治理是操作系统的一项**性能。
从操作系统启动到创立0号进程(也就是idle进程)的环节中,大局部口头的代码都触及到内存治理。
操作系统的内存治理大抵可以分为以下几个档次:
物理内存治理
物理内存指的是电脑中实践存在的内存容量,这个消息可以经过BIOS失掉。
在内存分页后,物理内存的治理结构变成了一个数组,其中每个元素示意一个物理内存页,每页的大小为4096字节
如下图:
在一个便捷的内核示例中,物理内存页的治理结构可以仅蕴含一项:
atomic_t refs
这示意物理内存页的援用计数:假设计数为0,示意该页闲暇;若计数大于0,则示意该页正在被经常使用,详细数值示意共享此页的进程数量。
便捷的内核示例通常不允许SMP架构,因此自旋锁(spinlock)并不须要。
但是,在对称多处置器(SMP)系统中,由于全局数据结构或许会被多个CPU同时访问,因此须要引入自旋锁。
在这种状况下,物理内存页的治理结构至少须要蕴含以下两项:
atomic_t spinlockatomic_t refs
自旋锁的作用相似于运行程序中的互斥锁(mutex),不同之处在于,当自旋锁失掉失败时,它会重复尝试直到成功。
void spin_lockatomic_t { spin_trylock }
以上代码展现了为自旋锁加锁的函数。while循环会始终尝试加锁,直到成功;假设未成功,它将继续自旋尝试,因此称之为自旋锁。
在SMP环境中,自旋锁用于包全共享的数据结构:当一个CPU持有自旋锁时,其余CPU不可访问该共享数据。
关于单处置器系统,没有必要经常使用自旋锁,间接封锁终止即可。
在单处置器环境中,封锁终止可以防止内核的并发口头,从而防止对共享数据的竞争。
但是,在多处置器系统中,必定经常使用自旋锁,由于封锁终止只能影响CPU,而不可影响其余CPU;此时,自旋锁用于包全共享数据。
物理内存的治理数组是最关键的全局共享数据。
当须要为某个进程调配内存时,判别哪个内存页闲暇、哪个已被经常使用,都依赖于这个数组。
在加自旋锁时,必定要先封锁终止,由于假设在加锁后、封锁终止前,刚好有终止出现,并在终止处置函数中再次恳求加同一个锁,就会造成递归死锁。
在Linux内核中,封锁终止并加锁的函数是:spin_lock_irqsave()。
调配物理内存页的函数是:get_free_pages(),它可以调配1页或延续多页的内存。
假设调配多页内存,起始地址需按页数对齐。
虚构内存治理
虚构内存的治理是经过进程的页表来成功的。
为了浪费物理内存,当一个新进程创立时,它会与父进程共享同一套物理内存页。
只要当新进程须要对某个内存页启动写操作时,系统才会为它创立一个新的物理内存页正本,并敞开该页与父进程的共享,这个环节称为写时复制(Copy-On-Write)
写时复制的环节可以形容为:
因此,在新进程刚创立时,它的用户空间并没有专属的物理内存页。只要在须要写操作时,系统才会经过写时复制机制逐渐调配内存页,从而尽或许地坚持物理内存的闲暇形态。
另一种坚持物理内存尽量闲暇的机制是“按需加载”:
这就是“写时复制”和“按需加载”的环节:只要在真正须要时,Linux系统才会将物理内存调配给进程。
用户态的内存函数
以上这些机制都是操作系统内核的一局部,运行程序的代码无需关注这些细节。
运行程序调配内存的最底层操作经过brk()系统调用成功。
brk()是一个系统调用,用于修正运行程序数据段的开端位置,以便调配或监禁运行程序的堆空间。
在C规范库中,brk() 被封装成了 sbrk() 和 brk() 两个函数,以便于程序员经常使用:
实践上,Linux系统中只要一个 brk() 系统调用,它担任设置进程数据段的开端,并将这个值前往给运行程序。
在Linux内核的头文件中,brk() 系统调用的处置函数 sys_brk() 如图所示。
假构想间接经常使用系统调用,可以经过 Linux 的 syscall() 函数来成功。该函数接受调用号和参数列表,能够协助辨别实践的系统调用和C库的封装。syscall() 函数的申明为:long syscall(long number, ...);,它的参数是可变的,最多允许6个参数,由于寄存器的数量有限。
基于 sbrk() 和 brk(),罕用的内存治理函数 malloc() 和 free() 被封装进去。malloc() 调配的内存块可以按需监禁,不用按顺序。而 brk() 和 sbrk() 调配的内存必定按顺序监禁,由于它们会调整进程数据段的开头。
数据段开头(brk)之外的堆空间假设被经常使用,会造成段失误。因此,Linux man 手册倡导运行程序不要间接经常使用 sbrk() 和 brk() 来放开和监禁内存。
brk() 的作用仅仅是通知 Linux 内核哪个范畴的堆内存是可用的。实践的物理内存页是在进程实践读写内存时由内核依据写时复制和按需加载机制智能放开的,运行程序并不会感知到这些细节。
此外,Linux 还会将不罕用的物理内存页交流到磁盘上的交流分区(swap),以监禁更多内存。因此,当内存无余时,磁盘的读写频率也会参与。