聊聊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文件&…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...