[linux][内存] 实例观察 linux 内存懒加载 和 写时拷贝

1 内存懒加载

linux 中写应用程序的时候,使用 malloc() 申请的内存,比如使用  malloc() 申请了 1MB 的内存,系统是立即分配了内存吗 ?

不是立即分配,而是懒加载。linux 中用户态的内存是懒加载的,不是申请之后就立即分配,而是在第一次访问的时候才会分配。

懒加载的优点:

(1)避免内存资源浪费,如果应用申请了内存但是一直没有使用,如果内存是立即分配的话就会导致很多内存资源浪费。懒加载类似于单例设计模式中的懒汉式。

(2)减少初始化开销,提升应用启动速度。在进程启动的时候,不需要立即给所有的虚拟内存分配物理内存,这样可以减少初始化开销。

懒加载缺点:

如果应用访问内存的时候,内存有已经加载的,有没加载的,那么两种情况下访问内存所消耗的时间就是不确定的。懒加载影响程序运行的确定性。

2 /proc/self/pagemap

通过 /proc/self/pagemap 可以将虚拟地址转化为物理地址。这个文件只能进程本身才有权限访问。关于 /proc/self/pagemap 的介绍在如下文件中。

Documentation/admin-guide/mm/pagemap.rst

从介绍中可以看出来,文件中的每一项是一个 8 字节的数据。bit63 用来表示虚拟内存有没有分配物理内存,bit 0-54 用来表示物理内存页号。

 * ``/proc/pid/pagemap``.  This file lets a userspace process find out which

   physical frame each virtual page is mapped to.  It contains one 64-bit

   value for each virtual page, containing the following data (from

   ``fs/proc/task_mmu.c``, above pagemap_read):

    * Bits 0-54  page frame number (PFN) if present

    * Bits 0-4   swap type if swapped

    * Bits 5-54  swap offset if swapped

    * Bit  55    pte is soft-dirty (see

      :ref:`Documentation/admin-guide/mm/soft-dirty.rst <soft_dirty>`)

    * Bit  56    page exclusively mapped (since 4.2)

    * Bits 57-60 zero

    * Bit  61    page is file-page or shared-anon (since 3.5)

    * Bit  62    page swapped

    * Bit  63    page present

如下代码,可以获取虚拟地址对应的物理地址。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

unsigned long GetPhysicalAddrOfVirtual(unsigned long virtual_addr) {
    int page_size = getpagesize(); // 页大小,一般是 4KB

    unsigned long virtual_page_index = virtual_addr / page_size; // 虚拟地址页编号
    unsigned long page_offset = virtual_addr % page_size; // 虚拟地址页内偏移
    unsigned long virtual_offset = virtual_page_index * sizeof(uint64_t); // 虚拟地址在 pagemap 中对应的表项
    uint64_t entry = 0;

    int fd = open("/proc/self/pagemap", O_RDONLY); // 打开文件
    if (fd < 0) {
        perror("open /proc/self/pagemap failed: ");
        return 0;
    }

    if (lseek(fd, virtual_offset, SEEK_SET) < 0) { // 定位到虚拟地址对应的页表项
        perror("seek error: ");
        return 0;
    }

    if (read(fd, &entry, sizeof(uint64_t)) != sizeof(uint64_t)) {
        perror("read entry error: ");
        return 0;
    }

    if ((((uint64_t)1 << 63) & entry) == 0){ // 使用 bit 63 来判断物理页是否存在
        printf("page is not present\n");
        return 0;
    }

    uint64_t phy_page_index = (((uint64_t)1 << 55) - 1) & entry; // 获取物理页编号
    unsigned long physical_addr = (phy_page_index * page_size) + page_offset; // 获取物理地址
    return physical_addr;
}

int main() {
    char *p = (char *)malloc(4096);
    p[0] = 1;
    p[2000] = 1;
    printf("virtual addr = %p, physical addr = %p\n", p, (void *)GetPhysicalAddrOfVirtual((unsigned long)(void *)p));
    return 0;
}

3 内存懒加载代码

 如下是示例代码,代码中的变量有两个,一个是申请内存的方式,包括 malloc(),mmap() 匿名映射,mmap() 基于 fd 映射,这 3 中申请内存的方式;一个是内存访问的方式,一个是读,一个是写。

从实验结果可以得出如下两点:

(1)malloc,mmap 匿名映射,mmap fd 映射,这 3 种方式申请的内存都是懒加载方式,因为在访问之前获取物理是否存在,是不存在的。

(2)内存读和写两种操作都会使得给虚拟内存分配物理页,因为内存访问之后获取物理页是否存在,是存在的。

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>

int PhysicalPageExist(unsigned long virtual_addr)
{
    int page_size = getpagesize();

    unsigned long virtual_page_index = virtual_addr / page_size;
    unsigned long page_offset = virtual_addr % page_size;
    unsigned long virtual_offset = virtual_page_index * sizeof(uint64_t);
    uint64_t entry = 0;

    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open /proc/self/pagemap failed: ");
        return 0;
    }

    if (lseek(fd, virtual_offset, SEEK_SET) < 0) {
        perror("seek error: ");
        return 0;
    }

    if (read(fd, &entry, sizeof(uint64_t)) != sizeof(uint64_t)) {
        perror("read entry error: ");
        return 0;
    }

    if ((((uint64_t)1 << 63) & entry) == 0){
        printf("page is not present\n");
        return 0;
    }
    return 1;
}

char *MmapFd() {
    const char *file_name = "mfile";
    int fd = open(file_name, O_RDWR | O_CREAT);
    if (fd == -1) { perror("open");
        return NULL;
    }

    ftruncate(fd, 1024 * 1024);
    void *p = mmap(NULL, 1024 * 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return NULL;;
    }
    close(fd);

    return (char *)p;
}

char *MmapAnon() {
    size_t size = 1024 * 1024;
    void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    if (p == MAP_FAILED) {
        perror("mmap");
        return NULL;
    }
    return (char *)p;
}

char *Malloc() {
  return (char *)malloc(1024 * 1024);
}

int main() {
    // char *p = Malloc();
    // char *p = MmapAnon();
    char *p = MmapFd();
    if (p == NULL) {
      printf("malloc memory failed");
      return 0;
    }
    for (int i = 0; i < 256; i++) {
      printf("before write, memory %p loaded %d\n", p + i * 4096, PhysicalPageExist((unsigned long)(void *)(p + i * 4096)));
    }

    for (int i = 0; i < 256; i++) {
      // p[i * 4096] = 100;
      printf("p[%d * 4096] = %d\n", i, p[i * 4096]);
    }

    for (int i = 0; i < 256; i++) {
      printf("after write, memory %p loaded %d\n", p + i * 4096, PhysicalPageExist((unsigned long)(void *)(p + i * 4096)));
    }

    return 0;
}

4 写时拷贝代码

写时拷贝发生在 fork() 的时候,fork() 创建的子进程和父进程共享内存资源,当子进程写的时候,才会给子进程分配新的内存。

如下是写时拷贝的验证代码,从代码运行结果,可以得出如下三点:

(1)fork() 之后,内存写之前,子进程和父进程的内存是共享的。写之前,在父子进程中分别打印出 g_data 的物理地址是相同的,可以证明这点。

(2)父进程写的话,父进程的内存是新分配的,原来的内存给子进程用;子进程写的话,子进程的内存是新分配的,原来的内存给父进程使用。并不是只有子进程写的时候,才会分配内存。谁先写,就给谁先分配一个。

(3)写时拷贝,只有写的时候才会分配新的内存,读的时候不会分配新内存。这点和上节说的内存懒加载的规律是不一样的。

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

unsigned long GetPhysicalAddrOfVirtual(unsigned long virtual_addr) {
    int page_size = getpagesize(); // 页大小,一般是 4KB

    unsigned long virtual_page_index = virtual_addr / page_size; // 虚拟地址页编号
    unsigned long page_offset = virtual_addr % page_size; // 虚拟地址页内偏移
    unsigned long virtual_offset = virtual_page_index * sizeof(uint64_t); // 虚拟地址在 pagemap 中对应的表项
    uint64_t entry = 0;

    int fd = open("/proc/self/pagemap", O_RDONLY); // 打开文件
    if (fd < 0) {
        perror("open /proc/self/pagemap failed: ");
        return 0;
    }

    if (lseek(fd, virtual_offset, SEEK_SET) < 0) { // 定位到虚拟地址对应的页表项
        perror("seek error: ");
        return 0;
    }

    if (read(fd, &entry, sizeof(uint64_t)) != sizeof(uint64_t)) {
        perror("read entry error: ");
        return 0;
    }

    if ((((uint64_t)1 << 63) & entry) == 0){ // 使用 bit 63 来判断物理页是否存在
        printf("page is not present\n");
        return 0;
    }

    uint64_t phy_page_index = (((uint64_t)1 << 55) - 1) & entry; // 获取物理页编号
    unsigned long physical_addr = (phy_page_index * page_size) + page_offset; // 获取物理地址
    return physical_addr;
}

int g_data = 10;
int main() {
   printf("pid = %d, g_data = %d, g_data vaddr = %p, g_data paddr = %p\n",
                      getpid(), g_data, &g_data, GetPhysicalAddrOfVirtual(&g_data));

    pid_t pid = fork();
    if (pid == 0) {
      printf("1, child process pid = %d, g_data = %d, g_data vaddr = %p, g_data paddr = %p\n",
                      getpid(), g_data, &g_data, GetPhysicalAddrOfVirtual(&g_data));
      // 子进程修改,父进程 sleep 2s 之后再读取
      // sleep(1);
      // g_data = 20;

      // 父进程修改,子进程 sleep 2s 之后再读取
      sleep(2);
      printf("2, child process pid = %d, g_data = %d, g_data vaddr = %p, g_data paddr = %p\n",
                      getpid(), g_data, &g_data, GetPhysicalAddrOfVirtual(&g_data));

    } else if (pid > 0) {
        printf("1, parent process pid = %d, g_data = %d, g_data vaddr = %p, g_data paddr = %p\n",
                      getpid(), g_data, &g_data, GetPhysicalAddrOfVirtual(&g_data));
        sleep(1);
        g_data = 20; // 写
        // printf("read g_data = %d\n", g_data); // 读
        // sleep(2);
        printf("2, parent process pid = %d, g_data = %d, g_data vaddr = %p, g_data paddr = %p\n",
                      getpid(), g_data, &g_data, GetPhysicalAddrOfVirtual(&g_data));

    } else {
      printf("fork error\n");
    }
    return 0;
}

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

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

相关文章

免费分享一套SpringBoot+Vue自习室(预约)管理系统,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue自习室预约)管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue自习室预约(预约)管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】SpringBootVue自习室预约(预约)管理系统…

flask库

文章目录 flask库1. 基本使用2. 路由路径和路由参数3. 请求跳转和请求参数4. 模板渲染1. 模板变量2. 过滤器3. 测试器 5. 钩子函数与响应对象 flask库 flask是python编写的轻量级框架&#xff0c;提供Werkzeug&#xff08;WSGI工具集&#xff09;和jinjia2&#xff08;渲染模板…

关于Ubuntu虚拟机识别不了USB设备的解决方案

唉昨天从网上找了一天的解决方案都没法让我的Ubuntu虚拟机识别USB设备&#xff0c;CSDN上有些方法是让从控制面板中进行修复&#xff0c;很多人都是一样的做法链接&#xff0c;那我觉得应该是可以解决的啊&#xff01; 结果我去控制面板执行修复的时候&#xff0c;显示报错“没…

【QT 5 +Linux下qt软件点击.sh脚本运行+Dconf编辑器+学习他人文章+番外篇:点击脚本运行软件】

【QT 5 Linux下qt软件点击.sh脚本运行Dconf编辑器学习他人文章番外篇&#xff1a;点击脚本运行软件】 1、前言2、实验环境3、自我学习总结-本篇总结1、说明&#xff1a;代替qt的快捷方式2、适用性更广3、了解工具&#xff1a;Dconf编辑器注意事项&#xff1a; 4、参考链接-感谢…

融入Facebook的世界:探索数字化社交的魅力

融入Facebook的世界&#xff0c;是一场数字化社交的奇妙之旅。在这个广袤的虚拟社交空间中&#xff0c;人们可以尽情展现自己、分享生活&#xff0c;与全球朋友、家人和同事保持紧密联系&#xff0c;共同探索社交互动的乐趣与魅力。让我们深入了解这个世界的魅力所在&#xff1…

【Git】Github 上commit后,绿格子contribution却不显示?不知道怎么弥补?解决方法在这里

github 上commit后&#xff0c;绿格子&#xff08;contribution&#xff09;却不显示 问题描述 今天一直在github上面commit代码&#xff0c;但是github中并没有显示自己的contribution&#xff08;没有绿色的格子&#xff09;&#xff0c;全是空白&#xff0c;网上一查是因为…

图像分类技术在电商平台的创新应用与实践

一、引言 在当今快速发展的互联网电商领域&#xff0c;商家面临着激烈的竞争和不断变化的市场需求。我们在服务电商的过程中&#xff0c;利用AI大模型技术创新性地引入了图像分类技术&#xff0c;为供应链管理带来了革命性的变革。接下来&#xff0c;我们将深入探讨这一项目的…

macOS Monterey 12.7.4 (21H1123) Boot ISO 原版可引导镜像下载

macOS Monterey 12.7.4 (21H1123) Boot ISO 原版可引导镜像下载 3 月 8 日凌晨&#xff0c;macOS Sonoma 14.4 发布&#xff0c;同时带来了 macOS Ventru 13.6.5 和 macOS Monterey 12.7.4 安全更新。 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xf…

京东按图搜索京东商品(拍立淘) API 返回值说明

京东按图搜索商品&#xff08;拍立淘&#xff09;的API返回值包含了关于通过图片搜索到的京东商品的相关信息。由于我无法提供最新的京东API返回值的确切结构&#xff0c;以下是基于常见API设计原则的一个大致的返回值示例和说明&#xff1a; 调用链接获取详情 item_search_i…

数据结构之链式二叉树

当我们初步了解二叉树后 我们就可以进一步去深入学习二叉树了 1.链式二叉树的遍历 这里我们先去定义链式二叉树的结构 分为两个指针 一左一右 他们分别指向左子树和右子树 typedef int BTDataType;typedef struct BinaryTreeNode {BTDataType data;struct BinartTreeNod…

InnoDB和MyISAM存储引擎

InnoDB mysql默认存储引擎 支持事务&#xff0c;行级锁&#xff08;并发量大&#xff09;&#xff0c;外键约束&#xff0c;容量大&#xff0c;支持缓存&#xff0c;支撑主键自增&#xff0c; 全文检索&#xff0c;不存储表的总行数&#xff0c;需要sql逐行统计 MyISAM 不…

扩展学习|网络问政的价值增量与实现条件:基于数据资源挖掘的视角

文献来源&#xff1a;[1]顾丹丹傅广宛.网络问政的价值增量与实现条件:基于数据资源挖掘的视角[J].中国行政管理, 2021, 000(004):76-82.DOI:10.19735/j.issn.1006-0863.2021.04.11. 一、技术赋能网络问政的机制生成 &#xff08;一&#xff09;技术赋能网络问政的流程&#xf…

Naive Ui Admin:企业级中后台项目开箱即用框架/让你少写一些代码

欢迎加入我们的前端组件学习交流群&#xff0c;可添加群主微信&#xff0c;审核通过后入群。 Naive Ui Admin&#xff1a;企业级中后台项目开箱即用框架/让你少写一些代码 在数字化时代&#xff0c;中后台系统对于企业的运营至关重要。然而&#xff0c;构建这样的系统往往需要…

202109青少年软件编程(图形化) 等级考试试卷(二级)

第1题:【 单选题】 执行下图所示程序, 舞台上的角色?( ) A:在 1 秒内滑行到随机位置 B:不断地重复滑行到随机位置 C:只有按下空格键的时候, 才会滑行到随机位置 D:只有按下空格键以外键的时候, 才会滑行到随机位置 【正确答案】: C 【试题解析】 : 第2题:【 单…

【C++】实现红黑树

目录 一、认识红黑树1.1 概念1.2 定义 二、实现红黑树2.1 插入2.2 与AVL树对比 一、认识红黑树 1.1 概念 红黑树是一个二叉搜索树&#xff0c;与AVL树相比&#xff0c;红黑树不再使用平衡因子来控制树的左右子树高度差&#xff0c;而是用颜色来控制平衡&#xff0c;颜色为红色…

详细分析Java中Stream流和for循环的差异之处

目录 前言1. 基本知识2. Demo 前言 事情起因是遍历大数据的时候&#xff0c;数据卡顿很严重 对于Java的基本知识推荐阅读&#xff1a;java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; 1. 基本知识 在Java中&#xff0c;Stream API提供…

Ubuntu 安装 KVM 虚拟化

1. Ubuntu 安装 KVM 虚拟化 KVM 是 Linux 内核中一个基于 hypervisor 的虚拟化模块&#xff0c;它允许用户在 Linux 操作系统上创建和管理虚拟机。 如果机器的CPU不支持硬件虚拟化扩展&#xff0c;是无法使用KVM(基于内核的虚拟机)直接创建和运行虚拟机的。此时最多只能使用…

HDS-NAS分配资源并挂载win和linux

1、首先创建系统文件。 选择nas存储池 2、根据自己的需求创建相应的挂载方式 3、window配置 配置成功 最后即可在window系统网络位置映射网络即可&#xff0c; 格式为\\123.3.4.5\test 注&#xff1a;IP地址 4、liunx挂载方式 创建完成之后即可挂载&#xff0c;注意目的主…

免费开源的 Vue 拖拽组件 VueDraggablePlus (兼容移动端)

VueDraggablePlus 支持 Vue2 / Vue3&#xff0c;是被尤雨溪推荐了的拖拽组件。我自己试用过了&#xff0c;还挺好用的&#xff0c;兼容移动端。 官网&#xff1a;https://alfred-skyblue.github.io/vue-draggable-plus/ 官网文档里面很详细了&#xff0c;我就不再介绍安装和用…

包冲突解决之-invalid constant type: 18

背景 现象一&#xff1a;引入了一个包A&#xff0c;服务突然起不来了&#xff0c;后台有报错信息&#xff0c;Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type xxx available: expected at least 1 bean which quali…