0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

内存剖析:从用户态到内核态内存都做了什么?

Linux阅码场 来源:Linux阅码场 作者:Linux阅码场 2023-01-06 11:04 次阅读

编者按:本文顺着c++关键字new向下,旨在分析介绍底层各层到底做了什么,为什么这么做。

1.c++用户层

1.1提供的接口

1.1.1new

l 调用operator new 从自由存储区分配一块足够大的内存(sizeof(结构))

l 调用相应的构造函数

l 构造完成后返回指向该对象的指针

1.1.2delete

l 调用相应的析构函数

l 调用operator delete将内存归还给自由存储区

1.1.3new数组

l 调用operator new[] 从自由存储区分配一块足够大的内存(sizeof(结构)+用区分对象数组指针和对象指针以及对象数组大小的额外数据),注意简单对象(即不需要构造函数的类型)将不会有额外数据的申请。

l 依次在内存中调用相应的构造函数

l 构造完成后返回指向该对象数组的起始地址,不包括前面的额外数据部分。

1.1.4delete数组

l 获取数组起始地址前面的额外数据,计算出数组长度

l 根据数据长度依次调用相应的析构函数

l调用operator delete将内存归还给自由存储区

1.2operator new 的三种形式

形式1.void* operator new (std::size_t size)throw (std::bad_alloc);

形式2.void* operator new (std::size_t size,const std::nothrow_t& nothrow_value) throw();

形式3.void* operator new (std::size_t size,void* ptr) throw();

形式1跟形式2的区别仅仅是是否抛出异常,当分配失败时,前者会抛出bad_alloc异常,后者返回NULL,不会抛出异常。它们都分配一个固定大小的连续内存。

形式3又被称为placement new,它多接收一个ptr参数,并且只是简单地返回该ptr。调用形式为 A* a=new(ptr)A()。在内存池中有广泛应用,ptr即来自自由存储区,可以是堆、栈或者预分配的内存块。

上述形式1和形式2都可以被重载,遵循作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。

注意在形式1中,如果new分配异常,将抛出异常导致后续代码不能被正常执行。即如果在new操作后有解锁操作,该解锁操作将不会执行导致死锁。

1.3设定内存分配失败入口函数

poYBAGO3kQCAC5egAACcaTL7jkU638.jpg

1.4自由存储区和堆的区别

从技术上来说,堆是C语言操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。

我们只需要记住:堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。这种区分大概是不同语言背景造成的。

1.5默认内存初始值

在vs2008(32bit)的debug模式下,由堆分配的内存初始值为0xcdcd,中文“屯”;由栈分配的内存初始值为0xcccc,中文“烫”。

1.6重载::operator new的理由

l 定位检查代码中内存错误

l 优化内存分配性能

l 获得内存使用统计数据

1.7重载::operator new的两种方式

方式1:不改变签名,替换系统现有版本

void* operator new(size_t size);

void operator delete(void* p);

使用方不需要包含任何特殊的头文件,也就是说不需要看见这两个函数声明。“性能优化”通常用这种方式。

方式2:增加新参数

// 其返回的指针必须能被普通的 ::operator delete(void*) 释放

void* operator new(size_t size, const char* file, int line);

Foo* p = new (__FILE, __LINE__) Foo;

也可以用宏替换 new 来节省打字。此种方式使用方需要看到这两个函数声明,也就是说要主动包含提供的头文件。“检测内存错误”和“统计内存使用情况”通常会用这种方式重载。

1.8重载::operator new的困境

1.8.1绝不能在library中重载::operator new

如果以上文提到的方式1来重载全局的::operator new,非常具有侵略性。使用该library的程序被迫使用了被重载的::operator new,并且一旦有另外的library也同样重载了::operator new,就将会导致链接问题。

那么如果采用上文提到的方式2来额外提供一个::operator new 版本呢,那就需要考虑重载后的::operator new 返回的指针能否被系统默认的::operator delete释放。如果不兼容系统则需要以方式1重载::operator new ,回到了上文提过的问题。如果兼容,那么在新版本的::operator new中能做的事比较有限,比如不能额外申请内存记录统计信息,除非定义一个包含统计信息的基类来作为所有申请对象的父类,但这样就相当于设定了开发规范,稍有不注意可能就会出错。

1.8.2使用重载带新参数的版本会有什么影响

如果使用方式1重载::operator new 使用起来似乎没有什么问题,但要考虑上节中提到的链接问题。

如果使用方式2来重载::operator new,分成以下两种场合。

对于以头文件形式提供的library,可以在所有的cpp实现文件起始部分包含重载::operator new 的头文件,但这具有侵略性。

对于以头文件加二进制库提供的library,实际上带新参数的版本并不会被这些库使用。

1.9单独为特定类重载成员函数operator new怎么样

与全局 ::operator new() 不同,per-class operator new() 和 operator delete () 的影响面要小得多,它只影响本 class 及其派生类。似乎重载 member operator new() 是可行的。但是我并不赞同这种做法。

如果一个类需要重载成员函数operator new(),说明它用到了特殊的内存分配策略,常见的情况是使用了内存池或对象池。宁愿把这一事实明显地摆出来,而不是改变 new的默认行为。

这可以归结为最小惊讶原则:如果我们在代码里读到 Node* p = new Node,通常我们会认为它在堆上分配了内存,如果 Node 类重载了成员函数operator new(),那么就需要事先仔细阅读 node.h 才能发现其实这行代码使用了私有的内存池。为什么不写得明确一点呢?如果写成Node*p = Node::createNode(),那么我们可能能猜到 Node::createNode() 肯定做了什么与 new不一样的事情,免得将来大吃一惊。

1.10代替重载::operator new的方案

从glibc的malloc入手,替换掉malloc。具体方式参考tcmalloc中的override方式,点此链接[1]。

主要使用了gcc提供的alias别名属性和weak属性,我们能实现替换掉系统默认的malloc原因在于系统提供的malloc系列函数都是被weak属性修饰的。

对于全局函数,如果没有显示修饰称weak属性,那么他属于强符号;对于全局变量,已初始化完毕的属于强符号,没有初始化完毕的则属于弱符号。

有如下3点规则:

l 链接时强弱符号都存在时以强符号为准;

l 链接时如果只有弱符号时以弱符号为准;

l 链接时如两个都是弱符号,则以内存占用大小较大的那个符号为准;

2.glibc层

2.1概述

实际上glibc采用了一种批发和零售的方式来管理内存。glibc每次通过系统调用的方式申请一大块内存(虚拟内存),当进程申请内存时,glibc就从自己获得的内存中取出一块给进程。

glibc对于heap内存申请大于128k的内存申请,glibc采用mmap的方式向内核申请内存,也就是此时的malloc是由mmap来实现的,这不能保证内存地址向上增长;小于128k的则采用brk,malloc调用系统调用brk来实现向内核批发虚拟内存,对于它来讲是正确的。128k的阀值,可以通过glibc的库函数进行设置。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1316

    浏览量

    39949
  • 内存
    +关注

    关注

    8

    文章

    2801

    浏览量

    73126
  • 函数
    +关注

    关注

    3

    文章

    4142

    浏览量

    61553
  • C++
    C++
    +关注

    关注

    21

    文章

    2070

    浏览量

    73039

原文标题:内存剖析:从用户态到内核态内存都做了什么?

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux 内存管理知识学习经验总结

    内核用户两部分,经典比例如下:用户
    发表于 02-25 17:08

    Linux内核下如何读写IIC

    目前在Linux3.12上,想在内核下读取LM75温度传感器的温度值,做了如下操作,但是读数据的时候i2c_transfer一直报错。先将LM75设备挂到IIC总线上:在sys下可以发现已经添加成功:但是读数据的时候就一直报错
    发表于 11-29 19:07

    【MYD-Y6ULX试用体验】用户蓝牙配置

    的stack为BlueZ, 参考前面的文章的图片,可以知道,内核用户使用的是MGMT接口来通讯。底层主要负责和Controller数据的传输,而数据的传输接口一般为USB,SDI
    发表于 03-24 09:21

    Linux内存系统: Linux 内存分配算法

    copy q,返回 q。并将 p 所指向的内存空间删除3、内核内存分配函数函数分配原理最大内存
    发表于 08-24 07:44

    Linux内存系统:内存使用场景

    文件映射、共享内存)· 程序的内存 map(栈、堆、code、data)· 内核用户的数据传递(copy_from_user、copy
    发表于 08-25 07:42

    Linux内存系统---走进Linux 内存

    内存区域· MMAP:共享库及匿名文件的映射区域· STACK:用户进程栈7、内核地址空间 · 直接映射区:线性空间中 3G 开始最大
    发表于 08-26 08:05

    鸿蒙内核源码分析(内存管理篇):虚拟内存和物理内存是怎么管理的

    内存一开始就是一张白纸,这些extern就是给它画大界线的,哪是属于什么段。这些值大小取决实际项目内存条的大小,不同的内存条,地址肯定
    发表于 11-20 10:54

    操作系统为什么分内核用户?这两者如何切换?

    操作系统为什么分内核用户,这两者如何切换?进程在地址空间会划分为哪些区域?堆和栈有什么区别?
    发表于 07-23 09:01

    linux内核解决竞引起的异常的方法

    文章目录linux系统中出现并发与竞相关概念:四种情形:linux内核解决竞引起的异常的方法:即同步方法中断屏蔽概念特点中断屏蔽的编程步骤中断屏蔽相关宏函数应用实例自旋锁概念特点利用自旋锁同步
    发表于 07-28 06:15

    请问CPU与寄存器,内核用户及如何切换?

    计算机硬件系统由哪几部分构成?编程语言的作用及与操作系统和硬件的关系是什么?请问CPU与寄存器,内核用户及如何切换?
    发表于 10-25 06:31

    内核是如何对task进行调度的呢

    调度器在runqueue里的算法是如何去实现的?内核是如何对task进行调度的呢?
    发表于 12-24 07:59

    OpenHarmony喂狗源码解读之用户源码

    timeout\n"); } else {// 用户设置喂狗超时时间为大于gap 用户喂狗间隔时间为// 获取内核的超时间 - gap
    发表于 01-26 10:57

    鸿蒙内核实现用户快速互斥锁Futex设计资料合集

    Futex(Fast userspace mutex,用户快速互斥锁),系列篇简称 快锁 ,是一个在 Linux 上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具,它第一次出现在
    发表于 03-23 14:12

    Linux虚拟内存和物理内存的深刻分析

    内存用户进程总是先获得一个虚拟内存区的使用权,最终通过缺页异常获得一块真正的物理内存。物理内存内核
    发表于 05-31 08:00

    FreeRTOS代码剖析之1:内存管理Heap

    内存管理是一个操作系统的重要组成部分之一,所有应用程序都离不开操作系统的内存管理。因此,在剖析FreeRTOS的内核代码之前,前对FreeRTOS的
    发表于 02-09 05:25 808次阅读
    FreeRTOS代码<b class='flag-5'>剖析</b>之1:<b class='flag-5'>内存</b>管理Heap