<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          說出來你可能不信,內核這家伙在內存的使用上給自己開了個小灶!

          共 4895字,需瀏覽 10分鐘

           ·

          2021-02-20 12:49


          現在你可能還覺得node、zone、伙伴系統、slab這些東東還有那么一點點陌生。別怕,接下來我們結合動手觀察,把它們逐個來展開細說。(下面的討論都基于Linux 3.10.0版本)

          一、NODE 劃分

          在現代的服務器上,內存和CPU都是所謂的NUMA架構

          CPU往往不止是一顆。通過dmidecode命令看到你主板上插著的CPU的詳細信息
          Processor?Information??//第一顆CPU
          ????SocketDesignation:?CPU1???
          ????Version:?Intel(R)?Xeon(R)?CPU?E5-2630?v3?@?2.40GHz
          ????Core?Count:?8
          ????Thread?Count:?16
          Processor?Information??//第二顆CPU
          ????Socket?Designation:?CPU2
          ????Version:?Intel(R)?Xeon(R)?CPU?E5-2630?v3?@?2.40GHz
          ????Core?Count:?8

          內存也不只是一條。dmidecode同樣可以查看到服務器上插著的所有內存條,也可以看到它是和哪個CPU直接連接的。

          //CPU1?上總共插著四條內存
          Memory?Device
          ????Size:?16384?MB
          ????Locator:?CPU1?DIMM?A1
          Memory?Device
          ????Size:?16384?MB
          ????Locator:?CPU1?DIMM?A2
          ......??
          //CPU2?上也插著四條
          Memory?Device
          ????Size:?16384?MB
          ????Locator:?CPU2?DIMM?E1
          Memory?Device
          ????Size:?16384?MB
          ????Locator:?CPU2?DIMM?F1
          ......

          每一個CPU以及和他直連的內存條組成了一個 node(節(jié)點)。

          在你的機器上,你可以使用numactl你可以看到每個node的情況

          numactl?--hardware
          available:?2?nodes?(0-1)
          node?0?cpus:?0?1?2?3?4?5?6?7?16?17?18?19?20?21?22?23
          node?0?size:?65419?MB
          node?1?cpus:?8?9?10?11?12?13?14?15?24?25?26?27?28?29?30?31
          node?1?size:?65536?MB

          二、ZONE 劃分

          每個 node 又會劃分成若干的 zone(區(qū)域) 。zone 表示內存中的一塊范圍

          • ZONE_DMA:地址段最低的一塊內存區(qū)域,ISA(Industry Standard Architecture)設備DMA訪問

          • ZONE_DMA32:該Zone用于支持32-bits地址總線的DMA設備,只在64-bits系統里才有效
          • ZONE_NORMAL:在X86-64架構下,DMA和DMA32之外的內存全部在NORMAL的Zone里管理

          為什么沒有提 ZONE_HIGHMEM 這個zone?因為這是 32 位機時代的產物?,F在應該沒誰在用這種古董了吧。

          在每個zone下,都包含了許許多多個 Page(頁面), 在linux下一個Page的大小一般是 4 KB。

          在你的機器上,你可以使用通過 zoneinfo 查看到你機器上 zone 的劃分,也可以看到每個 zone 下所管理的頁面有多少個。
          #?cat?/proc/zoneinfo
          Node?0,?zone??????DMA
          ????pages?free?????3973
          ????????managed??3973
          Node?0,?zone????DMA32
          ????pages?free?????390390
          ????????managed??427659
          Node?0,?zone???Normal
          ????pages?free?????15021616
          ????????managed??15990165
          Node?1,?zone???Normal
          ????pages?free?????16012823
          ????????managed??16514393????????????????????????

          每個頁面大小是4K,很容易可以計算出每個 zone 的大小。比如對于上面 Node1 的 Normal, 16514393 * 4K = 66 GB。

          三、基于伙伴系統管理空閑頁面

          每個 zone 下面都有如此之多的頁面,Linux使用伙伴系統對這些頁面進行高效的管理。在內核中,表示 zone 的數據結構是 struct zone。其下面的一個數組 free_area 管理了絕大部分可用的空閑頁面。這個數組就是伙伴系統實現的重要數據結構。

          //file:?include/linux/mmzone.h
          #define?MAX_ORDER?11
          struct?zone?{
          ????free_area???free_area[MAX_ORDER];
          ????......
          }

          free_area是一個11個元素的數組,在每一個數組分別代表的是空閑可分配連續(xù)4K、8K、16K、......、4M內存鏈表。


          通過 cat /proc/pagetypeinfo, 你可以看到當前系統里伙伴系統里各個尺寸的可用連續(xù)內存塊數量。

          內核提供分配器函數 alloc_pages 到上面的多個鏈表中尋找可用連續(xù)頁面。
          struct?page?*?alloc_pages(gfp_t?gfp_mask,?unsigned?int?order)

          alloc_pages是怎么工作的呢?我們舉個簡單的小例子。假如要申請8K-連續(xù)兩個頁框的內存。為了描述方便,我們先暫時忽略UNMOVEABLE、RELCLAIMABLE等不同類型

          伙伴系統中的伙伴指的是兩個內存塊,大小相同,地址連續(xù),同屬于一個大塊區(qū)域。

          基于伙伴系統的內存分配中,有可能需要將大塊內存拆分成兩個小伙伴。在釋放中,可能會將兩個小伙伴合并再次組成更大塊的連續(xù)內存。

          四、SLAB管理器

          說到現在,不知道你注意到沒有。目前我們介紹的內存分配都是以頁面(4KB)為單位的

          對于各個內核運行中實際使用的對象來說,多大的對象都有。有的對象有1K多,但有的對象只有幾百、甚至幾十個字節(jié)。如果都直接分配一個 4K的頁面 來存儲的話也太敗家了,所以伙伴系統并不能直接使用。

          在伙伴系統之上,內核又給自己搞了一個專用的內存分配器, 叫slab或slub。這兩個詞老混用,為了省事,接下來我們就統一叫 slab 吧。

          這個分配器最大的特點就是,一個slab內只分配特定大小、甚至是特定的對象。這樣當一個對象釋放內存后,另一個同類對象可以直接使用這塊內存。通過這種辦法極大地降低了碎片發(fā)生的幾率。

          slab相關的內核對象定義如下:
          //file:?include/linux/slab_def.h
          struct?kmem_cache?{
          ????struct?kmem_cache_node?**node
          ????......
          }

          //file:
          ?mm/slab.h
          struct?kmem_cache_node?{
          ????struct?list_head?slabs_partial;?
          ????struct?list_head?slabs_full;
          ????struct?list_head?slabs_free;
          ????......
          }

          每個cache都有滿、半滿、空三個鏈表。每個鏈表節(jié)點都對應一個 slab,一個 slab 由 1 個或者多個內存頁組成。

          在每一個 slab 內都保存的是同等大小的對象。 一個cache的組成示意圖如下:

          當 cache 中內存不夠的時候,會調用基于伙伴系統的分配器(__alloc_pages函數)請求整頁連續(xù)內存的分配。
          //file:?mm/slab.c
          static?void?*kmem_getpages(struct?kmem_cache?*cachep,?
          ?????????gfp_t?flags,?int?nodeid)

          {
          ????......
          ????flags?|=?cachep->allocflags;
          ????if?(cachep->flags?&?SLAB_RECLAIM_ACCOUNT)
          ????????flags?|=?__GFP_RECLAIMABLE;

          ????page?=?alloc_pages_exact_node(nodeid,?...);
          ????......
          }
          //file:?include/linux/gfp.h
          static?inline?struct?page?*alloc_pages_exact_node(int?nid,?
          ????????gfp_t?gfp_mask,unsigned?int?order)

          {
          ????return?__alloc_pages(gfp_mask,?order,?node_zonelist(nid,?gfp_mask));
          }

          內核中會有很多個 kmem_cache 存在。它們是在linux初始化,或者是運行的過程中分配出來的。它們有的是專用的,有的是通用的。

          上圖中,我們看到 socket_alloc 內核對象都存在 TCP的專用 kmem_cache 中。

          通過查看 /proc/slabinfo 我們可以查看到所有的 kmem cache。

          另外 linux 還提供了一個特別方便的命令 slabtop 來按照占用內存從大往小進行排列。這個命令用來分析 slab 內存開銷非常的方便。

          無論是 /proc/slabinfo,還是 slabtop 命令的輸出。里面都包含了每個 cache 中 slab的如下兩個關鍵信息。
          • objsize:每個對象的大小
          • objperslab:一個 slab 里存放的對象的數量

          /proc/slabinfo 還多輸出了一個pagesperslab。展示了一個slab 占用的頁面的數量,每個頁面4K,這樣也就能算出每個 slab 占用的內存大小。

          最后,slab 管理器組件提供了若干接口函數,方便自己使用。舉三個例子:

          • kmem_cache_create: 方便地創(chuàng)建一個基于 slab 的內核對象管理器。
          • kmem_cache_alloc: 快速為某個對象申請內存
          • kmem_cache_free: 歸還對象占用的內存給 slab 管理器

          在內核的源碼中,可以大量見到 kmem_cache 開頭函數的使用。

          總結

          通過上面描述的幾個步驟,內核高效地把內存用了起來。

          前三步是基礎模塊,為應用程序分配內存時的請求調頁組件也能夠用到。但第四步,就算是內核的小灶了。內核根據自己的使用場景,量身打造的一套自用的高效內存分配管理機制。

          #?cat?/proc/slabinfo?|?grep?TCP
          TCP??????????????????288????384???1984???16????8

          “可以看到 TCP cache下每個 slab 占用 8 個 Page,也就是 8* 4096 = 32768KB。該對象的單個大小是 1984 字節(jié) 字節(jié),每個slab內放了 16 個對象。1984*16=31744”

          “這個時候再多放一個 TCP 對象又放不下,剩下的 1K 內存就只好“浪費”掉了。但是鑒于 slab 機制整體提供的高性能、以及低碎片的效果,這一點點的額外開銷還是很值得的?!?/p>



          飛哥Github出爐,訪問請復制下面網址?

          網址https://github.com/yanfeizhang/coder-kung-fu

          附項目預覽圖如下:


          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人不卡视频 | 久久久久无码精品人妻 | 人人摸在线观看 | 日本高清色视影www | 欧美大黑逼|