聊聊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到brkunsigned long start_brk, brk, start_stack;// 参数列表的虚拟内存区域是从arg_start到arg_end// 环境变量的虚拟内促区域是从env_start到env_endunsigned 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 addresswithin 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内核源码分析教程,六大模块全面分析(内存管理、进程管理、设备驱动、网络协议栈、文件系统、中断管理及基础)
https://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_flagsvm_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);elsemmap_write_unlock(mm);userfaultfd_unmap_complete(mm, &uf);if (populate)mm_populate(oldbrk, newbrk - oldbrk);return brk;out:mmap_write_unlock(mm);return origbrk;
}
相关文章:
聊聊Linux内核中内存模型
介绍 在Linux中二进制的程序从磁盘加载到内存,运行起来后用户态是使用pid来唯一标识进程,对于内核都是以task_struct表示。二进制程序中的数据段、代码段、堆都能提现在task_struct中。每一个进程都有自己的虚拟地址空间,虚拟地址空间包含几…...
docker自动化部署示例
前提 安装docker 、 docker-cpmpose、git、打包环境(如meaven、jdk、node等) 原理 git Dockerfile docker-compose 获取源码(代码仓库)获取可运行程序的镜像(docker)将打包后的程序放入镜像内…...
Redis精品案例解析:Redis实现持久化主要有两种方式
Redis实现持久化主要有两种方式:RDB(Redis DataBase)和AOF(Append Only File)。这两种方式各有优缺点,适用于不同的使用场景。 1. RDB持久化 RDB持久化是通过创建一个二进制的dump文件来保存当前Redis数据…...
Python | Leetcode Python题解之第14题最长公共前缀
题目: 题解: class Solution:def longestCommonPrefix(self, strs: List[str]) -> str:def isCommonPrefix(length):str0, count strs[0][:length], len(strs)return all(strs[i][:length] str0 for i in range(1, count))if not strs:return &quo…...
烧坏两块单片机,不知道原因?
没有看你的原理图,以下是造成烧毁芯片的几个环节: 1. 最大的可能性是你的单片机电机控制输出与电机驱动电路没有隔离。 我的经验,使用STM32控制电机,无论是直流电机脉宽调制,还是步进电机控制,控制电路与…...
SV学习笔记(八)
文章目录 SV入门练习基本数据类型字符串类型数组类型接口的定义与例化类的封装类的继承package的使用随机约束线程的同步线程的控制虚方法方法(任务与函数)SV用于设计 参考资料 SV入门练习 基本数据类型 有符号无符号、四状态双状态、枚举类型、结构体…...
Java反射常用方法
反射 作用: 对于任意一个对象,把对象所有的字段名和值,保存到文件中去利用反射动态的创造对象和运行方法 1. 获取字节码文件对象 方法描述Class.forName(String)通过类的全限定名字符串获取字节码文件对象。类字面量直接使用类的字面量获…...
go语言实现无头单向链表
什么是无头单向链表 无头单向链表是一种线性数据结构,它的每个元素都是一个节点,每个节点都有一个指向下一个节点的指针。"无头"意味着这个链表没有一个特殊的头节点,链表的第一个节点就是链表的头。 优点: 动态大小&…...
SpringBoot快速入门笔记(5)
文章目录 一、elemetnUI1、main.js2、App.vue3、fontAwesome 一、elemetnUI 开源前端框架,安装 npm i element-ui -S 建议查看官方文档 Element组件,这里是Vue2搭配elementUI,如果是vue3就搭配elementPlus,这里初学就以Vue2为例子…...
solidity(3)
地址类型 pragma solidity ^0.8.0;contract AddressExample {// 地址address public _address 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;address payable public _address1 payable(_address); // payable address,可以转账、查余额// 地址类型的成员uint256…...
笔记 | 编译原理L1
重点关注过程式程序设计语言编译程序的构造原理和技术 1 程序设计语言 1.1 依据不同范型 过程式(Procedural programming languages–imperative)函数式(Functional programming languages–declarative)逻辑式(Logical programming languages–declarative)对象式(Object-or…...
k8s存储卷 PV与PVC 理论学习
介绍 存储的管理是一个与计算实例的管理完全不同的问题。PersistentVolume 子系统为用户和管理员提供了一组 API,将存储如何制备的细节从其如何被使用中抽象出来。为了实现这点,我们引入了两个新的 API 资源:PersistentVolume 和 Persistent…...
【WPF应用32】WPF中的DataGrid控件详解与示例
在WPF(Windows Presentation Foundation)开发中,DataGrid控件是一个强大的数据绑定工具,它以表格的形式展示数据,并支持复杂的编辑、排序、过滤和分组等操作。在本文中,我们将详细介绍DataGrid控件的功能、…...
numpy,matplotilib学习(菜鸟教程)
所有内容均来自于: NumPy 教程 | 菜鸟教程 Matplotlib 教程 | 菜鸟教程 numpy模块 numpy.nditer NumPy 迭代器对象 numpy.nditer 提供了一种灵活访问一个或者多个数组元素的方式。 for x in np.nditer(a, orderF):Fortran order,即是列序优先&#x…...
Web API(四)之日期对象节点操作js插件重绘和回流
Web API(四)之日期对象&节点操作&js插件&重绘和回流 日期对象实例化方法时间戳DOM 节点插入节点删除节点查找节点父子关系兄弟关系M端事件js插件重绘和回流进一步学习 DOM 相关知识,实现可交互的网页特效 能够插入、删除和替换元素节点能够依据元素节点关系查找…...
27.WEB渗透测试-数据传输与加解密(1)
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:26.WEB渗透测试-BurpSuite(五) BP抓包网站网址:http:…...
山寨windows
我的目标是能够运行windows 下的大部分PE格式的程序,这一点通过实验已经证明完全是可行的。 PE格式主要有exe dll sys等文件,这三个文件可以用相同的函数解析, 主要有以下段组成, 1、文件头,包含DOS文件头、PE文件头…...
unity工程输出的log在哪里?
在编辑器里进行活动输出的log位置: C:\Users\username\AppData\Local\Unity\Editor\Editor.log ------------------------------------ 已经打包完成,形成的exe运行后的log位置: C:\Users\xxx用户\AppData\LocalLow\xx公司\xx项目...
【力扣】7. 整数反转
7. 整数反转 题目描述 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。 假设环境不允许存储 64 位整数(有符号或无符号)。 …...
Android Apk签名算法使用SHA256
Android apk签名算法使用SHA256 本文不介绍复杂的签名过程,说一下Android签名算法使用SHA256。 但是SHA1不是相对安全签名算法,SHA256更加安全一些。 一般大公司才会有这种细致的安全要求。 如何查看apk签名是否是SHA1还是SHA256 1、拿到apk文件&…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
