Linux内存管理和应用
[作者:byeyear。首发于cnblogs,转载请注明。联系:east3@163.com]
本文对Linux内存管理使用到的一些数据结构和函数作了简要描述,而不深入到它们的内部。对这些数据结构和函数有了一个总体上的了解后,再针对各项分别作深入了解的时候,也许会简单一些。
Linux内存访问限制(仅针对32位系统)
- 默认情况下Linux的内核空间映射到4G虚拟地址的最高1G(即0xC0000000 - 0xFFFFFFF)
在x86中,这个偏移量表示为TASK_SIZE,也表示为PAGE_OFFSET。
对于其他arch,TASK_SIZE和PAGE_OFFSET之间可能存在HOLE。
- 如果不考虑HIGH_MEMORY,Kernel中内存物理地址和逻辑地址(此时就是虚拟地址)有线性对应关系
有两个函数:virt_to_phys和phys_to_virt用于在内核物理地址和逻辑地址间进行转换。
- 所以,32位系统中,Linux能直接访问的物理内存最大为1G
PHY_ADDR[0] -> LOG_ADDR[3G]
PHY_ADDR[1G] -> LOG_ADDR[4G]
- 除去保留给vmalloc、kmap和其他一些用途的地址空间,可直接访问的物理内存最大为896M
- 进程的虚拟地址空间仍然是4G。0-TASK_SIZE(3G)给user space,各个processor不同;kernel space的1G对每个processor都是一样的。
- 后面我们会说到如何访问1G以上的物理内存。
一些数据结构
- struct vm_area_struct
该结构用来描述进程虚拟地址空间中一段连续的虚拟内存。但是我们知道,进程所占用的虚拟地址空间通常是不连续的(例如要分为代码、Heap和stack;或者考虑经常申请/释放内存的情况),所以一个进程需要多个struct vm_area_struct来描述其virtual memory area。根据进程需要的该结构体数量的大小,这一堆的vm_area_struct将组织成Linear或Linear+AVL的形式。
- struct mm_struct
这个结构用来管理上文所述那一堆的vm_area_struct。在这个结构中有个指针,指向那堆struct vm_area_struct的链表头,另有一个指针指向AVL tree的root(如果有的话)。而mm_struct本身又是task_struct里的一个成员。
- zone和node
在多CPU系统中,每个CPU对应一个node用于描述该CPU所“拥有”的内存区域。这里说的“拥有”是指与该CPU具有最佳亲缘性的内存区域,该CPU访问这些内存时将获得最高的性能。一块CPU也可以访问*非*它所“拥有”的内存区域,但访问“非拥有内存区域”的性能不如访问“拥有的内存区域”的性能。
每个node又拆分成多个zone,不同的zone有不同的属性。例如分配给ISA总线设备进行DMA的内存必须在16M以下(天知道现在的计算机上还有木有ISA设备了),这些内存就归为ZONE_DMA。High Mem归属于ZONE_HIGHMEM。不具有特殊性质的内存都归结到ZONE_NORMAL。
描述node和zone的结构分别为typedef struct pglist_data pg_data_t和struct zone。
每个pg_data_t里有一个struct zone数组,数组的元素个数就是ZONE的类型数。
每个pg_data_t里还有一个struct zonelist结构的数组,数组的每个元素都用来描述一种特定的“分配策略”。所谓“分配策略”,就是在分配内存的时候根据分配需要(kmalloc的第二参数就是用来描述这方面的需要的)确定从哪个zone开始,以及可以使用哪些zone。用于ISA DMA的内存必须在16M以下,所以需要一种“策略”;从High Mem中分配内存必须在1G以上,也需要一种策略;普通的没有特殊要求的内存分配也需要一种策略。有多少种策略,struct zonelist数组中就有多少个元素。
- struct zonelist
该结构用于描述某种特定的分配“策略”,其主要成员是一个数组struct zone *zones[MAX_ZONES_PER_ZONELIST + 1],[...]中的“+1”用来仿照字符串结尾的方式确定数组元素的实际个数。该数组的元素是所有可用于某个特定“策略”的zone们。假定可用于ISA DMA的zone是zoneA,zoneB,zoneC,分配顺序也是从A开始,那么这个数组中的三个元素就是指向zoneA,zoneB,zoneC的指针。
- struct page
Linux会为它所管辖的物理内存的每个Page Frame建立一个struct page数据结构,而不管这个Page Frame是否是HIGH_MEMORY。
- high_memory
这是一个变量,表示能够直接管辖的物理内存尾。如果物理内存小于896M,那么它就是实际物理内存大小;否则就是896M。
vmalloc .vs. kmap
vmalloc用于分配一段虚拟地址空间连续、但是物理地址空间未必连续的内存。vmalloc所能使用的虚拟地址空间在high_memory之上(high_memory + 8M)。由于vmalloc分配得到的内存在虚拟地址和物理地址之间不存在线性关系,所以vmalloc可以从HIGH_MEMORY处分配page frame,而不局限于896M以下。事实上,vmalloc优先考虑HIGH_MEMORY。这里顺便说下,ioremap所得到的虚拟地址也占用vmalloc可用的虚拟地址空间。一般情况下,调用vmalloc能够分配到的内存空间可以远大于一次kmalloc所能得到的。不过vmalloc内部仍然依赖kmalloc逐个分配page frame。
kmap用于在page frame和虚拟地址之间建立映射。kmap本身并不分配物理内存,在使用前需要使用alloc_page先分配page_frame。对于Low memory,kmap将直接返回page frame对应的物理地址。
kmap和vmalloc在应用上的最大区别在于,kmap往往应用于“空间分配”与“地址映射”可以分开的场合。考虑下面的场景:
a) App想要通过网络发送数据;
b) Linux将用户数据复制到内核buffer;
c) NIC driver通过DMA发送数据。
在上述场景的步骤b)中,Linux将执行类似下面的代码(仅作示意,不代表真实代码):
while(more_data_to_copy) {
struct page* page = alloc_page(...);
void* vadr = kmap_atomic(page);
copy_to_kernel(vadr, user_buf, size);
kunmap_atomic(page);
link_page_to_list(page);
}
上面的代码中,一页数据拷贝完成后该页的地址映射就可以立即解除,因为后续的DMA操作是不需要用到内核虚拟地址的。在linux源代码的net目录中的代码大量使用了kmap(_atomic)。这样做(将空间分配和地址映射分离)的好处是可以节约大量的页表(考虑网络服务器中存在大量待收发数据的情况),可以很好的利用HIGH_MEMORY。快速、少量或不可休眠的页映射使用kmap_atomic,其他情况可使用kmap。
vmalloc与page fault
执行vmalloc(包括ioremap)时页表的设置是写在init_mm中的,这样当某个进程试图通过驱动访问该虚拟地址时必然发生page fault。do_page_fault函数判断出page fault发生在vmalloc region,于是从init_mm中将pgd/pud/pmd复制到当前进程,但pte是不复制的——内核地址空间的pte由所有进程共享。如果在将来的某个时刻映射解除,修改的也是init_mm的页表设置。后续对该地址的访问(假设仍为同一进程)在pgd、pud、pmd这三层仍然能够通过(进程有自己的pgd/pud/pmd),但到pte这一层就会发生page fault(init_mm已删除该pte)。类似的情况还有ioremap[注]。
[注] 根据64位atom上安装32位Linux(kernel 2.6.32)的测试结果,页表是3级,各级位宽2/9/9/12,第一级将内存分为4个1G区间,进程pgd的第四条目总是指向init_mm的pmd。这样,只要ioremap已建立好,init_mm对应的pmd就一定有效,也就不会发生page fault。(这种情况下vmalloc的效果未确认)
DirectIO
对于DirectIO来说,数据传输直接发生在设备与应用程序缓冲区之间。因此,一般来说DirectIO往往和DMA配合使用。DirectIO的步骤大体如下:
- 应用程序申请内存
最简单的当然是malloc或new之类;如果你要求获得的内存对齐于页边界,可以用anonymous mmap——使用mmap函数分配虚拟内存,并将mmap函数的flags参数设定为MAP_ANONYMOUS,同时将fd设置为-1(对linux非必须,仅作兼容性考虑)。具体请man mmap。或者使用valloc函数。
- Driver为其分配物理pages
应用程序调用malloc或new或valloc得到的虚拟地址,在初始状态是没有物理页面和其对应的。我们如果要对其做DMA,需要为其分配物理内存,并将其lock,不允许换出。kernel提供get_user_pages函数做这些工作。如果需要的内存空间很大,get_user_pages的执行过程会比较长。
- DMA
一般来说get_user_pages得到的物理页面是不连续的,所以我们需要dma_map_sg。
- 进一步说明
因为DirectIO的数据传输直接发生在设备和应用程序缓冲区之间,所以一般来说Driver是不需要访问缓冲区内容的。如果Driver也需要访问缓冲区内容,需要通过kmap获得这些物理页面对应的内核虚拟地址。
没有对应的put_user_pages函数;你需要自己做个循环调用page_cache_release。
- IBM有篇关于DirectIO的文章写得不错。
应用程序直接访问设备内存
- 应用程序要访问设备内存(例如PCI的memory bar),有两种方法:一种是使用ioremap(或ioremap_nocache),将设备内存物理地址映射到内核虚拟地址,App使用ioctl或read/write通过驱动访问;
- 另一种方法是使用mmap,让App直接访问设备内存。使用这种方法,App需要通过mmap系统调用开辟一块虚拟地址空间,并在驱动中实现file_operations中的mmap函数。App调用mmap时,fd参数设置为打开设备的fd。这块虚拟地址空间大小就是设备内存的大小。驱动在mmap的实现中,调用remap_pfn_range为App开辟的那块虚拟地址空间建立与设备物理内存的映射。和DirectIO相比,由于待映射的物理内存已存在(即设备内存),不需要分配并锁定page,所以速度很快。
- 驱动不需要实现unmap。
- 可能需要对映射得到的内存禁止cache。
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if(rempa_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vm_start, vma->vm_page_prot))
return - EAGAIN;
- 应用程序调用如下:
void *p = mmap(,
DEV_MEM_LEN, // 设备内存长度
prot,
flags,
dev_fd,
DEV_MEM_PHY); // 设备内存物理地址
注意:如果你需要对mmap映射区进行写操作,在建立设备文件的时候将文件属性设置为rw。
使用系统内存作为设备内存
- 在嵌入式系统中,我们也许对系统有较大的控制权,可以使用mem=启动参数。这个时候我们可以保留一部分系统内存作为设备内存,设备直接将数据DMA到保留下的系统内存中(这块系统内存在mem=启动参数设定的内存大小之外,因此不会被Linux看到)。然后使用上文所说的“应用程序直接访问设备内存”的方法获取数据。
内核物理页面的分配
内核中有多种情况需要分配物理页面,比如处理page fault,比如driver需要临时或永久存储区,等等,一般来说都是通过kmalloc或辅助函数完成(核心数据结构是struct page)。管理内核物理页面的模块并不关心是谁要用物理页面,你要就给你(如果有),你还就收下。至于分配出去后怎么用,那是调用者的事。
相对而言,应用程序中调用malloc仅仅是划出一段虚拟内存,但并不立即分配对应的物理内存空间。只有当有人读或写了这个页面,才会通过page fault为其分配物理页面,并且随时可能被换出。即,应用程序在用户空间所使用的物理页面都是在内核态分配的,纯用户态代码永远无法获得一块实际的物理内存:这是理所当然的事,因为物理页面的使用必须全部由内核所掌控。
物理页面交换
分配到的物理页面只有被挂在某个可换出的链表上,才会被页面换出代码扫描到。所以,如果你的驱动通过kmalloc获得了一些物理页面自用,它们是不可能被换出的——因为没有挂在某个可换出链表上。上文中通过get_user_pages得到的页面也是不会换出的。另一方面,发生page fault后分配到的物理页面是会被内核挂到某个可换出链表上的。
Linux内存管理和应用的更多相关文章
- 浅谈Linux内存管理机制
经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...
- linux内存管理
一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分: 1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...
- Linux内存管理原理
本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址又叫线性地址.linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻 ...
- 了解linux内存管理机制(转)
今天了解了下linux内存管理机制,在这里记录下,原文在这里http://ixdba.blog.51cto.com/2895551/541355 根据自己的理解画了张图: 下面是转载的内容: 一 物理 ...
- Linux内存管理原理【转】
转自:http://www.cnblogs.com/zhaoyl/p/3695517.html 本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址 ...
- Windows内存管理和linux内存管理
windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...
- linux 内存管理——内核的shmall 和shmmax 参数
内核的 shmall 和 shmmax 参数 SHMMAX= 配置了最大的内存segment的大小 ------>这个设置的比SGA_MAX_SIZE大比较好. SHMMIN= 最小的内存seg ...
- linux内存管理子系统
一.Linux内存管理模型 1.虚拟地址与物理地址的映射 2.物理地址的分配二.虚拟地址与物理地址的映射 1.虚拟地址空间分布 32位处理器有32根地址总线,可访问4G的物理空间.其中有0-3G为用户 ...
- Linux内核分析(三)----初识linux内存管理子系统
原文:Linux内核分析(三)----初识linux内存管理子系统 Linux内核分析(三) 昨天我们对内核模块进行了简单的分析,今天为了让我们今后的分析没有太多障碍,我们今天先简单的分析一下linu ...
- Linux内存管理之slab分配器
slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...
随机推荐
- POJ 1847 Floyd_wshall算法
前面用dijstra写过了.但是捏.数据很小.也可以用Floyd来写. 注意题目里给出的是有向的权值. 附代码:#include<stdio.h>#include<string.h& ...
- linux create a process
When the system starts up it is running in kernel mode and there is, in a sense, only one process, t ...
- customization arm ubuntu rootfs
requirment: want to get arm ubuntu rootfs method: base on debootstrap tool, customization full funct ...
- cookie -- 添加删除
前段时间学到了cookie,之前的公司用的jquery插件,现在终于学到了原生的js <!doctype html> <html> <head> <meta ...
- DevExpress v18.1新版亮点——ASP.NET篇(三)
用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容.本文将介绍了DevExpress ASP.NET v18.1 的新功能,快来下载试用新版本!点 ...
- “开始菜单”按钮今年8月将重回Windows 8
本月早些时候微软明确表示,“开始菜单”将重新回归Windows 8操作系统.尽管微软当时并没有公布具体的时间表,但据熟悉微软内部运作的消息灵通人士透露称,“开始菜单”极有可能将出现在预计于今年8月发布 ...
- WEB接口测试之Jmeter接口测试自动化 (二)
通过逐个录入的方式,好不容易将需要测试几十个接口的300多个测试用例录入sampler-http请求中,固定的测试环境跑起来也还 感觉良好.不料在新服务器环境中跑用例时,问题来了:修改参数维护脚本等成 ...
- Springboot中的连接池
pom.xml <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-ja ...
- Typescript(ES6) ...用法
简单例子: //数组深拷贝 var arr2 = arr; var arr3 = [...arr]; console.log(arr===arr2); //true, 说明arr和arr2指向同一个数 ...
- NSObject之一
Objective-C中有两个NSObject,一个是NSObject类,另一个是NSObject协议.而其中NSObject类采用了NSObject协议.在本文中,我们主要整理一下NSObject类 ...