linux2.6内核内存描述符与线性区分析

2024-01-06 21:58

本文主要是介绍linux2.6内核内存描述符与线性区分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

内存描述符与线性区

用户的等级与内核的等级是不同的,这个原因造成了当用户进程请求动态内存的时候,会被“拖延”,同时内核对用户进程也充满警惕,时刻准备捕获用户进程引起的所有寻址错误。用户进程所获得的空间是虚拟内存,是被称为“线性区”的空间,是一堆线性地址空间的使用权。而描述这一框架的数据结构就是内存描述符,线性区对象。内存描述符在slab分配器中,内存描述符彼此形成一个链表,而其中的字段的功能之一就是指向线性区的开始,和最后一个引用的线性区。也就是字段:mmap,mmap_cache。而线性区也彼此形成一个链表。mmap指向的就是当前内存描述符之下的线性区链表头。而线性区对象中的字段:vm_start,vm_end就是用来标示在线性地址空间的内存段信息的。所以在用户看来,这个结构的结果就是使用的一段线性的非连续内存区。线性区对象链表的作用就是将这些非连续的内存区变成看起来连续的一段线性区地址空间。也就是每个线性区都由一组号码连续的页所组成。

线性区对象中的字段:vm_mm,所指向的就是线性区所在的内存描述符,通过这几个关键结构,内存描述符,线性区对象,线性区地址空间,三者之间形成彼此联系。而在进程中,他的描述符中字段:mm,就是指向内存描述符mm_struct的。以上就是从进程到线性地址空间的框架。

但是需要特别注意的一点是,在线性区对象看来,他们是彼此链接成一个链表的,但是在内存描述符中有个字段:mm_rb.同样在线性区对象中也有个字段:vm_rb.这两个的作用就是红黑树的头。之所以在链表之外再形成一个树形结构的目的,就在于减少对链表元素操作时的系统开销。也就是可以更快的定位,而不用扫描链表。

#define allocate_mm()   (kmem_cache_alloc(mm_cachep, SLAB_KERNEL))
#define free_mm(mm)     (kmem_cache_free(mm_cachep, (mm)))

struct mm_struct * mm_alloc(void)
{
        struct mm_struct * mm;
        mm = allocate_mm();
        if (mm) {
                memset(mm, 0, sizeof(*mm));
                mm = mm_init(mm);
        }
        return mm;
}

通过一个申请内存描述符的函数,可以看到,追中还是还原到kmem_cache_alloc,也就是说,这些是被保存在slab分配器高速缓存中。联想在前面讨论slab分配器的矿价事,slab描述符之后是很多的同类slab对象。联系伙伴系统的讨论,基本就可以大体构建起一个内存管理的基本框架了。所有的内存描述符组成一个双向链表,而init_mm是初始化阶段进程0所使用的内存描述符。

描述线性区对象的数据结构是vm_area_struct.前面谈到,线性区是一堆连续的页来组成的。而同样,这些页也有很多特殊的标志。在字段:vm_flags中。以表示这个线性区中页的信息,以及线性区的信息。

#define VM_READ         0x00000001      /* currently active flags */
#define VM_WRITE        0x00000002
#define VM_EXEC         0x00000004
#define VM_SHARED       0x00000008

#define VM_MAYREAD      0x00000010      /* limits for mprotect() etc */
#define VM_MAYWRITE     0x00000020
#define VM_MAYEXEC      0x00000040
#define VM_MAYSHARE     0x00000080

#define VM_GROWSDOWN    0x00000100      /* general info on the segment */
#define VM_GROWSUP      0x00000200
#define VM_SHM          0x00000400      /* shared memory area, don't swap out */
#define VM_DENYWRITE    0x00000800      /* ETXTBSY on write attempts.. */

#define VM_EXECUTABLE   0x00001000
#define VM_LOCKED       0x00002000
#define VM_IO           0x00004000      /* Memory mapped I/O or similar */

                                        /* Used by sys_madvise() */
#define VM_SEQ_READ     0x00008000      /* App will access data sequentially */
#define VM_RAND_READ    0x00010000      /* App will not benefit from clustered reads */

#define VM_DONTCOPY     0x00020000      /* Do not copy this vma on fork */
#define VM_DONTEXPAND   0x00040000      /* Cannot expand with mremap() */
#define VM_RESERVED     0x00080000      /* Don't unmap it from swap_out */
#define VM_ACCOUNT      0x00100000      /* Is a VM accounted object */
#define VM_HUGETLB      0x00400000      /* Huge TLB Page VM */
#define VM_NONLINEAR    0x00800000      /* Is non-linear (remap_file_pages) */

对于线性区的操作也是至关重要的。有几个函数负责这些工作。

find_vma,函数的名字寻找虚拟内存区。这就是作用,而两个参数mm,addr.前者是进程内存描述符地址,而后者是线性地址。通过第二个if语句,可以看出这个函数的具体功能:vma->vm_end > addr && vma->vm_start <= addr,当这两个为真的时候,那么addr,一定是在当前线性区中的,此时直接返回vma = mm->mmap_cache就可以了。而当不在当前线性区中的时候,就需要找到这个区。此时就是用红黑树来找。rb_node = mm->mm_rb.rb_node;这句就是将从内存描述符中的红黑树根节点来开始。while循环体的作用就在于此。rb_entry函数的作用就是从指向红黑树中一个节点的指针导出相应线性区描述符的地址。在函数中是从根节点开始判断的。当end>addr的时候,那么就要从当前节点开始,向他的左孩子寻找。直到start<addr的时候,跳出,否则以左孩子为当前节点,继续循环。也由此可以看出,此函数的作用就是找到end>addr的第一个线性区位置。然后设置指针,mm->mmap_cache = vma,表示最后一个引用的线性区对象地址。return vma。

struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
{
        struct vm_area_struct *vma = NULL;
        if (mm) {
                vma = mm->mmap_cache;
                if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
                        struct rb_node * rb_node;
                        rb_node = mm->mm_rb.rb_node;
                        vma = NULL;
                        while (rb_node) {
                                struct vm_area_struct * vma_tmp;
                                vma_tmp = rb_entry(rb_node,struct vm_area_struct, vm_rb);
                                if (vma_tmp->vm_end > addr) {
                                        vma = vma_tmp;
                                        if (vma_tmp->vm_start <= addr)
                                                break;
                                        rb_node = rb_node->rb_left;
                                } else
                                        rb_node = rb_node->rb_right;
                        }
                        if (vma)
                                mm->mmap_cache = vma;
                }
        }
        return vma;
}

而通过以上函数,类似的还有一个函数:此函数通过引用上述函数,并修改参数变为start_addr,以及在判断中使用end_addr <= vma->vm_start,作用很明显,就是找到一个与start_add开始的地址相重叠的线性区。而类似的函数:insert_vm_area,向内存描述符链表中插入一个线性区,方式也同样。

static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
{
        struct vm_area_struct * vma = find_vma(mm,start_addr);
        if (vma && end_addr <= vma->vm_start)
                vma = NULL;
        return vma;
}

而分配与释放地址区间,有两个关键函数:do_mmap(),do_munmap().

static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)
{
    …………
        if (!(offset & ~PAGE_MASK))
                ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);
out:
        return ret;
}

函数中,只是做了一些初步检查,然后就引用函数do_mmap_pgoff,这是个关键函数。他的参数所代表的信息是很丰富的。
file:这是一个文件描述符指针。
offset:文件偏移量。
len:线性地址区间的长度。

prot:指定这个线性区所包含页的访问权限。
#define PROT_READ       0x1             /* page can be read */
#define PROT_WRITE      0x2             /* page can be written */
#define PROT_EXEC       0x4             /* page can be executed */
#define PROT_SEM        0x8             /* page may be used for atomic ops */
#define PROT_NONE       0x0             /* page can not be accessed */
#define PROT_GROWSDOWN  0x01000000      /* mprotect flag: extend change to start of growsdown vma */
#define PROT_GROWSUP    0x02000000      /* mprotect flag: extend change to end of growsup vma */

flag:线性区的标志。
#define MAP_SHARED      0x01            /* Share changes */
#define MAP_PRIVATE     0x02            /* Changes are private */
#define MAP_TYPE        0x0f            /* Mask for type of mapping */
#define MAP_FIXED       0x10            /* Interpret addr exactly */
#define MAP_ANONYMOUS   0x20            /* don't use a file */

#define MAP_GROWSDOWN   0x0100          /* stack-like segment */
#define MAP_DENYWRITE   0x0800          /* ETXTBSY */
#define MAP_EXECUTABLE  0x1000          /* mark it as an executable */
#define MAP_LOCKED      0x2000          /* pages are locked */
#define MAP_NORESERVE   0x4000          /* don't check for reservations */
#define MAP_POPULATE    0x8000          /* populate (prefault) pagetables */
#define MAP_NONBLOCK    0x10000         /* do not block on IO */

do_mmap_pgoff函数虽然较长,但是却容易阅读,程序的开始,对几个参数进行了验证,是对于请求能否通过的检查。所涉及的参数有file、prot、len、mm->map_count。都没有问题后,引用函数get_unmapped_area,得到新线性区的线性地址区间。紧接着是这一句:

vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;

vm_flags中最后存放的是新线性描述符的标志。是通过prot,flags来计算而出的。以上就是准备工作,如果要分配新的描述符,那么肯定要先判断所申请的条件能否被满足,然后是申请新的线性地址空间,计算他的区间标志。这一切都完成后,就是找到插入位置。此时引用了函数:vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);这个函数和find_vm非常相似,只是所用是找到处于新区之前的线性区的位置和在红黑树中新区的位置。此时还有一个问题需要检验,就是所插入新区是否适合插入进程地址空间,此时就是是否符合进程地址空间大小的问题。

(mm->total_vm << PAGE_SHIFT)+len> current->signal->rlim[RLIMIT_AS].rlim_cur判断语句中的条件就是这个目的。也就是判断插入新的线性区后,进程地址空间的大小是否超过了进程描述符中的规定值。然后是关于页面方面的判断。当都完成没有问题后,就为这个新区申请一个新的线性区对象描述符。

vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);这个函数很熟悉,也同时表明,这就是位于slab分配器中的。以下就是对这个描述符的初始化语句。vma是vm_area_cachep结构的。

        memset(vma, 0, sizeof(*vma));
        vma->vm_mm = mm;
        vma->vm_start = addr;
        vma->vm_end = addr + len;
        vma->vm_flags = vm_flags;
        vma->vm_page_prot = protection_map[vm_flags & 0x0f];
        vma->vm_pgoff = pgoff;

然后将其插入线性区链表和红黑树中,以上就是这个分配线性区函数的大体框架和思路。

这篇关于linux2.6内核内存描述符与线性区分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/577737

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断