Linux内核物理页分配,提升进程运行效率 (linux 内核为进程分配物理页)

Linux操作系统作为一种开源的操作系统,已经在业界得到了广泛的应用和认可。作为操作系统的核心,内核的有效性和优化对整个操作系统的性能和效率至关重要,而内核物理页分配机制则是内核实现高效的关键之一。

内核物理页分配是指内核在管理物理内存时,对于进程请求的空间进行合理的分配。这个过程不仅关乎进程运行的效率,也与操作系统的安全性和稳定性密切相关。

在Linux内核中,物理页分配是通过伙伴系统实现的。为了提高内核物理页分配的效率,Linux内核引用了伙伴算法,这种算法能够避免内存碎片和浪费的问题。

伙伴系统中,内存被分成一些等大小的块,每个块都有相应的伙伴——一个相同大小的块。当块被申请时,内核会优先选择一组大小相同的伙伴,然后再把它们拼接成一个更大的块,然后再次迭代,直到找到一个块大于等于需要的大小。

随着处理器速度和内存大小的持续增长,对Linux内核物理页分配机制的优化需求越来越高。而内存分配与释放是频繁发生的事件,它们直接影响到操作系统的性能和用户体验。

有很多因素影响了Linux内核物理页分配的效率,其中最重要的因素是内存分配的方式。内存分配方式的不同,直接影响到操作系统的性能和效率,而采用的分配方式不同,会对操作系统的内存管理、交换等部分造成不同的影响。

在低端服务器和桌面计算机上,直接内存分配是最常用的分配方式。这种分配方式的优势在于速度很快,因为它不需要进行复杂的地址转换和页表查找。但是它的缺点也很明显,它没有按照逻辑分类的方式进行分配,可能会造成内存碎片的问题。

另一种是池式内存分配。这种分配方式与直接内存分配方式类似,但它会根据需求动态地分配内存。这种方式的优点是可以尽量避免内存碎片问题,然而,池式内存分配不能很好地处理大块内存的申请请求。

虽然Linux内核伙伴系统已经为系统的内存管理带来了不小的提升,但是随着计算机发展的迅速,对于内核物理页分配的要求也越来越高。

除了伙伴算法和内存分配类型之外,需要考虑的其他因素包括内存交换、进程的内存使用情况和内存管理策略。在内核物理页分配方面,需要考虑的关键要素包括使用的算法、内存池的大小、分配和释放的锁等等。

Linux内核物理页分配是提高操作系统效率的一个非常重要的因素。尽管Linux内核已经引入了伙伴算法和池式内存分配等多种方式来优化物理页分配机制,但是在选择合适的分配方式时,还需根据实际需求情况灵活选择。优化物理页分配机制不仅是内核开发人员的责任,也需要操作系统的用户和系统管理员参与协同。通过共同努力,才能让Linux系统内核物理页分配机制变得更加有效和高效。

相关问题拓展阅读:

malloc()之后,内核发生了什么?

考虑这样一种常见的情况:用户进程调用malloc()动态分配了一块内存空间,悔亮再对这块内存进行访问。这些用户空间发生的事会引发内核空间的那些反映?本文将简单为您解答。1.brk系统调用服务例程malloc()是一个API,这个函数在库中封装了系统调用brk。因此如果调用malloc,那么首先会引发brk系统调用执行的过程。brk()在内核中对应的系统调用服务例程为SYSCALL_DEFINE1(brk, unsigned long, brk),参数brk用来指定heap段新的结束地址,也就是重新指定mm_struct结构中的brk字段。brk系统调用服务例程首先会确定heap段的起始地址min_brk,然后再检查资源的限制问题。接着,将新老heap地址分别按枯渣照页大小对齐,对齐后的地址分别存储与newbrk和okdbrk中。brk()系统调用本身既可以缩小堆大小,又可以扩大堆大小。缩小堆这个功能是通过调用do_munmap()完成的。如果要扩大堆的大小,那么必须先通过find_vma_intersection()检查扩大以后的堆是否与已经存在的某个虚拟内存重合,如何重合则直接退出。否则,调用do_brk()进行接下来扩大堆的各种工作。 SYSCALL_DEFINE1(brk, unsigned long, brk) { unsigned long rlim, retval; unsigned long newbrk, oldbrk; struct mm_struct *mm = current->mm; unsigned long min_brk; down_write(&mm->mmap_sem); #ifdef CONFIG_COMPAT_BRK min_brk = mm->end_code; #else min_brk = mm->start_brk; #endif if (brk start_brk) + (mm->end_data – mm->start_data) > rlim) newbrk = PAGE_ALIGN(brk); oldbrk = PAGE_ALIGN(mm->brk); if (oldbrk == newbrk) goto set_brk; if (brk brk) { if (!do_munmap(mm, newbrk, oldbrk-newbrk)) goto set_brk; goto out; } if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE)) goto out; if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk) goto out; set_brk: mm->brk = brk; out: retval = mm->brk; up_write(&mm->mmap_sem); return retval; } brk系统调用服务例程最后将返回堆的新结束地址。2.扩大堆用户进程调用malloc()会使得内核调用brk系统调用服务例程,因为malloc总是动态的分配内存空间,因此该服务例程此时会进入第二条执行路径中,即扩大堆。do_brk()主要完成以下工作:1.通过get_unmapped_area()在当前进程的地址空间中查找一个符合len大小的线性区间,并且该线性区间的必须在addr地址之后。如果找到了这个空闲的线性区间,则返回该区间的起始地址,否则返回错误代码-ENOMEM;2.通过find_vma_prepare()在当前进程所有线性区组成的红黑树中依次遍历每个vma,以确定上一步找到的新区间之前的线性区对象的位置。如果addr位于某个现存的vma中,则调用do_munmap()删除这个线性区。如果删除成功则继续查找,否则返回错误代码。3.目前已经找到了一个合碧败宽适大小的空闲线性区,接下来通过vma_merge()去试着将当前的线性区与临近的线性区进行合并。如果合并成功,那么该函数将返回prev这个线性区的vm_area_struct结构指针,同时结束do_brk()。否则,继续分配新的线性区。4.接下来通过kmem_cache_zalloc()在特定的slab高速缓存vm_area_cachep中为这个线性区分配vm_area_struct结构的描述符。5.初始化vma结构中的各个字段。6.更新mm_struct结构中的vm_total字段,它用来同级当前进程所拥有的vma数量。7.如果当前vma设置了VM_LOCKED字段,那么通过mlock_vma_pages_range()立即为这个线性区分配物理页框。否则,do_brk()结束。可以看到,do_brk()主要是为当前进程分配一个新的线性区,在没有设置VM_LOCKED标志的情况下,它不会立刻为该线性区分配物理页框,而是通过vma一直将分配物理内存的工作进行延迟,直至发生缺页异常。3.缺页异常的处理过程经过上面的过程,malloc()返回了线性地址,如果此时用户进程访问这个线性地址,那么就会发生缺页异常(Page Fault)。整个缺页异常的处理过程非常复杂,我们这里只关注与malloc()有关的那一条执行路径。当CPU产生一个异常时,将会跳转到异常处理的整个处理流程中。对于缺页异常,CPU将跳转到page_fault异常处理程序中: //linux-2.6.34/arch/x86/kernel/entry_32.S ENTRY(page_fault) RING0_EC_FRAME pushl $do_page_fault CFI_ADJUST_CFA_OFFSET 4 ALIGN error_code: ………… jmp ret_from_exception CFI_ENDPROC END(page_fault) 该异常处理程序会调用do_page_fault()函数,该函数通过读取CR2寄存器获得引起缺页的线性地址,通过各种条件判断以便确定一个合适的方案来处理这个异常。3.1.do_page_fault()该函数通过各种条件来检测当前发生异常的情况,但至少do_page_fault()会区分出引发缺页的两种情况:由编程错误引发异常,以及由进程地址空间中还未分配物理内存的线性地址引发。对于后一种情况,通常还分为用户空间所引发的缺页异常和内核空间引发的缺页异常。内核引发的异常是由vmalloc()产生的,它只用于内核空间内存的分配。显然,我们这里需要关注的是用户空间所引发的异常情况。这部分工作从do_page_fault()中的good_area标号处开始执行,主要通过handle_mm_fault()完成。 //linux-2.6.34/arch/x86/mm/fault.c dotraplinkage void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) { ………… good_area: write = error_code & PF_WRITE; if (unlikely(access_error(error_code, write, vma))) { bad_area_access_error(regs, error_code, address); return; } fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0); ………… } 3.2.handle_mm_fault()该函数的主要功能是为引发缺页的进程分配一个物理页框,它先确定与引发缺页的线性地址对应的各级页目录项是否存在,如何不存在则分进行分配。具体如何分配这个页框是通过调用handle_pte_fault()完成的。 int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, unsigned int flags) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; ………… pgd = pgd_offset(mm, address); pud = pud_alloc(mm, pgd, address); if (!pud) return VM_FAULT_OOM; pmd = pmd_alloc(mm, pud, address); if (!pmd) return VM_FAULT_OOM; pte = pte_alloc_map(mm, pmd, address); if (!pte) return VM_FAULT_OOM; return handle_pte_fault(mm, vma, address, pte, pmd, flags); } 3.3.handle_pte_fault()该函数根据页表项pte所描述的物理页框是否在物理内存中,分为两大类:请求调页:被访问的页框不再主存中,那么此时必须分配一个页框。写时复制:被访问的页存在,但是该页是只读的,内核需要对该页进行写操作,此时内核将这个已存在的只读页中的数据复制到一个新的页框中。用户进程访问由malloc()分配的内存空间属于之一种情况。对于请求调页,handle_pte_fault()仍然将其细分为三种情况: static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pte_t *pte, pmd_t *pmd, unsigned int flags) { ………… if (!pte_present(entry)) { if (pte_none(entry)) { if (vma->vm_ops) { if (likely(vma->vm_ops->fault)) return do_linear_fault(mm, vma, address, pte, pmd, flags, entry); } return do_anonymous_page(mm, vma, address, pte, pmd, flags); } if (pte_file(entry)) return do_nonlinear_fault(mm, vma, address, pte, pmd, flags, entry); return do_swap_page(mm, vma, address, pte, pmd, flags, entry); } ………… } 1.如果页表项确实为空(pte_none(entry)),那么必须分配页框。如果当前进程实现了vma操作函数中的fault钩子函数,那么这种情况属于基于文件的内存映射,它调用do_linear_fault()进行分配物理页框。否则,内核将调用针对匿名映射分配物理页框的函数do_anonymous_page()。2.如果检测出该页表项为非线性映射(pte_file(entry)),则调用do_nonlinear_fault()分配物理页。3.如果页框事先被分配,但是此刻已经由主存换出到了外存,则调用do_swap_page()完成页框分配。由malloc分配的内存将会调用do_anonymous_page()分配物理页框。3.4.do_anonymous_page()此时,缺页异常处理程序终于要为当前进程分配物理页框了。它通过alloc_zeroed_user_highpage_movable()来完成这个过程。我们层层拨开这个函数的外衣,发现它最终调用了alloc_pages()。 static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pte_t *page_table, pmd_t *pmd, unsigned int flags) { ………… if (unlikely(anon_vma_prepare(vma))) goto oom; page = alloc_zeroed_user_highpage_movable(vma, address); if (!page) goto oom; ………… } 经过这样一个复杂的过程,用户进程所访问的线性地址终于对应到了一块物理内存。参考:1.《深入理解LINUX内核》2.《深入LINUX内核架构》

linux 内核为进程分配物理页的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux 内核为进程分配物理页,Linux内核物理页分配,提升进程运行效率,malloc()之后,内核发生了什么?的信息别忘了在本站进行查找喔。


数据运维技术 » Linux内核物理页分配,提升进程运行效率 (linux 内核为进程分配物理页)