⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 linux-+

📁 linux下内存管理的文章 很长
💻
📖 第 1 页 / 共 4 页
字号:
<br> 
&nbsp;          
内存分区可以使内核页分配更加合理。当系统物理内存大于1G时,内核不能将所有的物理内存都预先映射到内核空间中,这样就产生了高端内存,高端内存最适于映射到用户进程空间中。预映射的部分可直接用于内核缓冲区,其中有一小块可用于DMA操作的内存,留给DMA操作分配用,一般不会轻易分配。内存分区还可以适应不连续的物理内存分布,是非一致性内存存取体系(NUMA)的基础。<br> 
<br> 
<br> 
</span>先看看代码中的注释:<span class="post"><br> 
In <b>linux\include\linux\<font color="#FF0000">mmzone.h</font></b>(version            
2.4.16, line 67)<br>           
/*<br> 
&nbsp;* On machines where it is needed (eg PCs) we divide physical memory<br>           
&nbsp;* into multiple physical zones. On a PC we have 3 zones:<br>           
&nbsp;*<br> 
&nbsp;* ZONE_DMA&nbsp; &lt; 16 MB ISA DMA capable memory<br>           
&nbsp;* ZONE_NORMAL16-896 MB direct mapped by the kernel<br>           
&nbsp;* ZONE_HIGHMEM &gt; 896 MB only page cache and user processes<br>           
&nbsp;*/</span></p> 
<p>高端页面的映射<br> 
1)<br> 
&nbsp; 高端物理页面共享一块<font color="#0000FF">4M的映射区域</font>,该区域对齐于4M页边界,并用一张页表(<font color="#800080">pkmap_page_table)</font>来完成映射操作。高端页面的映射地址由其页结构中virtual成员给出。<br>          
2)<br> 
&nbsp; 高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,对应于映射区内不同的逻辑页面。当分配项的值等于零时为自由项,等于1时为缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系统将进入等待状态。<br>           
3)<br> 
&nbsp; 页缓冲尽可能地使用高端页面,当通过块结构刷新高端页面时,系统会在提交块设备<br&gt>            
,原请求块,同时中转块被释放。<br>           
<br> 
&nbsp; 还是结合源码看一看, 给我的感觉是这样的:<br>         
在 <font color="#FF0000">include/linux/<b>highmem.h </b></font>中没有定义 <font color="#FF0000">CONFIG_HIGHMEM</font>          
时, 有&nbsp;<br>         
 void *<b><font color="#0000FF">kmap</font></b>(struct page *page) { return <font color="#0000FF">page_address</font>(page); }<br>          
#define <font color="#0000FF">page_address</font>(page) ((page)->virtual)<br>          
而在定义了<font color="#FF0000">CONFIG_HIGHMEM</font> 时其定义变为:<br>         
<font color="#FF0000">include/linux/asm_i386/<b>highmem.h</b></font><br> 
static inline void *<b><font color="#0000FF">kmap</font></b>(struct page *page)<br>         
{<br> 
if (in_interrupt())<br>         
BUG();<br> 
if (page &lt; <b><font color="#800080">highmem_start_page</font></b>) <font color="#0000FF">//如果是低端内存,我们相信page自己记录的虚拟地址</font><br>         
return page_address(page);<br>         
return <font color="#00AA00">kmap_high</font>(page); <font color="#0000FF">//高端内存可能需要重新映射</font><br>         
}<br> 
<br> 
void *<b><font color="#0000FF">kmap_high</font></b>(struct page *page)<br>         
{<br> 
unsigned long vaddr;<br>         
<font color="#0000FF">/*<br> 
 * For highmem pages, we can't trust &quot;virtual&quot; until<br> 
 * after we have the lock.<br> 
 *<br> 
 * We cannot call this from interrupts, as it may block<br> 
 */</font><br> 
spin_lock(&amp;kmap_lock);<br> 
vaddr = (unsigned long) page-&gt;virtual;<br>         
if (!vaddr)<br>         
vaddr = <font color="#00AA00">map_new_virtual</font>(page); <font color="#0000FF">//对于已经被冲掉的页面,需要重映射</font><br>         
<b><font color="#800080">pkmap_count</font></b>[PKMAP_NR(vaddr)]++; <font color="#0000FF">//逻辑页面操作</font><br>         
if (pkmap_count[PKMAP_NR(vaddr)] &lt; 2)<br>         
BUG();<br> 
spin_unlock(&amp;kmap_lock);<br> 
return (void*) vaddr;&nbsp;&nbsp;&nbsp; <font color="#0000FF">//返回映射好的虚拟地址</font><br>         
} 
</p> 
<hr> 
<p><b><font size="6" color="#0000FF"><a name="VM 和 vmalloc">VM 和 vmalloc</a></font></b></p>             
<p><span class="post"><br> 
&nbsp;&nbsp; 先和malloc比一比.</span></p>             
<p><span class="post">&nbsp; malloc和内核无关,事实上内核不知道malloc这回事,它是c              
lib做的,c lib在brk-end_code之间用malloc, mfree分配,如果没有地方分配了(比如碎片太多,或者没有空间了),就调用sys_brk,这是个系统调用,内核会分配一个页块,然后将brk上面的空间映射到这个页块,系统调用返回后,malloc会在这新分的空间上做一些细小的分配。<br>             
<br> 
&nbsp; vmalloc是内核用的,我们知道3 G-4G是内核空间,但是只有3G-3G+mem是被固定映射的,(mem是内存大小或指定的数值),上面还会有一些空间没有映射,vmalloc用来映射这个空间,有点象对用户空间的vma的操作。它一般分配的粒度都是页面。<br>             
<br> 
&nbsp; 如果内存大于(其实接近1G)1G,需要high mem的机制。vmalloc分配的空间中间是有洞的,起到保护的作用,可以看以前jkl的帖子。do_page_fault里有对vmalloc的操作。</span></p>             
<p>&nbsp; 而linux中对虚存的概念,linux对虚存的管理其实是用vma进行的.进程的0-4G空间中,user用到0-2G,用户编程序时对虚存线性空间的访问,用MMU对其进行到物理地址的转化,如果发生缺页错误,kernel用vma进行判断此地址是否分配,如果是才将物理页分配,再对此4k的虚存地址操作便不会发生缺页,对vma的操作是do_mmap和do_munmap进行的,象<font color="#00aa00"><span class="post">lucian_lao</span></font>所说malloc              
lib c的函数,它并不是每次都调brk,而是现有vm不够用时,调用sys_brk(),而brk为可用堆上限-end_code的值,sys_brk将增大brk值,并调用do_mmap分配vm.              
free相反.<br> 
<br> 
&nbsp; vmalloc是kernel进程用的函数,不同于kmalloc.kmalloc和kfree管理的kernel的内存是真正的物理地址,而vmalloc分配的是kernel的vm,祥见<LINUX内核源代码分析              
562页> 
,vmalloc是从VMALLOC_START到VMALLOC_END之间的虚存,VMALLOC_START一般是kernel物理地址后8M开始的,其实我自己认为它更和user空间的do_mmap相似,用于分配kernel空间的虚存,而它的操作有一部分通过kmalloc分配一部分不可交换的内存.但是其vmlist并不象user空间一样存在AVL树来增加查找速度.<br>             
</p> 
<p>&nbsp;&nbsp;&nbsp; 我们来总结一下:<br>             
&nbsp;&nbsp;&nbsp; <span class="post">在初始化的时候,3G~3G+mem--&gt;0~mem的映射有了,而且是固定不变的。vmalloc的部分(3G+mem+128M?,              
-固定映射区)的映射并不是总被定义,而且会变.vmalloc就是分一个页面,建一个映射,把分到的vm地址返回.vmalloc修改的是</span><font color="#800080"><b>swapper_pg_dir  
,</b></font>基准页目录.这样的话也会产生一些问题,举一个例子(<a href="http://www.linuxforum.net/forum/showprofile.php?Cat=&amp;User=lucian_yao&amp;Number=145018&amp;Board=linuxK&amp;what=showthreaded&amp;page=&amp;view=&amp;sb=&amp;o=&amp;fpart=&amp;vc=1"><font color="#00aa00">lucian_yao</font></a><font color="#00aa00">)&nbsp;</font></p> 
<p><span class="post">一个问题我没有想清楚:假定在某个时候用__vmalloc将[              
3.5G, 3.5G+1M ]映射到[ 2M, 3M ]这个时候进入进程A,然后在中断中访问[3.5G,              
3.5G+1M]这段空间,由缺页fault将进程A的页表补上[3.5G, 3.5G+1M]映射到[              
2M, 3M ]然后,释放了[ 3.5G, 3.5G+1M ],再次重新分配[3.5G, 3.5G+1M],这个时候[3.5G,              
3.5G+1M]映射到[ 5M, 6M ]但是这个时候(仍然使用进程A的页表)访问[3.5G,              
3.5G+1M],由于没有出现缺页fault,访问到的实际地址是[ 2M, 3M ],这不是不对了吗?是不是我理解上有什么问题?<br>             
<br> 
</span><span class="post">(</span><a href="http://www.linuxforum.net/forum/showprofile.php?Cat=&amp;User=lucian_yao&amp;Number=145018&amp;Board=linuxK&amp;what=showthreaded&amp;page=&amp;view=&amp;sb=&amp;o=&amp;fpart=&amp;vc=1"><font color="#00aa00">jkl</font></a><font color="#00aa00">)</font><span class="post"><br> 
在进程的内核页目录中补上的是只是页目录项,而页表对所有进程来说是共用的,不管vfree()多大的内存块,在vmalloc()时新分配的页表不会被释放,当重新vmalloc()时,仍旧使用原来的页表。</span><a href="#do_page_fault"><font color="#0000FF">do_</font><span class="post"><b><font color="#0000FF">page_fault</font></b></span></a><span class="post">使得进程的内核页目录项与swapper_pg_dir保持同步,swapper_pg_dir的内核页目录项一旦建立就不再被改变,需要改变的只是共享的页表而已。</span></p> 
<p><span class="post">&nbsp; 说的神些,kmalloc分配连续的物理地址,vmalloc分配连续的虚拟地址.并且有如下结论:<br>             
memory.c 主要负责一部分用户空间的虚存映射工作 也就是 0-3G的映射<br>             
<b><i>vmalloc</i></b>.c 主要负责内核空间高端的内存分配和映射<br>             
<br> 
实际上:<br> 
<br> 
用户空间 0 - 3G do_brk 调用来分配, 还有一些函数处理映射工作(比如memory.c中的函数)<br>             
内核空间 3G - 3G + mem kmalloc, __get_free_pages 来分配<br>             
内核空间 3G + mem + 隔离带 - 4G <b><i>vmalloc</i></b><br>             
<br> 
其中 mem              
可以看成是内存的大小,会自动检测到,也可以由命令行指定.</span><span class="post"><br> 
<br> 
memory.c 中的的copy_page_range。clear_page_tables等函数是用于              
建立或撤消映射,do_wp_page,do_no_page,do_swap_page等用于页面故障时处理。而vmscan.c则是处理页面交换。<br> 
<br> 
<br> 
&nbsp;&nbsp; 并且vmalloc为了捕获越界,vm中间是有洞的.关于这些,看看下面的讨论把:<br>             
&nbsp;&nbsp; 提问:<br>             
&nbsp;&nbsp; vmalloc()函数分配内存的虚拟地址从3G+high_memory+hole_8M开始其中hign_memory为实际物理内存,hole_8M为8M的隔离带为什么要有8M的隔离带?这样岂不是很浪费虚拟地址空间吗?另外分配的内存块之间有一个4K的隔离页,这样是不是也很浪费虚拟地址空间?</span></p>             
<p><span class="post">&nbsp;&nbsp; </span><span class="post">(</span><a href="http://www.linuxforum.net/forum/showprofile.php?Cat=&amp;User=lucian_yao&amp;Number=145018&amp;Board=linuxK&amp;what=showthreaded&amp;page=&amp;view=&amp;sb=&amp;o=&amp;fpart=&amp;vc=1"><font color="#00aa00">jkl</font></a><font color="#00aa00">)<br>             
&nbsp;&nbsp; <span class="post">Linux用这些空洞来检测存储器读写越界故障,当然空洞越大出现破坏性故障的可能性就越小,8M的空洞正好用两个页目录项来标记,4K的空洞用一个页表项来标记,由于一般物理内存远小于线性地址空间,因此这种浪费是微不足到的。<br>             
<br> 
&nbsp;&nbsp;</span></font><span class="post">  
谢谢您的回答,第一次请教问题,就得到您的耐心回答,非常感谢我还是有点不明白:<br>             
那为何用两个页目录表,用一个不行吗?甚至用一个4k的页表不就够了吗?加一个空洞,是不是利用了页保护的属性?如果越界的话,不论空洞的大小都应该产生异常,对吗?不明白空洞越大越安全的原因另外3G+phymem+8M给vmaloc剩下的空间不多了,如果实际物理内存接近1G的话,是不是vmaloc函数就不能使用了?我感觉空间并不充裕<br> 
</span></p> 
<p><span class="post">&nbsp;&nbsp; </span><span class="post">(</span><a href="http://www.linuxforum.net/forum/showprofile.php?Cat=&amp;User=lucian_yao&amp;Number=145018&amp;Board=linuxK&amp;what=showthreaded&amp;page=&amp;view=&amp;sb=&amp;o=&amp;fpart=&amp;vc=1"><font color="#00aa00">jkl</font></a><font color="#00aa00">)</font><span class="post"><font color="#00aa00"><br>             
&nbsp;&nbsp;&nbsp;              
隔离带的大小是任意的,但如果隔离带不够大的话,有可能会被故障代码跨过引起破坏。当内核有代码引用到这些隔离带的地址时,这些地址对应的页目录项或页表项由于被标记为"不存在",就会产生页故障,这样就可以准确定位故障所在。如果物理内存非常大,造成内核虚拟空间不足时,可以减小内核的起始线性地址,通过减小用户程序的虚拟地址空间来增大内核的虚拟空间。如果物理内存超过2G,可通过一个内核补丁<a href="http://oss.sgi.com/projects/bigmem/">big  
Physical Memory for IA-32</a>将应用程序与内核的页目录分开,尽管这样还是只能管理3.8G,如果物理内存还要大,就要使用64位的体系了。<br> 
<br> 
&nbsp;&nbsp;&nbsp;&nbsp;<br> 
<br> 
<br> 
<br> 
</font></span><span class="post"><br> 
<br> 
&nbsp;&nbsp;</span></p> 
<hr> 
<p><b><font size="6" color="#0000FF"><a name="物理内存管理">物理内存管理</a></font></b></p> 
<p><font color="#0000FF">kernel页表</font><br> 
kernel的pgd是在开始setup_32时初始化的,它应该使cr3指向swapper_pg_dir(这个变量在arch\i386\kernel.head.s中),它的定义如下:<br> 
ENTRY(swapper_pg_dir)<br> 
.long 0x00102007<br>  
.fill __USER_PGD_PTRS-1,4,0<br>  
/* default: 767 entries */<br>  
.long 0x00102007<br>  
/* default: 255 entries */<br>  
.fill __KERNEL_PGD_PTRS-1,4,0<br>  
这里面只有两项非空,用户空间的第一项和kernel空间的第一项,它们都指向地址0x00102000,也就是pg0,这个页表中装的是从物理地址0-4M的内容。但是这个pgd并不会一直这样,在start_kernel中会调用paging_init把它完全重新改掉。paging_init将初始化线性空间从start_mem到end_mem的页表项。它做的第一件事就是把swapper_pg_dir的第一项清0,用于捕获null访问。然后它会进入一个循环,这个循环有两种结果,如果你的CPU是Pentium以上,那么它就把分成4M为单位的页。然后把填入pgd中;否则就按4K进行分页,如果pgd中的项为空,就从start_mem开始分配一页作为页表,然后在页表中顺序填入物理页帧的首地址,如果超过了end_mem,就在页表中填入0。<br> 
<br> 
在swapper_pg_dir中的第一项和第768项都有一个指向pg0的项,因为,初始化的时候,其中有两个工作要做:<br> 
1.启动页机制,这个时候从物理地址寻址转为虚地址寻址,为确保平滑过渡,在低端(0到4M)的映射是恒同映射<br> 
2 跳到内核,实际上内存仍然保存在物理地址0-4M,这个时候,实际上是通过虚拟地址3G-4G访问的,为了平滑过渡,将3G-3G+4M的虚地址映射和0-4M虚地址一致,这样,开启页面映射后跳到内核时已经运行于3G以上的地址了,从这以后,内核自己的寻址都在3G以上了。<br>  
<br> 
</p> 
<p><font color="#0000FF">内核的内存分配</font>主要会涉及到三组分配函数:&nbsp;<br> 
1)页面分配器&nbsp;<br> 
&nbsp;&nbsp; __get_free_pages()/__free_pages()&nbsp;<br>         
2)0xc0000000 ~ 0xc0000000+phymem&nbsp;<br>         
&nbsp;&nbsp; kmalloc()/kfree()&nbsp;<br>         
3) 0xc0000000+phymem+8M_hole ~ 4G&nbsp;<br>         
&nbsp;&nbsp; vmalloc()/vfree()&nbsp;<br>         
</p> 
<p>以下简要的给予介绍:</p> 
<p>一:页面分配器:&nbsp;<br> 
&nbsp;&nbsp;&nbsp;          
页面分配器是最底层的内存分配,主要用于物理内存页的分配,在内核初始化时,调用paging_init创建swap_page_dir,使得从PAGE_OFFSET到PAGE_OFFSET+PhyMem的内核虚拟空间与0~PhyMem的物理空间建立起一一对应的关系。所以__get_free_pages返回的是实际物理+PAGE_OFFSET(由ADDRESS宏实现变换)。          
页面分配器采用的是“伙伴”算法。主要涉及两个重要全局变量。<br> 
&nbsp;&nbsp;&nbsp; 1)struct free_area_struct          
free_area[NR_MEM_TYPES][NR_MEM_LISTS]; 空闲块数组&nbsp;<br>         
&nbsp;&nbsp;&nbsp; 2)mem_map_t * mem_map          
逻辑页,标示每个物理页的使用情况,根据系统的实际内存,在内核初启时,由mem_init初始化。          
具体实现请阅读源码及参看《UNIX高级教程 系统技术内幕》、<br>         
</p> 
<p>二:kmalloc/kree kmalloc<br>         
&nbsp;&nbsp; 分配的是从PAGE_OFFSET~PAGE_OFFSET+PhyMem之间的内核空间,用于分配连续物理空间。将kmalloc返回值减去PAGE_OFFSET就是实际的物理地址。          
我现在看的源码(2.2.14)kmalloc实现采用了slab分配器算法。具体的实现请参阅lucian_yao以前的贴子及          
《UNIX高级教程 系统技术内幕》。</p>         
<p>三:vmalloc/vfree&nbsp;<br> 
&nbsp;&nbsp; 用于分配内核位于PAGE_OFFSET+PhyMem+8M_hole ~ 4G的虚拟空间,          
vmalloc的实现比较简单,主要是维护struct vm_struct vmlist链表          
当然vmalloc会调用kmalloc以分配vm_struct,然后为虚拟空间创建页表。&nbsp;<br> 
&nbsp;&nbsp; 这里我有一个疑问就是用vmalloc分配的内存似乎不会被swap出去,希望有高手指教。再有就是用malloc分配的位于数据段上端至brk之间的堆。          
这部分似乎要用到sys_remap,sys_munmap系统调用,以后看源码再说吧。刚看了点皮毛,写出点心得,就是想暴露一下自己的一些模糊概念,以期有高手指正。&nbsp;</p> 
<p>[jkl]内核要求实时性很高,可加载模块本身就是用vmalloc()分配的内存,它是不能允许极慢的磁盘交换的。</p> 
<p> </p> 
<hr> 
<p><a name="slab 管理"><font face="Arial Black" size="6" color="#0000FF">s</font></a><a name="slab 管理"><font face="Arial Black" size="6" color="#0000FF">lab  
</font><font size="6" color="#0000FF"> 管理</font></a></p>                    
<p><span class="post"><font color="blue">slab分配器在内存分配中起的作用</font><br> 
<br> 
slab分配器通过页面级分配器获得页块后,做进一步的精细分配,<br> 
将这个页块分割成一个个的对象,有点类似c中的malloc<br> 
c, mfree的作用。 
</span></p>                   
<p><span class="post"><font color="blue">cache描述符<br> 
</font><br> 
<font color="green">struct kmem_cache_s {<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* 1) each alloc &amp; free */<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* full, partial first, then free */<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct list_head slabs;<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct list_head *firstnotfull;<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int objsize;<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int flags; /* constant flags */<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int num; /* # of objs per slab */<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; spinlock_t spinlock;<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #ifdef CONFIG_SMP<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int batchcount;<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #endif<br> 
<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* 2) slab additions /removals */<br> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* order of pgs per slab (2^n) */<br> 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -