RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

內(nèi)存剖析:從用戶態(tài)到內(nèi)核態(tài)內(nèi)存都做了什么?

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者:Linux閱碼場 ? 2023-01-06 11:04 ? 次閱讀

編者按:本文順著c++關鍵字new向下,旨在分析介紹底層各層到底做了什么,為什么這么做。

1.c++用戶層

1.1提供的接口

1.1.1new

l 調(diào)用operator new 從自由存儲區(qū)分配一塊足夠大的內(nèi)存(sizeof(結構))

l 調(diào)用相應的構造函數(shù)

l 構造完成后返回指向該對象的指針

1.1.2delete

l 調(diào)用相應的析構函數(shù)

l 調(diào)用operator delete將內(nèi)存歸還給自由存儲區(qū)

1.1.3new數(shù)組

l 調(diào)用operator new[] 從自由存儲區(qū)分配一塊足夠大的內(nèi)存(sizeof(結構)+用區(qū)分對象數(shù)組指針和對象指針以及對象數(shù)組大小的額外數(shù)據(jù)),注意簡單對象(即不需要構造函數(shù)的類型)將不會有額外數(shù)據(jù)的申請。

l 依次在內(nèi)存中調(diào)用相應的構造函數(shù)

l 構造完成后返回指向該對象數(shù)組的起始地址,不包括前面的額外數(shù)據(jù)部分。

1.1.4delete數(shù)組

l 獲取數(shù)組起始地址前面的額外數(shù)據(jù),計算出數(shù)組長度

l 根據(jù)數(shù)據(jù)長度依次調(diào)用相應的析構函數(shù)

l調(diào)用operator delete將內(nèi)存歸還給自由存儲區(qū)

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的區(qū)別僅僅是是否拋出異常,當分配失敗時,前者會拋出bad_alloc異常,后者返回NULL,不會拋出異常。它們都分配一個固定大小的連續(xù)內(nèi)存。

形式3又被稱為placement new,它多接收一個ptr參數(shù),并且只是簡單地返回該ptr。調(diào)用形式為 A* a=new(ptr)A()。在內(nèi)存池中有廣泛應用,ptr即來自自由存儲區(qū),可以是堆、?;蛘哳A分配的內(nèi)存塊。

上述形式1和形式2都可以被重載,遵循作用域覆蓋原則,即在里向外尋找operator new的重載時,只要找到operator new()函數(shù)就不再向外查找,如果參數(shù)符合則通過,如果參數(shù)不符合則報錯,而不管全局是否還有相匹配的函數(shù)原型。

注意在形式1中,如果new分配異常,將拋出異常導致后續(xù)代碼不能被正常執(zhí)行。即如果在new操作后有解鎖操作,該解鎖操作將不會執(zhí)行導致死鎖。

1.3設定內(nèi)存分配失敗入口函數(shù)

poYBAGO3kQCAC5egAACcaTL7jkU638.jpg

1.4自由存儲區(qū)和堆的區(qū)別

從技術上來說,堆是C語言操作系統(tǒng)的術語。堆是操作系統(tǒng)所維護的一塊特殊內(nèi)存,它提供了動態(tài)分配的功能,當運行程序調(diào)用malloc()時就會從中分配,稍后調(diào)用free可把內(nèi)存交還。而自由存儲是C++中通過new和delete動態(tài)分配和釋放對象的抽象概念,通過new來申請的內(nèi)存區(qū)域可稱為自由存儲區(qū)?;旧希械腃++編譯器默認使用堆來實現(xiàn)自由存儲,也即是缺省的全局運算符new和delete也許會按照malloc和free的方式來被實現(xiàn),這時藉由new運算符分配的對象,說它在堆上也對,說它在自由存儲區(qū)上也正確。但程序員也可以通過重載操作符,改用其他內(nèi)存來實現(xiàn)自由存儲,例如全局變量做的對象池,這時自由存儲區(qū)就區(qū)別于堆了。

我們只需要記?。憾咽遣僮飨到y(tǒng)維護的一塊內(nèi)存,而自由存儲是C++中通過new與delete動態(tài)分配和釋放對象的抽象概念。堆與自由存儲區(qū)并不等價。這種區(qū)分大概是不同語言背景造成的。

1.5默認內(nèi)存初始值

在vs2008(32bit)的debug模式下,由堆分配的內(nèi)存初始值為0xcdcd,中文“屯”;由棧分配的內(nèi)存初始值為0xcccc,中文“燙”。

1.6重載::operator new的理由

l 定位檢查代碼中內(nèi)存錯誤

l 優(yōu)化內(nèi)存分配性能

l 獲得內(nèi)存使用統(tǒng)計數(shù)據(jù)

1.7重載::operator new的兩種方式

方式1:不改變簽名,替換系統(tǒng)現(xiàn)有版本

void* operator new(size_t size);

void operator delete(void* p);

使用方不需要包含任何特殊的頭文件,也就是說不需要看見這兩個函數(shù)聲明。“性能優(yōu)化”通常用這種方式。

方式2:增加新參數(shù)

// 其返回的指針必須能被普通的 ::operator delete(void*) 釋放

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

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

也可以用宏替換 new 來節(jié)省打字。此種方式使用方需要看到這兩個函數(shù)聲明,也就是說要主動包含提供的頭文件。“檢測內(nèi)存錯誤”和“統(tǒng)計內(nèi)存使用情況”通常會用這種方式重載。

1.8重載::operator new的困境

1.8.1絕不能在library中重載::operator new

如果以上文提到的方式1來重載全局的::operator new,非常具有侵略性。使用該library的程序被迫使用了被重載的::operator new,并且一旦有另外的library也同樣重載了::operator new,就將會導致鏈接問題。

那么如果采用上文提到的方式2來額外提供一個::operator new 版本呢,那就需要考慮重載后的::operator new 返回的指針能否被系統(tǒng)默認的::operator delete釋放。如果不兼容系統(tǒng)則需要以方式1重載::operator new ,回到了上文提過的問題。如果兼容,那么在新版本的::operator new中能做的事比較有限,比如不能額外申請內(nèi)存記錄統(tǒng)計信息,除非定義一個包含統(tǒng)計信息的基類來作為所有申請對象的父類,但這樣就相當于設定了開發(fā)規(guī)范,稍有不注意可能就會出錯。

1.8.2使用重載帶新參數(shù)的版本會有什么影響

如果使用方式1重載::operator new 使用起來似乎沒有什么問題,但要考慮上節(jié)中提到的鏈接問題。

如果使用方式2來重載::operator new,分成以下兩種場合。

對于以頭文件形式提供的library,可以在所有的cpp實現(xiàn)文件起始部分包含重載::operator new 的頭文件,但這具有侵略性。

對于以頭文件加二進制庫提供的library,實際上帶新參數(shù)的版本并不會被這些庫使用。

1.9單獨為特定類重載成員函數(shù)operator new怎么樣

與全局 ::operator new() 不同,per-class operator new() 和 operator delete () 的影響面要小得多,它只影響本 class 及其派生類。似乎重載 member operator new() 是可行的。但是我并不贊同這種做法。

如果一個類需要重載成員函數(shù)operator new(),說明它用到了特殊的內(nèi)存分配策略,常見的情況是使用了內(nèi)存池或?qū)ο蟪?。寧愿把這一事實明顯地擺出來,而不是改變 new的默認行為。

這可以歸結為最小驚訝原則:如果我們在代碼里讀到 Node* p = new Node,通常我們會認為它在堆上分配了內(nèi)存,如果 Node 類重載了成員函數(shù)operator new(),那么就需要事先仔細閱讀 node.h 才能發(fā)現(xiàn)其實這行代碼使用了私有的內(nèi)存池。為什么不寫得明確一點呢?如果寫成Node*p = Node::createNode(),那么我們可能能猜到 Node::createNode() 肯定做了什么與 new不一樣的事情,免得將來大吃一驚。

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

從glibc的malloc入手,替換掉malloc。具體方式參考tcmalloc中的override方式,點此鏈接[1]。

主要使用了gcc提供的alias別名屬性和weak屬性,我們能實現(xiàn)替換掉系統(tǒng)默認的malloc原因在于系統(tǒng)提供的malloc系列函數(shù)都是被weak屬性修飾的。

對于全局函數(shù),如果沒有顯示修飾稱weak屬性,那么他屬于強符號;對于全局變量,已初始化完畢的屬于強符號,沒有初始化完畢的則屬于弱符號。

有如下3點規(guī)則:

l 鏈接時強弱符號都存在時以強符號為準;

l 鏈接時如果只有弱符號時以弱符號為準;

l 鏈接時如兩個都是弱符號,則以內(nèi)存占用大小較大的那個符號為準;

2.glibc層

2.1概述

實際上glibc采用了一種批發(fā)和零售的方式來管理內(nèi)存。glibc每次通過系統(tǒng)調(diào)用的方式申請一大塊內(nèi)存(虛擬內(nèi)存),當進程申請內(nèi)存時,glibc就從自己獲得的內(nèi)存中取出一塊給進程。

glibc對于heap內(nèi)存申請大于128k的內(nèi)存申請,glibc采用mmap的方式向內(nèi)核申請內(nèi)存,也就是此時的malloc是由mmap來實現(xiàn)的,這不能保證內(nèi)存地址向上增長;小于128k的則采用brk,malloc調(diào)用系統(tǒng)調(diào)用brk來實現(xiàn)向內(nèi)核批發(fā)虛擬內(nèi)存,對于它來講是正確的。128k的閥值,可以通過glibc的庫函數(shù)進行設置。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關注

    關注

    3

    文章

    1372

    瀏覽量

    40276
  • 內(nèi)存
    +關注

    關注

    8

    文章

    3019

    瀏覽量

    74003
  • 函數(shù)
    +關注

    關注

    3

    文章

    4327

    瀏覽量

    62569
  • C++
    C++
    +關注

    關注

    22

    文章

    2108

    瀏覽量

    73618

原文標題:內(nèi)存剖析:從用戶態(tài)到內(nèi)核態(tài)內(nèi)存都做了什么?

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Linux 內(nèi)存管理知識學習經(jīng)驗總結

    內(nèi)核態(tài)用戶態(tài)兩部分,經(jīng)典比例如下:用戶態(tài)
    發(fā)表于 02-25 17:08

    Linux內(nèi)核態(tài)下如何讀寫IIC

    目前在Linux3.12上,想在內(nèi)核態(tài)下讀取LM75溫度傳感器的溫度值,做了如下操作,但是讀數(shù)據(jù)的時候i2c_transfer一直報錯。先將LM75設備掛到IIC總線上:在sys下可以發(fā)現(xiàn)已經(jīng)添加成功:但是讀數(shù)據(jù)的時候就一直報錯
    發(fā)表于 11-29 19:07

    Linux內(nèi)存系統(tǒng):內(nèi)存使用場景

    文件映射、共享內(nèi)存)· 程序的內(nèi)存 map(棧、堆、code、data)· 內(nèi)核用戶態(tài)的數(shù)據(jù)傳遞(copy_from_user、copy
    發(fā)表于 08-25 07:42

    Linux內(nèi)存系統(tǒng)---走進Linux 內(nèi)存

    內(nèi)存區(qū)域· MMAP:共享庫及匿名文件的映射區(qū)域· STACK:用戶進程棧7、內(nèi)核態(tài)地址空間 · 直接映射區(qū):線性空間中 3G 開始最大
    發(fā)表于 08-26 08:05

    操作系統(tǒng)為什么分內(nèi)核態(tài)用戶態(tài)?這兩者如何切換?

    操作系統(tǒng)為什么分內(nèi)核態(tài)用戶態(tài),這兩者如何切換?進程在地址空間會劃分為哪些區(qū)域?堆和棧有什么區(qū)別?
    發(fā)表于 07-23 09:01

    請問CPU與寄存器,內(nèi)核態(tài)用戶態(tài)及如何切換?

    計算機硬件系統(tǒng)由哪幾部分構成?編程語言的作用及與操作系統(tǒng)和硬件的關系是什么?請問CPU與寄存器,內(nèi)核態(tài)用戶態(tài)及如何切換?
    發(fā)表于 10-25 06:31

    OpenHarmony喂狗源碼解讀之用戶態(tài)源碼

    timeout\n"); } else {// 用戶態(tài)設置喂狗超時時間為大于gap 用戶態(tài)喂狗間隔時間為// 獲取內(nèi)核的超時間 - gap
    發(fā)表于 01-26 10:57

    鴻蒙內(nèi)核實現(xiàn)用戶態(tài)快速互斥鎖Futex設計資料合集

    Futex(Fast userspace mutex,用戶態(tài)快速互斥鎖),系列篇簡稱 快鎖 ,是一個在 Linux 上實現(xiàn)鎖定和構建高級抽象鎖如信號量和POSIX互斥的基本工具,它第一次出現(xiàn)在
    發(fā)表于 03-23 14:12

    Linux虛擬內(nèi)存和物理內(nèi)存的深刻分析

    內(nèi)存,用戶進程總是先獲得一個虛擬內(nèi)存區(qū)的使用權,最終通過缺頁異常獲得一塊真正的物理內(nèi)存。物理內(nèi)存內(nèi)核
    發(fā)表于 05-31 08:00

    一個內(nèi)核態(tài)Key-Value存儲系統(tǒng)

    的數(shù)據(jù)存儲需求中尤為突出。針對該問題,給出了一個內(nèi)核態(tài)Key-Value存儲系統(tǒng)的實現(xiàn)-KStore:提供內(nèi)核空間的索引和內(nèi)存分配機制,并在此基礎上,通過基于
    發(fā)表于 01-19 16:37 ?0次下載
    一個<b class='flag-5'>內(nèi)核</b><b class='flag-5'>態(tài)</b>Key-Value存儲系統(tǒng)

    詳解Linux的物理內(nèi)存

    內(nèi)核態(tài)申請內(nèi)存比在用戶態(tài)申請內(nèi)存要更為直接,它沒有采用用戶
    的頭像 發(fā)表于 01-18 17:45 ?2410次閱讀
    詳解Linux的物理<b class='flag-5'>內(nèi)存</b>

    Linux內(nèi)核態(tài)缺頁會發(fā)生什么 - 玩轉Exception fixup表

    Linux內(nèi)核的做法是提供了一張 異常處理表 ,使用專有的函數(shù)來訪問用戶態(tài)內(nèi)存。類似 try-catch塊一般。具體詳情可參見copy_to_user/copy_from_user的實
    的頭像 發(fā)表于 06-03 15:08 ?2965次閱讀

    探究slab在內(nèi)核內(nèi)存管理和用戶態(tài)Memcached的雙重存在

    ,但是作為內(nèi)核的堆用戶本身,經(jīng)常只是調(diào)用kmalloc()申請一個小內(nèi)存,或者調(diào)用kmem_cache_alloc()申請一個數(shù)據(jù)結構,2^n頁給它,會形成大量碎片浪費。所以slab找buddy要了2
    的頭像 發(fā)表于 08-13 14:55 ?1459次閱讀
    探究slab在<b class='flag-5'>內(nèi)核</b><b class='flag-5'>內(nèi)存</b>管理和<b class='flag-5'>用戶</b><b class='flag-5'>態(tài)</b>Memcached的雙重存在

    Linux內(nèi)核用戶態(tài)是如何睡眠的

    clock_nanosleep系統(tǒng)調(diào)用來進行睡眠(也就是說用戶態(tài)任務睡眠需要調(diào)用系統(tǒng)調(diào)用陷入內(nèi)核)。 下面我們來研究下clock_nanosleep的實現(xiàn)(這里集中睡眠的實現(xiàn),先忽略
    的頭像 發(fā)表于 08-16 15:06 ?1913次閱讀

    如何實現(xiàn)一個高性能內(nèi)存

    ,按照慣例先說內(nèi)存池的應用場景。 為什么我們需要內(nèi)存池? 因為malloc等分配內(nèi)存的方式,需要涉及系統(tǒng)調(diào)用sbrk,頻繁的malloc和free會消耗系統(tǒng)資源。 既然如此,我們就預
    的頭像 發(fā)表于 11-10 11:11 ?659次閱讀
    如何實現(xiàn)一個高性能<b class='flag-5'>內(nèi)存</b>池
    RM新时代网站-首页