CSAPP学习笔记——虚拟内存(二)
案例研究
Intel Core i7
该处理底层的Haswell微体系结构允许64位的虚拟和物理地址空间,而现在的Core i7实现支持48位(256TB)虚拟地址空间和52位(4PB)物理地址空间,这对目前来说已经完全够用了。(Linux的虚拟内存系统中页的大小为4KB)
-
介绍了Core i7内存系统的重要部分——处理器封装
-
介绍了Core i7的地址翻译情况
-
层级结构的TLB
-
层级结构的页表(4层),每一层页表占VPN的9位,也就是有292^929个PTE
-
层次结构的高速缓存L1、L2和L3
-
其中CR3控制寄存器指向第一级页表的起始位置,CR3的值是每个进程上下文的一部分,每次上下文切换的时候,CR3的值都会被恢复
-
-
介绍了页表项PTE的格式
- 每个PTE占8个字节64位
- 其中有40位是作为指向下一级页表物理基地址(这个地方我有个疑问?在这个例子里每一级页表的每一个PTE都有40位用来下一级页表的基地址,这难道不会很浪费吗?只存一次不就好了吗?)
- 还有其他位用于权限控制和协助替换算法
Linux虚拟内存系统
-
Linux虚拟内存区域
Linux将虚拟内存组织成一些区域的集合。一个区域就是已经存在着的(已分配的——已缓存和未缓存)虚拟内存的连续片,也就是说Linux虚拟内存系统所定义的区域范围是比虚拟页大的,虚拟页是区域的基本单元。这些页是以某种方式相关联的,例如代码段、数据段、堆、共享库段和用户栈都是不同的区域。
每个存在的虚拟页面都保存在某个区域,而不属于某个区域的虚拟页是不存在的(虚拟页本就是为进程所服务),并且不能被进程引用。内核不用记录这些不存在的虚拟页,能够节省更多的资源
下图是一个Linux进程等虚拟内存
-
Linux如何组织虚拟内存
内核为每一个进程都单独维护一个task_struct,task_struct中的元素包含或者指向内核运行该进程所需的所有信息(例如PID、指向用户栈的指针%rsp,可执行目标文件的名字,程序计数器PC等)
task_struct中一个条目指向mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的字段有两个,pgd和mmap:
-
pgd指向第一级页表的基址,当内核运行这个进程时就把pgd的值放进CR3寄存器中
-
mmap指向一个vm_area_struct,每一个vm_area_struct都描述了当前虚拟地址空间的一个区域
vm_area_struct的结构如下:
- vm_start:指向这个区域的起始处
- vm_end:指向这个区域的结束处
- vm_prot:描述这个区域内所有页的读写权限
- vm_flags:描述这个区域内的页面是与其他进程共享的还是私有的
- vm_next:指向链表的下一个vm_area_struct
-
-
Linux缺页异常处理
当出现缺页异常,控制将转移到内核的缺页处理程序,处理程序随后执行如下步骤:
-
虚拟地址A时合法的吗?即地址A是否在某个vm_area_struct指向的区域内。缺页处理程序通过搜索vm_area_struct的链表,把A和每个vm_area_struct的vm_start和vm_end进行对比来得到结果。如果虚拟地址A不是合法的,就会抛出段错误
因为一个进程可以创建任意个vm_area_struct,如果通过链表来搜索会造成大量时间损耗,因此在实际中,Linux利用我们看不见的字段建立了一个树,并在这棵树上查看
-
试图进行的内存访问是否合法?换句话说进程是否有读写或者执行这个区域内页面的权限?例如,如果缺页是由于一条对代码段中的只读页面进程写操作造成的,处理程序就会抛出段错误
-
此刻,内核已经知道这个缺页是由于对合法的虚拟地址进行合法访问造成的。那么处理程序会选择一个牺牲页面,如果牺牲页面被修改了,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令
-
内存映射
我们好奇虚拟内存是如何与磁盘上的对象关联起来的。Linux上通过一个叫内存映射的过程来实现虚拟内存的初始化,内存映射有两种形式:
-
**Linux文件系统中的普通文件:**一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行文件。文件区被分成页大小(4KB)的片,每一片包含一个虚拟页面的初始内容。因为虚拟页面是按需调度的(已缓存或未缓存),所以这些虚拟页面并没有实际进入物理内存(DRAM),直到CPU第一次引用到页面,即发射一个虚拟地址,触发访问合法地址的合法访问的缺页。
一个区域的大小如果比文件区要大,那么剩下的就用0填充
-
匿名文件:一个区域也可以映射到匿名文件。匿名文件是由内核创建的,包含得全是二进制零。CPU第一次引用这种页面时,内核会在虚拟内存中找到合适的替换页面,如果该页面有修改,那么就换出去,用二进制零覆盖页面并更新页表。注意在这个过程中,磁盘和内存之间没有实际的数据传输,因此映射到匿名文件的区域中的页面也叫请求二进制零的页,通常出现在可执行文件的.bss段以及栈和堆,初始长度都是0,特点都是没有和磁盘的数据交互所以就用匿名文件映射。
再看共享对象
内存映射为我们提供了一种清晰的基址,用来控制多个进程如何共享对象
一个对象被映射到虚拟内存的一个区域,可以是共享对象也可以是私有对象,一个进程对一个共享对象的任何写操作其他进程也都会看见,并且会作用在磁盘的原始对象上。

私有对象使用的是一种写时复制的技术映射到虚拟内存中,一个私有对象开始生命周期的方式与共享对象一致,在物理内存中只保存有私有对象的一份副本,其中两个进程将一个私有对象映射到它们虚拟内存的不同区域,但是共享这个对象的同一个副本。对于每个映射私有对象的进程,相应私有区域的PTE是标记为只读的,并且vm_area_struct标记为私有的写时复制
只要进程没有试图去写它的私有区域,它们就可以继续共享物理内存中对象的一个单独副本,然而只要有一个进程试图写私有区域的某个页面,那么这个写操作就会触发一个保护故障
当故障处理程序发现是由于进程试图写私有区域中一个页面而引发的时,它会在物理内存中新建这个页面的新副本(注意只是页面的副本而不是整个对象),更新当前进程对应页面的PTE指向该新副本(其他进程的页表仍然指向旧的页面),然后将页表的权限改为可写,处理完后控制传会引发故障的指令

再看fork函数
之前的知识已经告诉我们,父进程和fork的子进程拥有的是两个互不干涉的地址空间
当fork函数被父进程调用时,内核就为子进程创建各种数据结构,并分配了唯一的PID(task_struct),然后为了给这个进程创建虚拟内存,它创建了父进程的mm_struct、vm_area_struct和页表的原样副本给子进程的地址空间(因为父进程和子进程的代码和数据都有一模一样的,区别在于调用fork函数后跳转的部分不同)。并且将两个进程的每个页面都标记为只读,每个区域结构都标记为私有的写时复制
这样fork函数在子进程中返回时(即将跳转到与父进程不同的代码段),父进程现在的虚拟内存(调用fork函数时)和子进程的虚拟内存是一样的。当这两个进程中任意一个,有写操作时,写时复制机制都会创建新页面,彼此互不影响
再看execve函数
假设在当前进程中执行了如下的execve调用
execve("a.out", NULL, NULL);
execve函数在当前进程加载并运行a.out中的程序,用a.out程序替代当前程序,步骤如下:
- 删除已存在的用户区域
- 映射私有区域:为新程序的代码、数据、bss和栈区域创建新的vm_area_struct,所有的这些区域都是私有的,写时复制的
- 映射共享区域
- 设置程序计数器:execve的最后一件事就是设置当前程序上下文中的程序计数器,使之指向新代码区域的入口点

使用mmap函数的用户级内存映射
前面提到过的,一个进程可以创建大量的虚拟内存区域,怎么创建呢?Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域
#include <unistd.h>
#include <sys/mman.h>void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
// 如果成功则返回指向映射区域的指针,如出错则为MAP_FAILED(-1)
mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start
开始的一个区域,并将文件描述符fd
指定的对象的一个连续的片映射到这个新区域。连续的片的大小为length
字节,从距文件开始处偏移量offset
字节的地方开始。
start
只是一个暗示,通常设置为NULL
prot
包含新映射的虚拟区域的访问权限位(vm_area_struct里的vm_prot)
flags
由描述被映射对象类型的位组成,如果设置了MAP_ANON标记位,那么映射的对象就是一个匿名对象,那么相应的虚拟页面就是请求二进制零的。相应的还有MAP_PRIVATE和MAP_SHARED
例如
bufp = mmap(NULL, size, PROT_READ, MAP_PRIVATE|MAP_ANON, 0, 0);
该函数让内核创建了一个size字节的,只读的,私有的,请求二进制零的虚拟内存区域,如果调用成功,bufp包含新区域的地址
mmap函数可以删除虚拟内存的区域
int mmap(void *start, size_t length);
删除从虚拟地址start开始的长度为length字节的区域
相关文章:

CSAPP学习笔记——虚拟内存(二)
案例研究 Intel Core i7 该处理底层的Haswell微体系结构允许64位的虚拟和物理地址空间,而现在的Core i7实现支持48位(256TB)虚拟地址空间和52位(4PB)物理地址空间,这对目前来说已经完全够用了。ÿ…...
面试sql
创建表 create table Student ( Sno varchar(20) primary key,Sname varchar(20) UNIQUE,Ssex varchar(2),Sbirthday date,class varchar(20) )create table Course ( Cno varchar(20) primary key,Cname varchar(20) UNIQUE,Tno varchar(20) )create table Score ( …...

Python编程自动化办公案例(2)
作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.前期代码 二.实现批量读取 1.os库 2.实现思路 (1&#…...

Vulnhub 渗透练习(七)—— FRISTILEAKS: 1.3
环境搭建 下载链接 virtualbox 打开靶机设置为 host-only,攻击机同样。 具体可点此处 信息收集 开了个 80 端口。 用的是 apache 2.2.15 ,这个版本有个解析漏洞。 目录 根据首页的图片猜测 /fristi/ 目录(不过我没想到 -_-&#x…...

阶段二10_面向对象高级_分类分包思想和案例环境搭建
一.分类思想 1.分类思想概念: 分工协作,专人干专事 2.信息管理系统分类[案例] Student 类-------------------->标准学生类,封装键盘录入的学生信息(id , name , age , birthday) StudentDao 类-----------------&…...
关于打印工具print-js的使用
https://www.jianshu.com/p/f6f09dd9f7db第一步 安装组件//安装print-js npm install print-js --save //删除print-js npm uninstall print-js //安装固定版本 npm install print-js版本号 --save // 全局安装 npm install print-js --save -g第二步 引入组件安装成功后&#…...
Doxygen使用
文章目录简介Doxygen的安装Doxygen的配置生成配置文件常用配置Doxygen注释头文件注释:函数的注释:Doxygen文档生成reference简介 Doxygen 是一个流行的用于生产代码文档的工具,关于它的介绍可以参考官网:https://www.doxygen.nl/index.html。 我使用Dox…...
MySQL数据库调优————表结构设计优化
三范式 第一范式 字段具有原子性,即数据库表的每一个字段都是不可分割的原子数据项,不能是集合、数组、记录等非原子数据项当实体中的每个属性有多个值时,必须拆分为不同的属性 第二范式 满足第一范式的基础上,要求每一行数据…...
set对象和map对象
1 Set对象 介绍: Set数据结构类似数组,但所有成员的值唯一。 Set本身为一个构造函数,用来生成 Set数据结构,使用 add方法来添加新成员。 let a new Set(); [1,2,2,1,3,4,5,4,5].forEach(x>a.add(x)); for(let k of a){ console.log(k…...
stream()流的使用
文章目录引入流流的操作中间操作终端操作流的使用谓词筛选筛选各异的元素流的切片截断流跳过元素映射流的扁平化查找和匹配归约元素求和、最大值和最小值数值流构建流由值构建流由数组创建流引入流 java api提供的一种利用声明式的方式处理数据集合的一个东西,可以…...
C++学习笔记-常量
在程序执行过程中,其值不能改变的量称为常量(Constant)。普通常量的类型是根据数据的书写形式来决定的。如 100 是整型常量,0.5 是实型常量,‘q’ 是字符型常量,“qianfeng” 是字符串常量。 常量是固定值,在程序执行期…...

JavaScript系列之实现继承的几种方式
文章の目录一、借助父构造函数继承属性1、实现方式2、优点3、缺点二、原型链继承1、实现方式2、优点3、缺点三、组合继承四、ES6继承的实现方式参考写在最后一、借助父构造函数继承属性 1、实现方式 先定义一个父构造函数(this指向为window);再定义一个子构造函数…...
java面试准备
1.自我介绍: 2.基础 : 1.集合 : java容器中分为collection 和map两大类 collection 分为list集合(有序且重复的),set集合(无序,不可重复) list集合分为arrayList集合 : 查询快,增删慢,它是基于数组结构的,对数据的增删是在数组的尾部进行添加或删除的,其效率相对于LinkedList…...

kafka-6-python单线程操作kafka
使用Python操作Kafka:KafkaProducer、KafkaConsumer Python kafka-python API的帮助文档 1 kafka tools连接 (1)/usr/local/kafka_2.13-3.4.0/config/server.properties listeners PLAINTEXT://myubuntu:9092 advertised.listenersPLAINTEXT://192.168.1.8:2909…...

【Spring教程】1.Spring概述
1、概述 1.1、Spring是什么? Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测…...

设计模式-代理模式
控制和管理访问 玩过扮白脸,扮黑脸的游戏吗?你是一个白脸,提供很好且很友善的服务,但是你不希望每个人都叫你做事,所以找了黑脸控制对你的访问。这就是代理要做的:控制和管理对象。 监视器编码 需求&…...
DPDK — MALLOC(librte_malloc,Memory Manager,内存管理组件)
目录 文章目录 目录MALLOC(librte_malloc,Memory Manager,内存管理组件)rte_malloc() 接口malloc_heap 结构体malloc_elem 结构体内存初始化流程内存申请流程内存释放流程MALLOC(librte_malloc,Memory Manager,内存管理组件) MALLOC 库基于 hugetlbfs 内核文件系统来实…...

【Java开发】Spring 12 :Spring IOC控制反转和依赖注入(解决单接口多实现类调用)
IOC 是 Inversion of Control 的简写,译为“控制反转”,Spring 通过 IOC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IOC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建…...

【C++学习】基础语法(三)
众所周知C语言是面向过程的编程语言,关注的是过程;解决问题前,需要分析求解的步骤,然后编辑函数逐步解决问题。C是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,不同对象间交互解决问…...

k8s自动化安装脚本(kubeadm-1.23.7)
文章目录介绍软件架构版本介绍更新内容2023-02-192023-02-152023-02-142023-02-102022-10-202022-08-06准备部署包操作步骤环境准备结构备注解压部署包修改host文件脚本使用方式初始化环境验证ansible配置安装k8s集群登录master的节点添加node节点master节点状态检查组件安装安…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...