目录
[性能优化]-linux内存体系结构
1.说说linux内存分类
2.内存是怎么管理的?
3. 物理内存的分页技术
3.1 什么是分页技术
3.1.1 页帧的分配
3.1.2 进程分配内存
3.1.3 linux物理地址空间布局
3.1.4 linux虚拟地址内核空间分布
3.1.5 linux虚拟地址用户空间分布
3.1.6 linux虚拟地址与物理地址映射关系
3.2 虚拟内存管理
3.2.1伙伴系统( buddy system)
3.2.2分页回收
3.2.3 swap
4 总结
4.1 内存代码层优化
4.2 内存性能优化
second60 20180801
在linux中,内存分两种:物理内存和swap交换内存。
物理内存:真正的内存,内存条里的内存,一个内存条4G,那么物理内存大小就 是4G。
swap交换内存:把一定的磁盘做为交换内存,为了物理内存不够用时,把内存中的 数据page out或swap out到交换内存中。
进程是怎么使用内存的呢?是直接操作的吗?
答案当然不是。
linux可以同时运行成千上万的进程。每个进程都会占用一定的内存,如果进程直接操作内存,那么内存是根本不够用的。同时也不方便管理物理内存。如果一启动就把进程的所有内存分配好,那么会造成内存的浪费,因为可能运行过程根本不会运行到那。
根据上面原因,内核对内存管理做了处理:
如果物理内存是按字节来存取,那么效率是比较差的。为了高效地管理内存和提升效率,采用了内存分页技术。
把内存分成固定大小的chunk组成,称为分页page。分页大小取决于处理器体系结构。i386和x86_64中的分页大小是4KB。内核以页大小处理内存。
系统上的物理内存被分成页帧(page frame),一个页帧包含数据的一个分页。
一个分页page是物理内存(page frame)或虚拟内存中一组连续性地址。通常4KB。
当一个进程请求一定数量的内存页时,如果有有效内存页,内刻立即分配给进程。
否则,需要从一些其他进程或分页缓存中得到。
进程是不能直接对物理内存寻址的。每个进程有一个虚拟地址空间(32位为4GB)。当为一个进程分配内存时,页帧的物理地址被映射到进程的虚拟地址。
一个进程的虚拟地址空间大小取决于处理器构构。32位系统,一个进程虚拟地址空间大小2的32次方(4GB);64位系统上,大小为2的64次方(16EB)。然而,一个进程不会使用到那么多的地址空间,大部份是未分配的,并没有映射到任何物理内存。
以32位系统为例:
上图为一个进程在32位系统的虚拟地址空间图。
以32位系统为例,内核地址空间划分为0-3G为用户空间,3-4G为内核空间。64位划分不同。
物理地址空间的顶部以下一段空间,被PCI设备IO内存映射占据,它们的大小和布局由PCI规范所决定。640K-1M这段地址被BIOS和VGA适配器所占据。
linux系统在初始化时,会根据实际的物理内存大小,为每个物理页面创建一个page对象,所有page对象构成一个mem_map数组。
内核将所有物理页面划分到3类内存管理区中
总结:内核使用0-1G的内存,其中896-1G中的128M给映射高端内存。1G以上的内存内核通过映射来操作。
在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。
由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map数组等放在ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问I/O设备存储空间时,就使用ioremap()将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。
用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。
这个大家都很熟悉了,就不详细说明了,请看另一篇博客。
Linux将4G的线性地址空间分为2部分,0~3G为user space,3G~4G为kernel space。
由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到1G的内核线性空间中,这显然不可能。于是,内核将0~896M的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问ZONE_DMA和ZONE_NORMAL里的物理页面;此时内核剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM里的物理页面映射到kernel space的最后128M线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。
问题:分配了1GB给虚拟地址空间给内核,够用吗?
答:在内核896-1024M,可以映射到其他的物理内存。所以不存在不够的问题。除非内存不够了。
操作系统的物理内存对于应用程序和用户来说通常是隐藏的,因为操作系统可以将任何物理内存映射到虚似内存。
系统不会给应用程序分配物理内存,但是它会向linux内核请求一定大小的虚拟内存,并在虚拟内存中交换得到映射。
虚拟内存不必映射到物理内存。如果你的应用程序被分了大量内存,则有一部分可能被映射到磁盘的swap文件。
应用程序通常不直接向磁盘子系统写入,而是向高速缓存或缓冲区写入。当时间片到达,或者一个文件的大小超出缓冲缓存时,内核线程pdflush/Per-BDI flush会将缓存/缓冲中的数据刷新到磁盘中。
管理虚拟内存的默认配置是:分配所有有效的空闲内存空间作为磁盘的缓存。
同样,linux也可以非常有效地处理swap空间。
内核使用一种被称为伙伴系统的机制来维护它的空闲分页。伙伴系统维护空闲分页,并尝试给分页分配请求分页。它试图保持内存区域是连接的。
当分配分页失败时,会进行分页回收。
通过/proc/buddyinfo找到伙伴系统的信息。
当一个进程请求映射一定数量分页的时候,如果没有有效的分页,linux内核将尝试释放一定数量的分页,然后将这些分页分配给新的请求内存的进程。这个过程称为分页回收(pace reclaiming)。内核线程kswapd和内核函数try_to_free_page()负责分页回收。
分页的使用主要有两个目的:分页缓存和进程地址空间。分页缓存是分页被映射到磁盘的一个文件。分页属于一个进程地址空间,它被用于堆和栈。当kswapd回收分页时,它宁可缩小分页缓存也不愿分页移出进程拥有的分页。
分页缓存的分页回收和进程地址空间的回收在很大程序上依赖于使用场景,这将会影响性能。通过/proc/sys/vm/swappiness来控制这些行为.
当发生分页回收时,在非活路列表中属于进程进址空间的候选分页可以被分页移出。目的保证主内存的分配和更加有效的利用空间。
虚似内存由物理内存和磁盘子系统或swap分区组成。如果linux中虚拟内存管理器发现内存分页已经被分配,但大量埋单还没有使用完,它会将这个内存分页移动到swap空间。
内存是性能优化的最要一环。了解linux内存分配的原理,可以在写应用的内存使用时,可以更优的写出更优的代码。
1. 结构体对齐:可以使内存更小。可以学习C语言中的__attribute__选项
2. 内存预分配:在能预测到使用内存最大数量时,或限制最大量数时,预分配内存。
a) 减少内存碎片的产生,存减少创建消毁的开销
b) 缺点:预知估测好大小,占用一定的内存
3. 可以设置TCP接收和发送缓冲区大小和分页大小的N倍,如分页4K,缓冲设为8K.
4. 编写适合项目应用的内存分配函数。开源库基本都会提供一套自已的内存分配函数,为了更适合功能场景的内存分配。
这个留在后面写。