Linux内存管理详解.docx
- 文档编号:5743577
- 上传时间:2022-12-31
- 格式:DOCX
- 页数:52
- 大小:61.81KB
Linux内存管理详解.docx
《Linux内存管理详解.docx》由会员分享,可在线阅读,更多相关《Linux内存管理详解.docx(52页珍藏版)》请在冰豆网上搜索。
Linux内存管理详解
内存
ARM里面,其用来控制访问权限的就是域值和AP值两个手段
内存管理系统是操作系统中最为重要的部分,因为系统的物理内存总是少于系统所需要的内存数量。
虚拟内存就是为了克服这个矛盾而采用的策略。
系统的虚拟内存通过在各个进程之间共享内存而使系统看起来有多于实际内存的内存容量。
虚拟内存可以提供以下的功能:
*广阔的地址空间。
系统的虚拟内存可以比系统的实际内存大很多倍。
*进程的保护。
系统中的每一个进程都有自己的虚拟地址空间。
这些虚拟地址空间是完全分开的,这样一个进程的运行不会影响其他进程。
并且,硬件上的虚拟内存机制是被保护的,内存不能被写入,这样可以防止迷失的应用程序覆盖代码的数据。
*内存映射。
内存映射用来把文件映射到进程的地址空间。
在内存映射中,文件的内容直接连接到进程的虚拟地址空间。
*公平的物理内存分配。
内存管理系统允许系统中每一个运行的进程都可以公平地得到系统的物理内存。
*共享虚拟内存。
虽然虚拟内存允许进程拥有自己单独的虚拟地址空间,但有时可能会希望进程共享内存。
linux仅仅使用四个段
两个代表(code和data/stack)是内核空间从[0xC0000000](3GB)到[0xFFFFFFFF](4GB)
两个代表(code和data/stack)是用户空间从[0](0GB)到[0xBFFFFFFF](3GB)
__
4GB--->| | |
| Kernel | | 内核空间(Code+Data/Stack)
| | __|
3GB--->|----------------| __
| | |
| | |
2GB--->| | |
| Tasks | | 用户空间(Code+Data/Stack)
| | |
1GB--->| | |
| | |
|________________| __|
0x00000000
内核/用户线性地址
linux可以使用3层页表映射,例如在高级的I64服务器上,但是i386体系结构下只有2层有实际意义:
------------------------------------------------------------------
线性地址
------------------------------------------------------------------
\___/ \___/ \_____/
PD偏移 PF偏移 Frame偏移
[10bits] [10bits] [12bits]
| | |
| | ----------- |
| | | Value |----------|---------
| | | | |---------| /|\ | |
| | | | | | | | |
| | | | | | |Frame偏移 |
| | | | | | \|/ |
| | | | |---------|<------ |
| | | | | | | |
| | | | | | |x4096 |
| | | PF偏移 |_________|------- |
| | | /|\| | |
PD偏移 |_________|----- | | | _________|
/|\| | | | | | |
| | | | \|/| | \|/
_____ | | | ------>|_________| 物理地址
| | \|/| | x4096| |
|CR3|-------->| | | |
|_____| |.......| |.......|
| | | |
页目录表 页表
Linuxi386分页
注意内核(仅仅内核)线性空间就等于内核物理空间,所以如下:
_____________________
| 其他内核数据|___ | | |
|----------------| ||__| |
| 内核 |\ |____| 实际的其他 |
3GB--->|----------------|\ | 内核数据 |
| |\\ | |
| __|_\_\____|__ Real |
| Tasks | \\ | Tasks |
| __|___\_\__|__ Space |
| | \\| |
| | \\|----------------|
| | \| 实际内核空间 |
|________________| \|________________|
逻辑地址 物理地址
[内存实时分配]
|copy_mm
|allocate_mm=kmem_cache_alloc
|__kmem_cache_alloc
|kmem_cache_alloc_one
|alloc_new_slab
|kmem_cache_grow
|kmem_getpages
|__get_free_pages
|alloc_pages
|alloc_pages_pgdat
|__alloc_pages
|rmqueue
|reclaim_pages
·copy_mm[kernel/fork.c]
·allocate_mm[kernel/fork.c]
·kmem_cache_alloc[mm/slab.c]
·__kmem_cache_alloc
·kmem_cache_alloc_one
·alloc_new_slab
·kmem_cache_grow
·kmem_getpages
·__get_free_pages[mm/page_alloc.c]
·alloc_pages[mm/numa.c]
·alloc_pages_pgdat
·__alloc_pages[mm/page_alloc.c]
·rm_queue
·reclaim_pages[mm/vmscan.c]
[内存交换线程kswapd]
|kswapd
|//initializationroutines
|for(;;){//Mainloop
|do_try_to_free_pages
|recalculate_vm_stats
|refill_inactive_scan
|run_task_queue
|interruptible_sleep_on_timeout//wesleepforanewswaprequest
|}
·kswapd[mm/vmscan.c]
·do_try_to_free_pages
·recalculate_vm_stats[mm/swap.c]
·refill_inactive_scan[mm/vmswap.c]
·run_task_queue[kernel/softirq.c]
·interruptible_sleep_on_timeout[kernel/sched.c]
[内存交换机制:
出现内存不足的Exception]
|PageFaultException
|causebyalltheseconditions:
| a-)Userpage
| b-)Readorwriteaccess
| c-)Pagenotpresent
|
|
----------->|do_page_fault
|handle_mm_fault
|pte_alloc
|pte_alloc_one
|__get_free_page=__get_free_pages
|alloc_pages
|alloc_pages_pgdat
|__alloc_pages
|wakeup_kswapd//Wewakeupkernelthreadkswapd
·do_page_fault[arch/i386/mm/fault.c]
·handle_mm_fault[mm/memory.c]
·pte_alloc
·pte_alloc_one[include/asm/pgalloc.h]
·__get_free_page[include/linux/mm.h]
·__get_free_pages[mm/page_alloc.c]
·alloc_pages[mm/numa.c]
·alloc_pages_pgdat
·__alloc_pages
·wakeup_kswapd[mm/vmscan.c]
[目录]
内存管理子系统导读fromaka
我的目标是‘导读’,提供linux内存管理子系统的整体概念,同时给出进一步深入研究某个部分时的辅助信息(包括代码组织,文件和主要函数的意义和一些参考文档)。
之所以采取这种方式,是因为我本人在阅读代码的过程中,深感“读懂一段代码容易,把握整体思想却极不容易”。
而且,在我写一些内核代码时,也觉得很多情况下,不一定非得很具体地理解所有内核代码,往往了解它的接口和整体工作原理就够了。
当然,我个人的能力有限,时间也很不够,很多东西也是近期迫于讲座压力临时学的:
),内容难免偏颇甚至错误,欢迎大家指正。
存储层次结构和x86存储管理硬件(MMU)
这里假定大家对虚拟存储,段页机制有一定的了解。
主要强调一些很重要的或者容易误解的概念。
存储层次
高速缓存(cache)--〉主存(mainmemory)---〉磁盘(disk)
理解存储层次结构的根源:
CPU速度和存储器速度的差距。
层次结构可行的原因:
局部性原理。
LINUX的任务:
减小footprint,提高cache命中率,充分利用局部性。
实现虚拟存储以满足进程的需求,有效地管理内存分配,力求最合理地利用有限的资源。
参考文档:
《toolittle,toosmall》byRikVanRiel,Nov.27,2000.
以及所有的体系结构教材:
)
MMU的作用
辅助操作系统进行内存管理,提供虚实地址转换等硬件支持。
x86的地址
逻辑地址:
出现在机器指令中,用来制定操作数的地址。
段:
偏移
线性地址:
逻辑地址经过分段单元处理后得到线性地址,这是一个32位的无符号整数,可用于定位4G个存储单元。
物理地址:
线性地址经过页表查找后得出物理地址,这个地址将被送到地址总线上指示所要访问的物理内存单元。
LINUX:
尽量避免使用段功能以提高可移植性。
如通过使用基址为0的段,使逻辑地址==线性地址。
x86的段
保护模式下的段:
选择子+描述符。
不仅仅是一个基地址的原因是为了提供更多的信息:
保护、长度限制、类型等。
描述符存放在一张表中(GDT或LDT),选择子可以认为是表的索引。
段寄存器中存放的是选择子,在段寄存器装入的同时,描述符中的数据被装入一个不可见的寄存器以便cpu快速访问。
(图)P40
专用寄存器:
GDTR(包含全局描述附表的首地址),LDTR(当前进程的段描述附表首地址),TSR(指向当前进程的任务状态段)
LINUX使用的段:
__KERNEL_CS:
内核代码段。
范围0-4G。
可读、执行。
DPL=0。
__KERNEL_DS:
内核代码段。
范围0-4G。
可读、写。
DPL=0。
__USER_CS:
内核代码段。
范围0-4G。
可读、执行。
DPL=3。
__USER_DS:
内核代码段。
范围0-4G。
可读、写。
DPL=3。
TSS(任务状态段):
存储进程的硬件上下文,进程切换时使用。
(因为x86硬件对TSS有一定支持,所有有这个特殊的段和相应的专用寄存器。
)
default_ldt:
理论上每个进程都可以同时使用很多段,这些段可以存储在自己的ldt段中,但实际linux极少利用x86的这些功能,多数情况下所有进程共享这个段,它只包含一个空描述符。
还有一些特殊的段用在电源管理等代码中。
(在2.2以前,每个进程的ldt和TSS段都存在GDT中,而GDT最多只能有8192项,因此整个系统的进程总数被限制在4090左右。
2。
4里不再把它们存在GDT中,从而取消了这个限制。
)
__USER_CS和__USER_DS段都是被所有在用户态下的进程共享的。
注意不要把这个共享和进程空间的共享混淆:
虽然大家使用同一个段,但通过使用不同的页表由分页机制保证了进程空间仍然是独立的。
x86的分页机制
x86硬件支持两级页表,奔腾pro以上的型号还支持PhysicaladdressExtensionMode和三级页表。
所谓的硬件支持包括一些特殊寄存器(cr0-cr4)、以及CPU能够识别页表项中的一些标志位并根据访问情况做出反应等等。
如读写Present位为0的页或者写Read/Write位为0的页将引起CPU发出pagefault异常,访问完页面后自动设置accessed位等。
linux采用的是一个体系结构无关的三级页表模型(如图),使用一系列的宏来掩盖各种平台的细节。
例如,通过把PMD看作只有一项的表并存储在pgd表项中(通常pgd表项中存放的应该是pmd表的首地址),页表的中间目录(pmd)被巧妙地‘折叠’到页表的全局目录(pgd),从而适应了二级页表硬件。
TLB
TLB全称是TranslationLook-asideBuffer,用来加速页表查找。
这里关键的一点是:
如果操作系统更改了页表内容,它必须相应的刷新TLB以使CPU不误用过时的表项。
Cache
Cache基本上是对程序员透明的,但是不同的使用方法可以导致大不相同的性能。
linux有许多关键的地方对代码做了精心优化,其中很多就是为了减少对cache不必要的污染。
如把只有出错情况下用到的代码放到.fixupsection,把频繁同时使用的数据集中到一个cache行(如structtask_struct),减少一些函数的footprint,在slab分配器里头的slabcoloring等。
另外,我们也必须知道什么时候cache要无效:
新map/remap一页到某个地址、页面换出、页保护改变、进程切换等,也即当cache对应的那个地址的内容或含义有所变化时。
当然,很多情况下不需要无效整个cache,只需要无效某个地址或地址范围即可。
实际上,
intel在这方面做得非常好用,cache的一致性完全由硬件维护。
关于x86处理器更多信息,请参照其手册:
Volume3:
ArchitectureandProgrammingManual
8.Linux相关实现
这一部分的代码和体系结构紧密相关,因此大多位于arch子目录下,而且大量以宏定义和inline函数形式存在于头文件中。
以i386平台为例,主要的文件包括:
page.h
页大小、页掩码定义。
PAGE_SIZE,PAGE_SHIFT和PAGE_MASK。
对页的操作,如清除页内容clear_page、拷贝页copy_page、页对齐page_align
还有内核虚地址的起始点:
著名的PAGE_OFFSET:
)和相关的内核中虚实地址转换的宏__pa和__va.。
virt_to_page从一个内核虚地址得到该页的描述结构structpage*.我们知道,所有物理内存都由一个memmap数组来描述。
这个宏就是计算给定地址的物理页在这个数组中的位置。
另外这个文件也定义了一个简单的宏检查一个页是不是合法:
VALID_PAGE(page)。
如果page离memmap数组的开始太远以至于超过了最大物理页面应有的距离则是不合法的。
比较奇怪的是页表项的定义也放在这里。
pgd_t,pmd_t,pte_t和存取它们值的宏xxx_val
pgtable.hpgtable-2level.hpgtable-3level.h
顾名思义,这些文件就是处理页表的,它们提供了一系列的宏来操作页表。
pgtable-2level.h和pgtable-2level.h则分别对应x86二级、三级页表的需求。
首先当然是表示每级页表有多少项的定义不同了。
而且在PAE模式下,地址超过32位,页表项pte_t用64位来表示(pmd_t,pgd_t不需要变),一些对整个页表项的操作也就不同。
共有如下几类:
·[pte/pmd/pgd]_ERROR出措时要打印项的取值,64位和32位当然不一样。
·set_[pte/pmd/pgd]设置表项值
·pte_same比较pte_page从pte得出所在的memmap位置
·pte_none是否为空。
·__mk_pte构造pte
pgtable.h的宏太多,不再一一解释。
实际上也比较直观,通常从名字就可以看出宏的意义来了。
pte_xxx宏的参数是pte_t,而ptep_xxx的参数是pte_t*。
2.4kernel在代码的cleanup方面还是作了一些努力,不少地方含糊的名字变明确了,有些函数的可读性页变好了。
pgtable.h里除了页表操作的宏外,还有cache和tlb刷新操作,这也比较合理,因为他们常常是在页表操作时使用。
这里的tlb操作是以__开始的,也就是说,内部使用的,真正对外接口在pgalloc.h中(这样分开可能是因为在SMP版本中,tlb的刷新函数和单机版本区别较大,有些不再是内嵌函数和宏了)。
pgalloc.h
包括页表项的分配和释放宏/函数,值得注意的是表项高速缓存的使用:
pgd/pmd/pte_quicklist
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 内存 管理 详解