![](https://img-blog.csdnimg.cn/20201014180756927.png?x-oss-process=image/resize,m_fixed,h_64,w_64)
疯狂内核之内存管理
文章平均质量分 87
yunsongice
这个作者很懒,什么都没留下…
展开
-
基于80x86的Linux的分段和分页机制
1 基于80x86的Linux分段机制80386的两种工作模式:80386的工作模式包括实地址模式和虚地址模式(保护模式)。Linux主要工作在保护模式下。在保护模式下,80386虚地址空间可达16K个段,每段大小可变,最大达4GB。逻辑地址到线性地址的转换由80386分段机制管理。段寄存器CS、DS、ES、SS、FS或GS各标识一个段。这些段寄存器作为段选择器,用来选择该段的描述符。分段逻辑原创 2010-01-21 15:19:00 · 9234 阅读 · 6 评论 -
分配线性地址区间
<br />前面讲了那么多线性区底层分配的细节,现在让我们讨论怎样分配一个新的线性地址区间。为了做到这点,do_mmap()函数为当前进程创建并初始化一个新的线性区。不过,分配成功之后,可以把这个新的线性区与进程已有的其他线性区进行合并。<br /><br />static inline unsigned long do_mmap(struct file *file, unsigned long addr,<br /> unsigned long len, unsigned long prot,<br />原创 2010-05-31 18:46:00 · 2612 阅读 · 1 评论 -
释放线性地址区间
<br />内核使用do_munmap()函数从当前进程的地址空间中删除一个线性地址区间。<br /> 1 do_munmap()函数<br /> <br />该参数为:进程内存描述符的地址mm,地址区间的起始地址start和它的长度len。要删除的区间并不总是对应一个线性区,它或许是一个线性区的一部分,或许跨越两个或多个线性区。<br /> <br />该函数经过两个主要的阶段。第一阶段(第1一6步),扫描进程所拥有的线性区链表,并把包含在进程地址空间的线性地址区间中的所有线性区从链表中解除链接。第二阶段原创 2010-05-31 18:49:00 · 3987 阅读 · 3 评论 -
处理地址空间以外的错误地址
<br />前面博文提到了,如果address不属于进程的地址空间,那么do_page_fault()函数继续执行bad_area标记处的语句。如果错误发生在用户态,则发送一个SIGSEGV信号给current进程并结束函数:<br /><br />/*<br /> * Something tried to access memory that isn't in our memory map..<br /> * Fix it, but check if it's kernel or user first..原创 2010-05-31 20:16:00 · 2886 阅读 · 0 评论 -
处理地址空间内的错误地址
<br />如果addr地址属于进程的地址空间,则do_page_fault()转到good_area标记处的语句执行:<br />/*<br /> * Ok, we have a good vm_area for this memory access, so<br /> * we can handle it..<br /> */<br />good_area:<br /> si_code = SEGV_ACCERR;<br /> write = 0;<br /> switch (error_code &原创 2010-05-31 20:20:00 · 4101 阅读 · 0 评论 -
请求调页
<br />上一篇博文引出了“请求调页”技术,术语“请求调页”指的是一种动态内存分配技术,它把页框的分配推迟到不能再推迟为止,也就是说,一直推迟到进程要访问的页不在物理RAM中时为止,由此引起一个缺页异常。<br /> <br />请求调页技术背后的动机是:进程开始运行的时候并不访问其线性地址空间中的全部地址。<br /> <br />事实上,有一部分地址也许永远不被进程使用。此外,程序的局部性原理保证了在程序执行的每个阶段,真正引用的进程页只有一小部分,因此临时用不着的页所在的页框可以由其他进程来使用。因原创 2010-05-31 20:24:00 · 3987 阅读 · 1 评论 -
写时复制
<br />第一代Unix系统实现了一种傻瓜式的进程创建:当发出fork()系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。这种行为是非常耗时的,因为它需要:<br />- 为子进程的页表分配页框<br />- 为子进程的页分配页框<br />- 初始化子进程的页表<br />- 把父进程的页复制到子进程相应的页中<br /> <br />这种创建地址空间的方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存中的内容。在大多数情况下,这样做常常是毫无意义的,因为许多子进原创 2010-05-31 20:27:00 · 3468 阅读 · 5 评论 -
处理非连续内存区访问
<br />回忆一下“缺页异常处理程序”,当出现缺页异常,并且是进程处于内核态,即do_page_fault()中的那个if (unlikely(address >= TASK_SIZE))分支语句后,将通过vmalloc_fault(address)判断该发生缺页异常的地址address是否处于非连续内存区:(arch/i386/mm/Fault.c)<br />static inline int vmalloc_fault(unsigned long address)<br />{<br /原创 2010-06-01 10:55:00 · 2595 阅读 · 0 评论 -
堆的管理
<br />一般人喜欢把堆和栈来做对比,网上资料也很多,这里我只分享一下我本人的理解。堆这个东西跟栈没有直接的关联,它只给程序员提供一个手工分配和释放的内存空间,仅此而已。<br /><br />对于每个Unix进程来说,都拥有一个特殊的线性区,这个线性区就是所谓的堆(heap),堆用于满足进程的动态内存请求。内存描述符的start_brk与brk字段分别限定了这个区的开始地址和结束地址。<br /><br />进程可以使用下面的C语言API来请求和释放动态内存:<br /><br />mall原创 2010-06-01 17:35:00 · 2040 阅读 · 0 评论 -
创建和删除进程的地址空间
<br />本博,我们重点关注fork()系统调用为子进程创建一个完整的新地址空间。相反,当进程结束时,内核撤消它的地址空间。我们重点来讨论Linux如何执行这两种操作。<br />1 创建进程的地址空间<br /><br />回忆一下“进程的创建 —— do_fork()函数详解”博文:当创建一个新的进程时内核调用copy_mm()函数。这个函数通过建立新进程的所有页表和内存描述符来创建进程一的地址空间:<br /><br />static int copy_mm(unsigned long原创 2010-06-01 17:17:00 · 3905 阅读 · 0 评论 -
线性区的底层处理
<br />在上一篇博文对控制内存处理所用的数据结构和状态信息有了基本理解以后,我们来看一组对线性区描述符进行操作的低层函数。这些函数应当被看作简化了do_map()和do_unmap()实现的辅助函数。这两个函数将在后面的相关博文中进行描述,它们分别扩大或者缩小进程的地址空间。这两个函数所处的层次比我们这里所考虑函数的层次要高一些,它们并不接受线性区描述符作为参数,而是使用一个线性地址区间的起始地址、长度和访问限权作为参数。<br /> 1 查找给定地址的最邻近区<br /> <br />find_vma原创 2010-05-31 18:39:00 · 2354 阅读 · 2 评论 -
线性区的数据结构
<br />上一篇博文我们会看到,内核使用一种新的资源成功实现了对进程动态内存的推迟分配。当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得对一个新的线性地址区间的使用权,而这一线性地址区间就成为进程地址空间的一部分。这一区间就叫做“线性区”。本博,我们就来详细讨论这个线性区。<br /> 1 线性区数据结构<br /> <br />Linux通过类型为vm_area_struct的对象实现线性区,它的字段如下所示:<br />struct vm_area_struct {<br /> struc原创 2010-05-31 18:30:00 · 3642 阅读 · 1 评论 -
缺页异常处理程序
<br />我们在中断专题中提到,Linux的缺页(Page Fault)异常处理程序必须区分以下两种情况:由编程错误所引起的异常,及由引用属于进程地址空间但还尚未分配物理页框的页所引起的异常。<br /> <br />线性区描述符可以让缺页异常处理程序非常有效地完成它的工作。do_page_fault()函数是80x86上的缺页中断服务程序,它把引起缺页的线性地址和当前进程的线性区相比较,从而能够根据和下图所示的方案选择适当的方法处理这个异常。<br /> <br /><br /> <br /> <br原创 2010-05-31 20:14:00 · 12652 阅读 · 7 评论 -
伙伴系统算法
讲了这么多了,很多人肯定会一头雾水,前边提到的都是些数据结构或者是些概念性的东西,真正对动态页面的管理机制在哪里?换句话说,如何将每个节点,每个区中的页框分配给进程?要理清这个思路,我们首先必须学习一种算法 —— 伙伴系统算法。 内核要分配一组连续的页框,必须建立一种健壮、高效的分配策略。为此,必须解决著名的外部碎片(external fragmentation)问题。频繁地请求和释放不同原创 2010-01-22 17:43:00 · 11960 阅读 · 6 评论 -
Linux内存布局
在上一篇博文里,我们已经看到Linux如何有效地利用80x86的分段和分页硬件单元把逻辑地址转换为线性地址,在由线性地址转换到物理地址。那么我们的应用程序如何使用这些逻辑地址,整个内存的地址布局又是怎样的?打一个比方,内存就像一座城市,而居住在这个城市里的市民就像是各个进程,一个市民吃喝拉撒睡,当然就得用于“房子”、“车子”、“票子”等各种各样的资源。有些资源是固定的,如“房子”,我们称之为静态数原创 2010-01-22 11:32:00 · 5781 阅读 · 0 评论 -
Linux页框管理
在前面的博文里,我们讲解了基于80x86体系的Linux内核分段和分页机制,并详细地讨论了Linux的内存布局。有了这些基本概念以后,我们就来详细讨论内核如何动态地管理那些可用的内存空间。 对于80386这种32位的处理器结构,Linux采用4KB页框大小作为标准的内存分配单元。内核必须记录每个页框的当前状态,例如,区分哪些页框包含的是属于进程的页,而哪些页框包含的是内核代码或内核数据。内原创 2010-01-22 15:20:00 · 8783 阅读 · 0 评论 -
Linux页框级内存管理处理细节
弄清楚伙伴系统算法的原理以后,我们就可以开开心心地处理页框了。 我们可以通过6个稍有差别的函数和宏请求页框。一般情况下,他们都返回第一个所分配页的线性地址,或者分配失败则返回NULL。alloc_pages(gfp_mask, order):用这个函数请求2order 个连续的页框。他返回第一个所分配页框描述符的地址,或者如果失败,则返回NULL。alloc_page(gfp_ma原创 2010-01-25 11:12:00 · 5142 阅读 · 5 评论 -
高端内存映射
1 内核空间和用户空间 用户空间:在Linux中,每个用户进程都可以访问4GB的线性虚拟内存空间。其中从0到3GB的虚存地址是用户空间,通过每个进程自己的页目录、页表,用户进程可以直接访问。内核空间:从3GB到4GB的虚存地址为内核态空间,存放供内核访问的代码和数据,用户态进程不能访问,只有内核态进程才能寻址。所有进程从3GB到4GB的虚拟空间都是一样的,linux以此方式让内核态原创 2010-01-26 17:02:00 · 9945 阅读 · 0 评论 -
slab分配器
前面详细讨论了伙伴系统算法,以及基于该算法的页框管理细节。这些内容都是采用页框作为基本内存区,这适合于对大块内存的请求。但是,内核如何处理对一些数据结构分配内存空间,大多数数据结构根本占用不到一个页框。我们如何处理对小内存区的请求呢,比如说几十或几百个字节?显然,如果为了存放很少的字节而给它分配一个整页框,这显然是一种浪费。取而代之的正确方法就是引入一种新的数据结构来描述在同一页框中如何分原创 2010-01-30 15:56:00 · 11541 阅读 · 5 评论 -
每CPU页框高速缓存
在“Linux页框级内存管理处理细节”一篇博文中,我们谈到内核调用alloc_pages等系列函数分配一个或一片连续的页框。这一系列函数本质上是使用伙伴算法从指定zone_t中取到一个或一片连续的空闲的页框。正如我们将在以后重点博文“slab分配器”所看到的,内核经常请求和释放单个页框。为了提升系统性能,如果请求单个或释放单个页框时,内核在使用伙伴算法之前多添了一个步骤,即每CPU页框高速原创 2010-01-29 19:04:00 · 5500 阅读 · 1 评论 -
非连续内存区
从前面的博文中我们已经知道,把一块存放slab结构的内存区映射到一组连续的物理页是最好的选择,这样会充分利用高速缓存并获得较低的平均访问时间。不过,上面的方式主要是针对那些使用非常频繁的内核数据结构——如task_struct、inode来设计的。如果对内存区的请求不是很频繁,那么,通过连续的线性地址,而不是物理地址来访问非连续的物理页框这样一种分配模式就会很有意义了。这种模式的主原创 2010-04-27 20:19:00 · 5451 阅读 · 0 评论 -
内存描述符
<br />在前面的系列博文中我们已经看到,内核中的函数以相当直截了当的方式获得动态内存:__get_free_pages()或alloc_pages()通过伙伴算法从分区页框分配器中获得页框,kmem_cache_alloc()或kmalloc()使用slab分配器为专用或通用对象分配内存,而vmalloc()或vmalloc_32()获得一块非连续的内存区。如果所请求的内存区得以满足,这些函数都返回一个页描述符或线性地址(即所分配动态内存区的起始地址)。<br /> <br />使用这些简单方法是基于以原创 2010-05-31 16:37:00 · 3594 阅读 · 0 评论 -
Linux x86_64与i386区别之 —— 内存寻址
1 引子毫无疑问,不管是32位,还是64位处理器,所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。对任何一个普通进程来讲,它都会涉及到5种不同的数据段。稍有编程知识的朋友都该能想到这几个数据段种包含有“程序代码段”、“程序数据段”、“程序堆栈段”等。不错,这几种数据段都在其中,但除了以上几种数据段之外,进程还另外包含两原创 2010-08-18 17:13:00 · 13783 阅读 · 3 评论