聊聊Linux内核中内存模型

介绍

在Linux中二进制的程序从磁盘加载到内存,运行起来后用户态是使用pid来唯一标识进程,对于内核都是以task_struct表示。二进制程序中的数据段、代码段、堆都能提现在task_struct中。每一个进程都有自己的虚拟地址空间,虚拟地址空间包含几种区域,具体参照如下

在内核中进程分配内存时候并非立马给定虚拟内存对应的物理内存,而是分配虚拟内存的使用权。只有当进程真正访问申请的虚拟内存才会分配物理页帧并建立页表映射。就如下面的代码malloc仅仅是在当前的进程的地址空间内分配虚拟内存的使用权,分配物理页帧是在memset函数访问虚拟内存的时候。

void *ptr=malloc(sizeof(int));
memset(ptr,0,sizeof(int));

进程虚拟地址空间

之前聊过task_struct用来表示内核中的进程或者线程,在task_struct中有一个进程内存空间的描述符,用来描述进程的内部虚拟空间布局。这个结构非常大,我们会从task_struct->mm_struct->vm_area_struct从上往下的顺序简单介绍下

// 内核中用来表示进程或者线程的数据结构
struct task_struct {
    // 进程的内存空间描述符
    struct mm_struct        *active_mm;
};

// 进程的虚拟内存空间描述符号
struct mm_struct {
    struct {
        // 进程是使用的所有虚拟内存的链表
        struct vm_area_struct *mmap;        /* list of VMAs */
        // 链表中的节点组成的红黑树
        struct rb_root mm_rb;
        // 当前进程最大的虚拟地址空间大小
        unsigned long task_size;    /* size of task vm space */
        // 页表的物理地址
        pgd_t * pgd;
 
        // 二进制代码的虚拟内存区域是从start_code到end_code来表示
        // 初始化区域虚拟内存用start_data和end_data来表示
        unsigned long start_code, end_code, start_data, end_data;
        // 动态变化的堆虚拟内存区域是从start_brk到brk
        unsigned long start_brk, brk, start_stack;
        // 参数列表的虚拟内存区域是从arg_start到arg_end
        // 环境变量的虚拟内促区域是从env_start到env_end
        unsigned long arg_start, arg_end, env_start, env_end;
    } __randomize_layout;
};

// 用来表示各个虚拟内存区域的结构
struct vm_area_struct {
    // 虚拟内存的起始地址
    unsigned long vm_start;     /* Our start address within vm_mm. */
    // 虚拟内存的结束地址
    unsigned long vm_end;       /* The first byte after our end address
                       within vm_mm. */

    // 进程所使用的各个虚拟内存区域通过vm_prev和vm_next链表链接起来
    struct vm_area_struct *vm_next, *vm_prev;
    // 当查找虚拟地址存在于哪个区域时链表性能显然不行,通过vm_rb构建的红黑树查找
    struct rb_node vm_rb;

 
    // 指向属于哪一个mm_struct结构用来表示从属关系
    struct mm_struct *vm_mm;    /* The address space we belong to. */

   
    // 内存区域的标记
    pgprot_t vm_page_prot;
    unsigned long vm_flags;     /* Flags, see mm.h. */


    // vma的操作函数
    const struct vm_operations_struct *vm_ops;

    // 文件映射的偏移量
    unsigned long vm_pgoff;     
    // 如果是文件映射vm_file则是表示对应的文件指针
    struct file * vm_file;      /* File we map to (can be NULL). */
    void * vm_private_data;     /* was vm_pte (shared mem) */

} __randomize_layout;

相关视频推荐

2024,彻底搞懂计算机的底层原理,linux内核源码分析教程,六大模块全面分析(内存管理、进程管理、设备驱动、网络协议栈、文件系统、中断管理及基础)icon-default.png?t=N7T8https://www.bilibili.com/video/BV1GT4y1t7Hs/

免费学习地址:Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)

需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

内存操作

这里涉及到的是文件映射和堆内存分配两种的情况

文件映射

用户态的文件映射是通过mmap系统调用进行实现,它可以绕靠文件系统的过程,利用内存指针快速访问文件数据。mmap新的系统调用对应的是内核中ksys_mmap_pgoff.

// mmap的系统调用的实现,底层是调用ksys_mmap_pgoff的函数
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
        unsigned long, prot, unsigned long, flags,
        unsigned long, fd, unsigned long, off)
{
    if (off & ~PAGE_MASK)
        return -EINVAL;

    return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}
// ksys_mmap_pgoff的具体定义如下
unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
                  unsigned long prot, unsigned long flags,
                  unsigned long fd, unsigned long pgoff)
{
    struct file *file = NULL;
    unsigned long retval;
    // 匿名文件映射,设置映射的文件
    if (!(flags & MAP_ANONYMOUS)) {
        audit_mmap_fd(fd, flags);
        file = fget(fd);
      
    // 大页方式
    } else if (flags & MAP_HUGETLB) {
        struct ucounts *ucounts = NULL;
        struct hstate *hs;

        hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
        file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
                VM_NORESERVE,
                &ucounts, HUGETLB_ANONHUGE_INODE,
                (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
    
    }
    // 最核心的映射函数实现
    retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
    return retval;
}


// 调用底层的do_mmap函数实现映射
unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
    unsigned long len, unsigned long prot,
    unsigned long flag, unsigned long pgoff)
{
   
    
    ret = do_mmap(file, addr, len, prot, flag, pgoff, &populate,
                  &uf);
    return ret;
}


// 最底层的文件映射的实现
unsigned long do_mmap(struct file *file, unsigned long addr,
            unsigned long len, unsigned long prot,
            unsigned long flags, unsigned long pgoff,
            unsigned long *populate, struct list_head *uf)
{
    struct mm_struct *mm = current->mm;
    vm_flags_t vm_flags;
    int pkey = 0;
    // 在线性区间找到未被使用并且足够大的地址空间
    addr = get_unmapped_area(file, addr, len, pgoff, flags);
  

 
    // 传入prot和flags设置vm_flags
    vm_flags = calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) |
            mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;

    // 如果是文件映射,则通过find_inode找到inode并检查文件
    if (file) {
        struct inode *inode = file_inode(file);
    }


    addr = mmap_region(file, addr, len, vm_flags, pgoff, uf)
    {
        // 检查虚拟地址空间容量限制
        if(!may_expand_vm(mm, vm_flags, len >> PAGE_SHIFT))
        {}
        // 检查是否有当前的vma有重叠如果有则进行munmap操作
        munmap_vma_range(mm, addr, len, &prev, &rb_link, &rb_parent, uf);

        // 与现有的vma进行合并
        vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
            NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);
        if (vma) {
            goto out; 
        }
        // 申请新的vm_area_struct结构
        vma = vm_area_alloc(mm);
        // 将新的vm_area_struct插入到mm_struct中的链表、红黑树以及对应文件的地址空间上的adress_space->i_mmap或者address_space->i_mmap_nolinear中
        vma_link(mm, vma, prev, rb_link, rb_parent);
    }

    return addr;
}

堆内存

在用户态申请内存和释放内存通过malloc/free库函数进行,它们的底层还是通过SYSCALL_DEFINE1(brk, unsigned long, brk)系统调用来完成。堆内存的扩大可以通过SYSCALL_DEFINE1(brk, unsigned long, brk)进行,如果需要缩小的空间则通过do_munmap实现。如果分配空间大于128KB(glibc源码中定义的MMAP_THRESHOLD),malloc使用sys_mmap2实现内存申请。不论是malloc还是calloc申请的是线性虚拟地址而非物理地址,连续的空间也是指的虚拟地址空间的连续。

// brk系统调用的实现
SYSCALL_DEFINE1(brk, unsigned long, brk)
{
    origbrk = mm->brk;
    // 检查资源的限制
    if (check_data_rlimit(rlimit(RLIMIT_DATA), brk, mm->start_brk,
                  mm->end_data, mm->start_data))
        goto out;

    // page的对齐
    newbrk = PAGE_ALIGN(brk);
    oldbrk = PAGE_ALIGN(mm->brk);
    if (oldbrk == newbrk) {
        mm->brk = brk;
        goto success;
    }

    // 如果是是释放操作则执行__do_munmap调整指针的位置
    if (brk <= mm->brk) {
        int ret;
        mm->brk = brk;
        ret = __do_munmap(mm, newbrk, oldbrk-newbrk, &uf, true);
       
        goto success;
    }

    // 对应malloc的实现,申请新的vm_area_struct、插入到mm_struct中的list和rb树中
    // do_b rk_flags可以理解是mmap简单版的实现
    if (do_brk_flags(oldbrk, newbrk-oldbrk, 0, &uf) < 0)
        goto out;
    mm->brk = brk;

success:
    populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
    if (downgraded)
        mmap_read_unlock(mm);
    else
        mmap_write_unlock(mm);
    userfaultfd_unmap_complete(mm, &uf);
    if (populate)
        mm_populate(oldbrk, newbrk - oldbrk);
    return brk;

out:
    mmap_write_unlock(mm);
    return origbrk;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/522820.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

校园抄表电表系统

校园抄表电表系统是一种专门为学校宿舍、教学楼等校园建筑设计的电能计量和管理解决方案。随着校园数字化管理水平的提升&#xff0c;传统的电表抄录方式已经无法满足现代化校园管理的需求。因此&#xff0c;校园抄表电表系统应运而生&#xff0c;它通过自动化、信息化技术&…

一文读懂模块化赛道新的头部公链Meta Earth

加密货币诞生了15年后已经形成一定的规模&#xff0c;作为互联网和金融的延伸&#xff0c;加密货币也逐渐融合了人类经济的规律周期中&#xff0c;而这规律似乎早已被中本聪写入区块链上&#xff0c;那就是比特币的4年减半机制&#xff0c;也许是对传统金融和人类社会的洞察&am…

C++从入门到精通——类对象模型

类对象模型 前言一、如何计算类对象的大小问题 二、类对象的存储方式猜测对象中包含类的各个成员代码只保存一份&#xff0c;在对象中保存存放代码的地址只保存成员变量&#xff0c;成员函数存放在公共的代码段问题总结 三、结构体内存对齐规则四、例题结构体怎么对齐&#xff…

设计模式总结-适配器模式

适配器模式 模式动机模式定义模式结构适配器模式实例与解析实例一&#xff1a;仿生机器人实例二&#xff1a;加密适配器 总结 模式动机 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。 通常情况下&#xff0c;客户端可以通过目标类的接口访问它所提供的…

【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(2)Python通讯和可视化

一 背景说明 计划通过西门子 S7-1200&#xff08;CPU 1212C-DCDCDC&#xff09;&#xff0c;进行PLC与设备间的数据监控。但 TIA Portal V15.1 的交互数据非专业人员很难一目了然&#xff0c;又不想专门购买西门子的可编程屏幕&#xff0c;所以拟采用 python-snap7 模块实现上位…

MQTT 5.0 报文解析 01:CONNECT 与 CONNACK

在 MQTT 5.0 报文介绍 中&#xff0c;我们介绍了 MQTT 报文由固定报头、可变报头和有效载荷三个部分组成&#xff0c;以及可变字节整数、属性这类 MQTT 报文中的通用概念。现在&#xff0c;我们将按照实际的用途来进一步介绍各个类型的报文的组成。首先&#xff0c;我们将专注于…

初识Java中的NIO

1.概述 Java NIO 全称java non-blocking IO &#xff0c;是指 JDK 提供的新 API。从 JDK1.4 开始&#xff0c;Java 提供了一系列改进的输入/输出新特性&#xff0c;被统称为 NIO(即 New IO)&#xff0c;是同步非阻塞的。NIO采用内存映射文件的方式来处理输入输出&#xff0c;NI…

电池UN38.3测试电池模组蓄电池检测报告出具

UN38.3运输报告规定了以下类型的电池&#xff1a; 1. 锂金属电池&#xff1a;包括锂金属氧化物电池&#xff08;如锂锰电池、锂铁电池、锂钴电池&#xff09;&#xff0c;锂-硫电池等。 2. 锂离子电池&#xff1a;包括锂聚合物电池、锂离子聚合物电池、锂离子含水电池等。 3.…

软考-系统集成项目管理中级-新一代信息技术

本章历年考题分值统计 本章重点常考知识点汇总清单(掌握部分可直接理解记忆) 本章历年考题及答案解析 32、2019 年上半年第 23 题 云计算通过网络提供可动态伸缩的廉价计算能力&#xff0c;(23)不属于云计算的特点。 A.虚拟化 B.高可扩展性 C.按需服务 D.优化本地存储 【参考…

记录一个C语言基础错误——scanf()输入!

今天犯了一个很傻的问题&#xff0c;记录一下。 Lint’Code 题目&#xff1a; 错误代码 #include <stdio.h>int function(int a, int b, int c, int x, int y) {// Write your code hereprintf("In function: %d\n", x y);x - y;return (a * (x y) * (x y…

深圳MES系统服务商

盈致MES系统是一款专业的制造执行系统&#xff0c;专注于为企业提供全面的生产管理解决方案。该系统涵盖了制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理等功能模块&#xff0c;能够帮助企业实现生产过程的数字化、智能化和精益化。 盈致MES系统具有以下特点和…

openharmony launcher 调研笔记(01)数据初始化

最近在看launcher&#xff0c;把自己调研的点做个笔记&#xff0c;持续修改更新中&#xff0c;个人笔记酌情参考。 初始化MainAbility ● common 等 包以 三方库形式 被引入使用 在每个包中的oh-package.json5 文件有配置 { "devDependencies": {}, "n…

宏的使用(C语言详解)

在写一个代码生成可执行文件的过程需要经过编译和链接&#xff0c;编译又要经过三部&#xff1a;预处理&#xff0c;编译&#xff0c;汇编。 #define定义的变量和宏就是在预处理阶段会处理的。 一个简单的宏定义&#xff1a; #include<stdio.h>; #define Max(a,b) a>…

如何利用义乌购API实现用户个性化推荐及商品详情 API 返回值说明

用户个性化推荐 利用义乌购API实现用户个性化推荐是一个涉及多个步骤的过程&#xff0c;主要包括数据收集、用户画像构建、推荐算法选择与实施以及推荐结果的展示与反馈。以下是一个大致的流程和步骤说明&#xff1a; 一、数据收集&#xff1a; 1.用户行为数据&#xff1a;收…

如何理解单片机 pwm 控制的基本原理?

单片机PWM&#xff08;脉宽调制&#xff09;控制的基本原理&#xff0c;简而言之&#xff0c;就是通过改变脉冲信号的宽度&#xff08;占空比&#xff09;来控制模拟电路。这涉及到单片机生成一系列脉冲信号&#xff0c;每个脉冲信号的高电平持续时间和整个周期的比值&#xff…

桌面便签电脑版哪个好?好用便签是哪款

在快节奏的现代生活中&#xff0c;桌面便签软件成为了我们不可或缺的助手。它们轻便、灵活&#xff0c;能够随时记录重要事项&#xff0c;提醒我们按时完成各项任务。面对市面上众多的便签软件&#xff0c;选择一款既实用又好用的便签显得尤为重要。经过深入体验&#xff0c;我…

C++ | Leetcode C++题解之第12题整数转罗马数字

题目&#xff1a; 题解&#xff1a; const string thousands[] {"", "M", "MM", "MMM"}; const string hundreds[] {"", "C", "CC", "CCC", "CD", "D", "DC&qu…

【Angular性能优化】项目8版本加载速度缓慢、白屏时间、首页渲染性能优化方案

前言 随着业务的代码一点点增加,加上Angular的项目本身就比 vue、react 的重一些,随之而来的启动速度,更改文件后编译速度,以及打包速度也会变慢,于是乎想着优化下我们的项目。 本文章主要说的是 : 打包Angular项目的一些配置,性能优化方面的方案打包后,用户进入页面…

vue vue3 手写 动态加载组件

效果展示 一、需求背景&#xff1a; # vue3 项目涉及很多图表加载、表格加载 #考虑手写一个动态加载组件 二、实现思路 通过一个加载状态变量&#xff0c;通过v-if判断&#xff0c;加载状态的变量等于哪一个&#xff0c;动态加载组件内部就显示的哪一块组件。 三、实现效果…

Coursera上托福专项课程03:TOEFL Test-Taking Strategies 学习笔记(完结)

TOEFL Preparation Specialization Specialization Certificate TOEFL Test-Taking Strategies Course Certificate 本文是学习 TOEFL Test-Taking Strategies 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 TOEFL Preparation SpecializationTOEF…