热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux内存管理一(内存原理与分析)

创作人QQ:851301776,邮箱:lfr890207163.com欢迎大家一起技术交流,本博客主要是自己学习的心得体会,

创作人QQ:851301776,邮箱:lfr890207@163.com
        欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点!

个人座右铭:
         1.没有横空出世,只要厚积一定发。
         2.你可以学历不高,你可以不上学,但你不能不学习
 


一、UMA/NUMA内存结构

共享存储多处理机有两种模型:

(1)均匀存储器存取(Uniform-Memory-Access,简称UMA)模型

(2)非均匀存储器存储(Nonuniform-Memory-Access,简称NUMA)模型

 比较典型NUMA服务器:SUN15K、IBMp690等


二、mm_struct结构体

        Linux内核内存管理子系统架构如下图所示,分为用户空间、内核空间和硬件层3个层面:

 个人认为:这个图能很好的梳理,Linux内存管理都是依照此图来梳理会很快梳理清楚,内存管理这张图的子模块。

用户空间:malloc/free 

ptmalloc(glibc)、jemalloc(FreeBSD)、sys_munmap等等


1.用户空间

        应用程序使用malloc()申请内存,使用free()释放内存,malloc()/free()是glibc库的内存分配器ptmalloc提供的接口,ptmalloc使用系统调用brk/mmap向内核以页为单位申请内存,然后划分成小内存块分配给用户应用程序。用户空间的内存分配器出glibc库的ptmalloc,google的tcmalloc,FreeBSD的jemalloc。


2.内核空间

        内核空间的基本功能:虚拟内存管理负责从进程的虚拟地址空间分配虚拟页,sys_brk用来扩大或收缩堆,sys_mmap用来在内存映射区域分配虚拟页,sys_munmap用来释放虚拟页。

        页分配器负责分配物理页,当前使用的页分配器伙伴分配器。内核空间提供把页划分成小内存块分配的块分配器,提供非配内存接口kmalloc()和释放内存接口kfree().块分配器:SLAB/SLUB/SLOB。

        内核空间的扩展功能:不连续页分配器提供了分配内存的接口vmalloc和释放内存接口vfree,在内存碎片化时,申请连续物理页的成功率很低,可申请不连续的物理页,映射到连续的虚拟页,及虚拟地址连续物理地址不连续。

        连续内存分配器(contiguous memory allocator, CMA)用来给驱动程序预留一段连续的内存,当驱动程序不用的时候,可以给进程使用;当驱动程序需要使用的时候,把进程占用的内存通过回收或迁移的方式让出来,给驱动程序使用。


3.硬件层面

     处理器包含一个成为内存处理单元(Memory Management Unit,MMU)的部件,负责把虚拟地址转换成物理地址。内存管理单元包含一个成为页表缓存(Translationg Loockasde Buffer, TLB)的部件,保存最近使用的页表映射,避免每次把虚拟地址转物理地址都需要查询内存中的页表。


4.虚拟地址空间布局


        (1)虚拟地址空间划分

        


        以ARM64处理器为例:虚拟地址 的最大宽度是48位。内核虚拟地址在64位地址空间顶部,高16位全是1,范围是[0xFFFF 0000 0000 0000,0xFFFF FFFF FFFF FFFF] 。用户虚拟地址 在64位地址 空间的底部,高16位全是0,范围是[0x0000 0000 0000 0000,0x0000 FFFF FFFF FFFF]。

        在编译ARM64架构的Linux内核时,可以选择虚拟地址宽度:

        a.选择页长度4KB,默认虚拟地址宽度为39位;

        b.选择页长度16KB,默认虚拟地址宽度为47位;

        c.选择页长度64KB,默认虚拟地址宽度为42位;

        d.选择48位虚拟地址。        

        在ARM64架构linux内核中,内核虚拟地址用户虚拟地址宽度相同。所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址 空间,同一个线程组的用户线 程共享用户虚拟地址空间,内核线程没有用户虚拟地址 空间。


(2)用户虚拟地址空间布局

         进程的用户虚拟地址空间的起始地址是0,长度是TASK_SIZE,由每种处理器架构

定义自己的宏TASK_SIZE。ARM64架构定义宏TASK_SIZE如下所示:

​    a.32位用户空间程序:TASK_SIZE的值是TASK_SIZE_32,即0x10000000,等于4GB。

​    b. 64位用户空间程序:TASK_SIZE的值是TASK_SIZE_64,即2的VA_BITS次方字节,VA_BITS是编译内核时选择的虚拟地址位数。

 Linux内核使用内存描述符mm_struct描述进程的用户虚拟地址空间,主要核心成员如下:

// 内存描述符结构体类型(今后要用的主要成员)

struct mm_struct {

        struct vm_area_struct mmap; / 虚拟内存区域链表 */

        struct rb_root mm_rb; /* 虚拟内存区域红黑树*/

        u32 vmacache_seqnum; /* per-thread vmacache */#ifdef CONFIG_MMU

        unsigned long (*get_unmapped_area) (struct file *filp,

        unsigned long addr, unsigned long len,

        unsigned long pgoff, unsigned long flags); // 在内存映射区域找到一个没有映射的区域

       #endif

        unsigned long mmap_base; /* 内存映射区域的起始地址 */

        unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */

 #ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES

        /* Base adresses for compatible mmap() */

        unsigned long mmap_compat_base;

        unsigned long mmap_compat_legacy_base;

  #endif

        unsigned long task_size; /* 用户虚拟地址空间的长度 */

        unsigned long highest_vm_end; /* highest vma end address */

        pgd_t * pgd; // 指向页全局目录,即第一级页表

        /*@mm_users: The number of users including userspace.*

* Use mmget()/mmget_not_zero()/mmput() to modify. When this drops

* to 0 (i.e. when the task exits and there are no other temporary

* reference holders), we also release a reference on @mm_count

* (which may then free the &struct mm_struct if @mm_count also

* drops to 0).*/

        atomic_t mm_users; // 共享同一个用户虚拟地址空间的进程数量,也就是线程组包含的进程的数量

/*** @mm_count: The number of references to &struct mm_struct* (@mm_users count as 1).

*

* Use mmgrab()/mmdrop() to modify. When this drops to 0, the

* &struct mm_struct is freed.

*/

        atomic_t mm_count; // 内存描述符的引用计数

        atomic_long_t nr_ptes; /* PTE page table pages */

#if CONFIG_PGTABLE_LEVELS > 2

        atomic_long_t nr_pmds; /* PMD page table pages */

#endif

        int map_count; /* number of VMAs */

        spinlock_t page_table_lock; /* Protects page tables and some counters */

        struct rw_semaphore mmap_sem;

        struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung * together off init_mm.mmlist, and are protected

* by mmlist_lock*/

        unsigned long hiwater_rss; /* High-watermark of RSS usage */

        unsigned long hiwater_vm; /* High-water virtual memory usage */

        unsigned long total_vm; /* Total pages mapped */

        unsigned long locked_vm; /* Pages that have PG_mlocked set */

        unsigned long pinned_vm; /* Refcount permanently increased */

        unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */

        unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */

        unsigned long stack_vm; /* VM_STACK */

        unsigned long def_flags;

        // 代码段/数据段起始地址和结束地址

        unsigned long start_code, end_code, start_data, end_data;

        // 堆的起始地址和结束地址 栈的起始地址

        unsigned long start_brk, brk, start_stack;

        // 参数字符串起始地址和结束地址 环境变量的起始地址和结束地址

        unsigned long arg_start, arg_end, env_start, env_end;

        unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

/*

* Special counters, in some configurations protected by the

* page_table_lock, in other configurations by being atomic.

*/

        struct mm_rss_stat rss_stat;

        struct linux_binfmt *binfmt;

        cpumask_var_t cpu_vm_mask_var;

/* Architecture-specific MM context */

        mm_context_t context; // 处理器架构特定的内存管理上下文

        unsigned long flags; /* Must use atomic bitops to access the bits */

        struct core_state core_state; / coredumping support */

#ifdef CONFIG_AIO

        spinlock_t ioctx_lock;

        struct kioctx_table __rcu *ioctx_table;

#endif

#ifdef CONFIG_MEMCG

/*

* "owner" points to a task that is regarded as the canonical

* user/owner of this mm. All of the following must be true in

* order for it to be changed:

*

* current == mm->owner * current->mm != mm

* new_owner->mm == mm

* new_owner->alloc_lock is held

*/

        struct task_struct __rcu *owner;

#endif

        struct user_namespace *user_ns;

/* store ref to file /proc//exe symlink points to */

struct file __rcu *exe_file;

#ifdef CONFIG_MMU_NOTIFIER

        struct mmu_notifier_mm *mmu_notifier_mm;

#endif

#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS

        pgtable_t pmd_huge_pte; /* protected by page_table_lock */

#endif

#ifdef CONFIG_CPUMASK_OFFSTACK

        struct cpumask cpumask_allocation;

#endif

#ifdef CONFIG_NUMA_BALANCING

/*

* numa_next_scan is the next time that the PTEs will be marked

* pte_numa. NUMA hinting faults will gather statistics and migrate

* pages to new nodes if necessary.

*/

        unsigned long numa_next_scan;

/* Restart point for scanning and setting pte_numa */

        unsigned long numa_scan_offset;

/* numa_scan_seq prevents two threads setting pte_numa */

        int numa_scan_seq;

#endif

#if defined(CONFIG_NUMA_BALANCING) || defined(CONFIG_COMPACTION)

/*

* An operation with batched TLB flushing is going on. Anything that

* can move process memory needs to flush the TLB when moving a

* PROT_NONE or PROT_NUMA mapped page.

*/

        bool tlb_flush_pending;

#endif

        struct uprobes_state uprobes_state;

#ifdef CONFIG_HUGETLB_PAGE

        atomic_long_t hugetlb_usage;#endif

        struct work_struct async_put_work;

};


(3)进程的进程描述符和内存描述符关系如下图 

         struct mm_struct *mm; // 进程的mm指向一个内存描述符,内核线程没有用户虚拟地址空间,所以mm是空指针。

        struct mm_struct *active_mm; // 进程的active_mm和mm总是指向一个内存描述符,内核线程的active_mm在没有运行时是空指针,在运行时指向上一个进程借用的内存描述符。


 (4)内核地址空间布局

        ARM64处理器架构的内核地址空间如下:


 三、TLB工作原理

         处理器包含一个成为内存处理单元(Memory Management Unit,MMU)的部件,负责把虚拟地址转换成物理地址。为了改进虚拟地址到物理地址的转换速度,避免每次都需查找内存中的页表,处理器厂商在内存管理单元里面增加一个页表缓存(Translationg Loockasde Buffer, TLB)高速缓存,TLB直接为转换后备缓冲区,意为页表缓存。


1.TLB表项格式

        不同处理器架构的TLB表项的格式不同,ARM64处理器的每条TLB表项不仅包含虚拟地址和物理地址,也包含属性:内存类型、缓存策略、访问权限、地址空间标示符(ASID)和虚拟机标识符(VMID)。


2.TLB管理

        如果内核修改了可能缓存在TLB里面的页表项,那么内核必须负责使旧的TLB表项失效,内核定义每种处理器架构必须实现的函数如下:

 

// 使所有TLB表项失效

static inline void flush_tlb_all(void)

{

        dsb(ishst);

        __tlbi(vmalle1is);

        dsb(ish);

        isb();

}

// 使指定用户地址空间的所有TLB表项失效,参数mm是进程的内存描述符

static inline void flush_tlb_mm(struct mm_struct *mm)

{

        unsigned long asid &#61; ASID(mm) <<48;

        dsb(ishst);

        __tlbi(aside1is, asid);

        dsb(ish);

}

/* 使指定用户地址空间的某个范围的TLB表项失效&#xff0c;参数vma是虚拟内存区域&#xff0c;start起始地址&#xff0c;end结束地址 */

static inline void flush_tlb_range(struct vm_area_struct *vma,

unsigned long start, unsigned long end)

{

        __flush_tlb_range(vma, start, end, false);

}

// 使指定用户地址空间里面的指定虚拟页的TLB表项失效&#xff0c;

// 参数vma是虚拟内存区域&#xff0c;uaddr是一个虚拟页中的任意虚拟地址

static inline void flush_tlb_page(struct vm_area_struct *vma,

unsigned long uaddr)

{

        unsigned long addr &#61; uaddr >> 12 | (ASID(vma->vm_mm) <<48);

        dsb(ishst);

        __tlbi(vale1is, addr);

        dsb(ish);

}

// 使内核的某个虚拟地址范围的TLB表项失效&#xff0c;参数start是起始地址&#xff0c;end结束地址

static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end)

{

        unsigned long addr;

        if ((end - start) > MAX_TLB_RANGE) {

                flush_tlb_all(); return;

        }

        start >>&#61; 12;

        end >>&#61; 12;

        dsb(ishst);

        for (addr &#61; start; addr

                __tlbi(vaae1is, addr);

         dsb(ish);

        isb();

}


3.ARM64架构TLB失效指令&#xff08;TLB[IS]{,} &#xff09;

type:

        ALL&#xff08;所有表项&#xff09;

       VMALL&#xff08;当前虚拟机的阶段1的所有表项&#xff0c;即表项的VMID是当前虚拟机的VMID&#xff09;。虚拟机里面运行的客户操作系统的虚拟地址 转换成物理地址分两个阶段&#xff1a;1把虚拟地址转换成中间物理地址&#xff1b;2把中间物理地址 转换成物理地址。

level&#xff08;指定异常级别&#xff09;&#xff1a;

        E1:异常级别1

        E2:异常级别2

        E3:异常级别3

IS表示内存共享&#xff08;inner Shareable&#xff09;&#xff0c;多个核共享。如果不使用字段IS&#xff0c;表示非共享&#xff0c;只被一个核使用。在SMP系统中&#xff0c;如果指令TLBI不携带字段IS&#xff0c;仅仅使当前核的TLB表项失效&#xff1b;如果指令TLBI携带字段IS&#xff0c;表示使所有核的TLB表项失效。

选项Xt是X0-X31中的任何一个寄存器。

flush_tlb_all用来使所有核的所有TLB失效&#xff0c;内核代码如下&#xff1a;

 dsb ishst:确保屏障前面的存储指令执行完毕&#xff0c;dsb是数据同步屏障&#xff0c;ishst中ish表示共享域是内部共享&#xff0c;st表示存储 &#xff0c;ishst表示数据同步屏障指令对所有核的存储指令起作用。

tlbi vmalle1is:使用所有核上匹配当前VMID、阶段1和异常级别1的所有TLB表项失效。

dsb ish:确保当前的TLB失效指令执行完毕&#xff0c;ish表示数据同步屏障指令对所有核起作用。

isb:isb是指令同步屏障&#xff0c;这条指令冲刷处理器流水线&#xff0c;重新读取屏障指令后面的所有指令。


4.地址空间表示符

      为了减少在进程切换时清空页表缓存的需要&#xff0c;ARM64处理器的页表缓存使用非全局位区分内核和进程的页表项&#xff0c;使用地址空间标识符&#xff08;Address SpaceIdentifier&#xff0c;ASID&#xff09;区分不同进程的页表项。


5.虚拟地址表示符

        虚拟机里面运行的客户OS的虚拟地址转换成物理地址 分为两个阶段&#xff1a;

        a.把虚拟地址转换成中间物理地址&#xff08;由客户操作系统的内核控制&#xff0c;和非虚拟化的转换过程相同&#xff09;&#xff1b;

        b.把中间物理地址转换成物理地址&#xff08;由虚拟机监控器控制&#xff0c;虚拟机监控器为每个虚拟机维护一个转换表&#xff0c;分配一个虚拟机标识符VMID(Virutal machineidentifier)&#xff09;&#xff1b;

        每个虚拟机有独立的ASID空间&#xff0c;页表缓存使用虚拟标识 符区别不同虚拟机转换表项&#xff0c;可以避免每次虚拟机切换都要清空页表缓存&#xff0c;只需要在虚拟机标识符回绕时把处理器的页表缓存清空。


四、页表

        层次化的页表用于支持对大地址空间的快速、高效的管理。页表用于建立用户进程的虚拟地址空间和系统物理内存&#xff08;内存、页帧&#xff09;之间的关联。页表用来把虚拟页映射到物理页&#xff0c;并且存放页的保护位&#xff0c;即访问权限。

        Linux内核把页表分为4级&#xff1a;PGD、PUD、PMD、PT。

        PGD&#xff08;Page Global Directory&#xff09;-->页全局目录

        PUD&#xff08;Page Upper Directory&#xff09;-->页上层目录

        PMD&#xff08;Page Middle Directory&#xff09;-->页中间目录

        PT&#xff08;Page Table&#xff09;-->直接页表

4.11以后版本把页表扩展到五级&#xff0c;在页全局目录和页上层目录之间增加了页四级目录&#xff08;Page 4th Directory&#xff0c;P4D&#xff09;

        选择四级页表&#xff1a;页全局目录、页上层目录、页中间目录、直接页表&#xff1b;

        选择三级页表&#xff1a;页全局目录、页中间目录、直接页表&#xff1b;

        选择二级页表&#xff1a;页全局目录、直接页表&#xff1b;

处理器架构怎么选择多少级&#xff1f;在内核配置宏CONFIG_PGTABLE_LEVELS配置页表级数&#xff0c;案例分析五级页表结构如下&#xff1a;

 

   


推荐阅读
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 本文介绍了利用ARMA模型对平稳非白噪声序列进行建模的步骤及代码实现。首先对观察值序列进行样本自相关系数和样本偏自相关系数的计算,然后根据这些系数的性质选择适当的ARMA模型进行拟合,并估计模型中的位置参数。接着进行模型的有效性检验,如果不通过则重新选择模型再拟合,如果通过则进行模型优化。最后利用拟合模型预测序列的未来走势。文章还介绍了绘制时序图、平稳性检验、白噪声检验、确定ARMA阶数和预测未来走势的代码实现。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • Harmony 与 Game Space 达成合作,在 Shard1 上扩展 Web3 游戏
    旧金山20 ... [详细]
author-avatar
青大柠的小号_247
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有