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

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

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

保留Linux內(nèi)存的初始化原理及應用實戰(zhàn)

冬至子 ? 來源:Linux與SoC ? 作者:Linux與SoC ? 2023-06-05 15:07 ? 次閱讀

1. 概述

linux啟動過程中會打印出如下信息,這些信息為我們呈現(xiàn)出系統(tǒng)下的保留內(nèi)存空間情況。

Reserved memory: created DMA memory pool at 0x4c000000, size 8 MiB
OF: reserved mem: initialized node vram@4c000000, compatible id shared-dma-pool

本文只介紹基本的保留內(nèi)存,不涉及CMA部分內(nèi)容

保留內(nèi)存的初始化流程如下圖所示:

圖片

本文所說的保留內(nèi)存的作用,概況來講包括如下四方面:

  1. 驅(qū)動程序特定使用
  2. 加載固件到指定內(nèi)存
  3. DDR中某段內(nèi)存區(qū)域存放特定數(shù)據(jù),如多核處理相關(guān)代碼
  4. 調(diào)試驅(qū)動

2. 保留內(nèi)存初始化流程

setup_arch()函數(shù)中我們可以發(fā)現(xiàn),保留內(nèi)存初始化是在設(shè)備樹釋放之前,通過解析FDT,獲取保留內(nèi)存的參數(shù)來進行初始化。

void __init setup_arch(char **cmdline_p)
{
...
 arm_memblock_init(mdesc);
...
 unflatten_device_tree();
...

2.1 解析內(nèi)核中的保留內(nèi)存空間

在各平臺初始化過程中調(diào)用early_init_fdt_scan_reserved_mem()進行保留內(nèi)存的初始化。

setup_arch_memory in init.c (arch\\arc\\mm) :  early_init_fdt_scan_reserved_mem();
arm64_memblock_init in init.c (arch\\arm64\\mm) :  early_init_fdt_scan_reserved_mem();
arm_memblock_init in init.c (arch\\arm\\mm) :  early_init_fdt_scan_reserved_mem();
setup_bootmem in init.c (arch\\riscv\\mm) :  early_init_fdt_scan_reserved_mem();
bootmem_init in init.c (arch\\xtensa\\mm) :  early_init_fdt_scan_reserved_mem();
sh_of_mem_reserve in of-generic.c (arch\\sh\\boards) :  early_init_fdt_scan_reserved_mem();
of_fdt.h (include\\linux) line 66 : extern void early_init_fdt_scan_reserved_mem(void);
of_fdt.h (include\\linux) line 94 : static inline void early_init_fdt_scan_reserved_mem(void) {}
early_reserve_mem_dt in prom.c (arch\\powerpc\\kernel) :  early_init_fdt_scan_reserved_mem();
csky_memblock_init in setup.c (arch\\csky\\kernel) :  early_init_fdt_scan_reserved_mem();
bootmem_init in setup.c (arch\\h8300\\kernel) :  early_init_fdt_scan_reserved_mem();
arch_mem_init in setup.c (arch\\mips\\kernel) :  early_init_fdt_scan_reserved_mem();
setup_memory in setup.c (arch\\nds32\\kernel) :  early_init_fdt_scan_reserved_mem();
setup_arch in setup.c (arch\\nios2\\kernel) :  early_init_fdt_scan_reserved_mem();
setup_memory in setup.c (arch\\openrisc\\kernel) :  early_init_fdt_scan_reserved_mem();

它的定義位于drivers/of/fdt.c中,需要內(nèi)核配置打開CONFIG_OF_EARLY_FLATTREE宏。

圖片

主體函數(shù)如下:

void __init early_init_fdt_scan_reserved_mem(void)
{
 int n;
 u64 base, size;

 if (!initial_boot_params)
  return;

 for (n = 0; ; n++) {
  fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
  if (!size)
   break;
  early_init_dt_reserve_memory_arch(base, size, false);
 }

 of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
 fdt_init_reserved_mem();
}

2.1.1 解析memreserve

early_init_fdt_scan_reserved_mem()函數(shù)中的initial_boot_params可以再次確定這一點。initial_boot_params代表的是fdt的地址,如下:

## Flattened Device Tree blob at 41000000
   Booting using the fdt blob at 0x41000000
   Loading Kernel Image ... OK
   Loading Device Tree to 4ffef000, end 4ffffff2 ... OK

Starting kernel ...

Uncompressing Linux... done, booting the kernel.
[    0.000000] Booting Linux on physical CPU 0xa00
...
[    0.000000] --- initial_boot_params(fdt addr 0x4ffef000)

通過fdt_get_mem_rsv()解析設(shè)備樹中的/memreserve/fields,例如樹莓派處理器的設(shè)備樹中定義了該屬性,通常來講,這部分內(nèi)存區(qū)域是存放和rom或者多核啟動相關(guān)的程序,需要注意的是內(nèi)核無法使用這部分內(nèi)存。這是和reserver memory的區(qū)別。

/memreserve/ 0x00000000 0x00001000;
...
/ {
        compatible = "brcm,bcm2835";
...

若在設(shè)備樹中查找到了memreserve且未進行映射,則通過memblock_reserve()將這部分內(nèi)存區(qū)域加入到memblock.reserved,當進行memblock到buddy轉(zhuǎn)換時,釋放掉memblock.reserved所標記的內(nèi)存區(qū)域。

int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
 phys_addr_t end = base + size - 1;

 memblock_dbg("memblock_reserve: [%pa-%pa] %pS\\n",
       &base, &end, (void *)_RET_IP_);

 return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

2.1.2 解析reserve memory

通過__fdt_scan_reserved_mem()解析設(shè)備樹中保留內(nèi)存相關(guān)的結(jié)點信息。

static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
       int depth, void *data)
{
 static int found;
 int err;

 if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
  ...
 }

 if (!of_fdt_device_is_available(initial_boot_params, node))
  return 0;

 err = __reserved_mem_reserve_reg(node, uname);
 if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
  fdt_reserved_mem_save_node(node, uname, 0, 0);
...
}

該函數(shù)首先解析設(shè)備樹中reserved-memory結(jié)點并確認是否有效。若有效,繼續(xù)檢查regsize屬性定義的內(nèi)存區(qū)域,通過fdt_reserved_mem_save_node()將內(nèi)存信息更新到數(shù)據(jù)結(jié)構(gòu)struct reserved_mem

void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,phys_addr_t base, phys_addr_t size)
{
 struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

 if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
  pr_err("not enough space all defined regions.\\n");
  return;
 }

 rmem- >fdt_node = node;
 rmem- >name = uname;
 rmem- >base = base;
 rmem- >size = size;

 reserved_mem_count++;
 return;
}

2.2 保留內(nèi)存初始化

保留內(nèi)存初始化的主體函數(shù)是fdt_init_reserved_mem(),其首先解析設(shè)備樹結(jié)點no-map、phandle等信息,最后通過關(guān)鍵函數(shù)__reserved_mem_init_node()完成保留內(nèi)存子節(jié)點的初始化。

放開解析保留內(nèi)存解析相關(guān)的打印,再回頭再看kernel的啟動信息,啟動信息中保留內(nèi)存相關(guān)內(nèi)容正是此處打印出來的。

OF: fdt: Reserved memory: reserved region for node 'vram@4c000000': base 0x4c000000, size 8 MiB
Reserved memory: created DMA memory pool at 0x4c000000, size 8 MiB

created DMA memory pool...來自函數(shù)rmem_dma_setup(),這個函數(shù)從數(shù)據(jù)結(jié)構(gòu)struct reserved_mem獲取保留內(nèi)存的信息。

static int __init rmem_dma_setup(struct reserved_mem *rmem)
{
 unsigned long node = rmem- >fdt_node;

 if (of_get_flat_dt_prop(node, "reusable", NULL))
  return -EINVAL;

#ifdef CONFIG_ARM
 if (!of_get_flat_dt_prop(node, "no-map", NULL)) {
  pr_err("Reserved memory: regions without no-map are not yet supported\\n");
  return -EINVAL;
 }

 if (of_get_flat_dt_prop(node, "linux,dma-default", NULL)) {
  WARN(dma_reserved_default_memory,
       "Reserved memory: region for default DMA coherent area is redefined\\n");
  dma_reserved_default_memory = rmem;
 }
#endif

 rmem- >ops = &rmem_dma_ops;
 pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\\n",
  &rmem- >base, (unsigned long)rmem- >size / SZ_1M);
 return 0;
}

在函數(shù)rmem_dma_setup()中還會例化reserved_mem.ops,如下:

static const struct reserved_mem_ops rmem_dma_ops = {
 .device_init = rmem_dma_device_init,
 .device_release = rmem_dma_device_release,
};

3. 設(shè)備樹中保留內(nèi)存的定義方式

vexpress-v2p-ca9.dts中保留內(nèi)存的定義方式為例,說明dts文件中如何定義保留內(nèi)存。

reserved-memory {
        #address-cells = < 1 >;
        #size-cells = < 1 >;
        ranges;

        /* Chipselect 3 is physically at 0x4c000000 */
        vram: vram@4c000000 {
                /* 8 MB of designated video RAM */
                compatible = "shared-dma-pool";
                reg = < 0x4c000000 0x00800000 >;
                no-map;
        };
};

保留內(nèi)存由根節(jié)點和1個或多個子結(jié)點組成。

根節(jié)點包括如下信息:

  • #address-cells、#size-cells

    必須項,需要同dts根節(jié)點中相關(guān)屬性保持一致。

/dts-v1/;
#include "vexpress-v2m.dtsi"

/ {
        model = "V2P-CA9";
        arm,hbi = < 0x191 >;
        arm,vexpress,site = < 0xf >;
        compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
        interrupt-parent = < &gic >;
        #address-cells = < 1 >;
        #size-cells = < 1 >;
...
  • ranges

    必須項,且定義為空

子結(jié)點包括如下信息:

  • 空間大小

    可以通過regsize來指定保留內(nèi)存空間大小,若二者同時存在,以reg屬性為準。通過size的方式如下:

reserved-memory {
        #address-cells = < 1 >;
        #size-cells = < 1 >;
        ranges;

        mfc_left: region_mfc_left {
                compatible = "shared-dma-pool";
                no-map;
                size = < 0x2400000 >;
...
  • alignment

    可選項

  • alloc-ranges

    可選項,通??梢院蛃ize同時使用。

reserved-memory {
        #address-cells = < 1 >;
        #size-cells = < 1 >;
        ranges;
        default-pool {
                compatible = "shared-dma-pool";
                size = < 0x6000000 >;
                alloc-ranges = < 0x40000000 0x10000000 >;
...
  • compatible

    可能包括shared-dma-pool或者shared-dma-pool。

    主要關(guān)注shared-dma-pool,當驅(qū)動程序需要申請DMA空間時,可以從這里進行申請內(nèi)存空間。

  • no-map

    該屬性意為不會為這段內(nèi)存創(chuàng)建地址映射,在使用之前,需要調(diào)用者通過ioremap創(chuàng)建頁表映射關(guān)系才可以正常訪問。這個屬性與reusable是互斥的。

  • no-map-fixup

    保持內(nèi)存映射。

  • reusable

    當驅(qū)動程序不使用這些內(nèi)存的時候,OS可以使用這些內(nèi)存。

  • linux,cma-default

    定義該段保留內(nèi)存空間是默認的CMA內(nèi)存池。

reserved-memory {
        #address-cells = < 1 >;
        #size-cells = < 1 >;
        ranges;
        default-pool {
                compatible = "shared-dma-pool";
                size = < 0x6000000 >;
                alloc-ranges = < 0x40000000 0x10000000 >;
                reusable;
                linux,cma-default;
        };
};

4. 保留內(nèi)存的使用

4.1 設(shè)備樹編碼

定義保留內(nèi)存:

memory@60000000 {
        device_type = "memory";
        reg = < 0x60000000 0x40000000 >;
};

reserved-memory {
        #address-cells = < 1 >;
        #size-cells = < 1 >;
        ranges;

        /* test reserve memory */
        test_reserve: test_reserve@90000000 {
                /* 1 MB reserve memory */
                compatible = "shared-dma-pool";
                reg = < 0x90000000 0x00100000 >;
                no-map;
        };
};

定義memory-region,將保留內(nèi)存指定給特定設(shè)備,如下:

driver-test@8000 {
        /* compatible = "test_driver_0", "simple_bus"; */
        compatible = "test_driver_0";
        reg = < 0x80008000 0x1000 >;

        interrupt-parent = < &gic >;
        interrupts= < 0 89 4 >, < 0 90 4444 >;
        interrupt-names = "first_irq", "second_irq";

        clocks = < &oscclk2 >;
        clock-names = "apb_pclk";

        memory-region = < &test_reserve >;

        status = "okay";

        simple_bus_test{
            compatile = "simple_bus_test";
        };
};

加載kernel后,保留內(nèi)存相關(guān)的打印信息如下:

Reserved memory: created DMA memory pool at 0x90000000, size 1 MiB
OF: reserved mem: initialized node test_reserve@90000000, compatible id shared-dma-pool

4.2 驅(qū)動程序編碼

驅(qū)動程序中調(diào)用of_reserved_mem_device_init()申請保留內(nèi)存空間。

static inline int of_reserved_mem_device_init(struct device *dev)
{
 return of_reserved_mem_device_init_by_idx(dev, dev- >of_node, 0);
}

主體函數(shù)是of_reserved_mem_device_init_by_idx()

int of_reserved_mem_device_init_by_idx(struct device *dev,
           struct device_node *np, int idx)
{
 struct rmem_assigned_device *rd;
 struct device_node *target;
 struct reserved_mem *rmem;
 int ret;

 if (!np || !dev)
  return -EINVAL;

 target = of_parse_phandle(np, "memory-region", idx);
 if (!target)
  return -ENODEV;

 if (!of_device_is_available(target)) {
  of_node_put(target);
  return 0;
 }

 rmem = __find_rmem(target);
 of_node_put(target);

 if (!rmem || !rmem- >ops || !rmem- >ops- >device_init)
  return -EINVAL;

 rd = kmalloc(sizeof(struct rmem_assigned_device), GFP_KERNEL);
 if (!rd)
  return -ENOMEM;

 ret = rmem- >ops- >device_init(rmem, dev);
 if (ret == 0) {
  rd- >dev = dev;
  rd- >rmem = rmem;

  mutex_lock(&of_rmem_assigned_device_mutex);
  list_add(&rd- >list, &of_rmem_assigned_device_list);
  mutex_unlock(&of_rmem_assigned_device_mutex);

  dev_info(dev, "assigned reserved memory node %s\\n", rmem- >name);
 } else {
  kfree(rd);
 }

 return ret;
}

然后可以通過dma_alloc_coherent()在保留內(nèi)存空間申請DMA空間。

void *dma_vaddr;
dma_addr_t dma_handler;

/* Start: test reserve memory */
ret = of_reserved_mem_device_init(&pdev- >dev);
if (!ret) {
        dev_info(&pdev- >dev, "using device-specific reserved memory\\n");
}

dma_set_coherent_mask(&pdev- >dev, 0xFFFFFFFF);
dma_vaddr = dma_alloc_coherent(&pdev- >dev, 64*1024, &dma_handler, GFP_KERNEL);
if (!dma_vaddr) {
        pr_notice("DMA allocation failed\\n");
        return false;
}
dev_info(&pdev- >dev, "DMA alloc phy addr 0x%X\\n", (u32)dma_handler);
/* End: test reserve memory */

執(zhí)行結(jié)果:

test_driver ahb:driver-test@8000: assigned reserved memory node test_reserve@90000000
test_driver ahb:driver-test@8000: using device-specific reserved memory
test_driver ahb:driver-test@8000: DMA alloc phy addr 0x90000000

結(jié)果表明,DMA申請的內(nèi)存落于保留內(nèi)存空間0x90000000-0x90100000。

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

    關(guān)注

    68

    文章

    19038

    瀏覽量

    228458
  • DDR
    DDR
    +關(guān)注

    關(guān)注

    11

    文章

    701

    瀏覽量

    65096
  • Linux系統(tǒng)
    +關(guān)注

    關(guān)注

    4

    文章

    588

    瀏覽量

    27266
  • CMA
    CMA
    +關(guān)注

    關(guān)注

    0

    文章

    26

    瀏覽量

    9781
  • 樹莓派
    +關(guān)注

    關(guān)注

    116

    文章

    1683

    瀏覽量

    105396
收藏 人收藏

    評論

    相關(guān)推薦

    一文解析Linux系統(tǒng)保留內(nèi)存初始化流程

    1、Linux系統(tǒng)保留內(nèi)存初始化流程在啟動過程中會打印出如下信息,這些信息為linux呈現(xiàn)出系統(tǒng)下的
    發(fā)表于 06-30 16:27

    手機模塊初始化向?qū)?/a>

    手機模塊初始化向?qū)?為了剛好的對手機模塊進行初始化,所以把最基本的向?qū)懴聛?本向?qū)нm用于本公司的西門子TC35I和華為GT9000模塊。一、在初始化手機模塊前,請先確定DT
    發(fā)表于 09-18 09:41 ?17次下載

    LINUX系統(tǒng)引導和初始化-LINUX內(nèi)核解讀

    Linux 的系統(tǒng)引導和初始化 ----------Linux2.4.22內(nèi)核解讀之一 一、 系統(tǒng)引導和初始化概述 相關(guān)代碼(引導扇區(qū)的程序及其輔助程序,以 x86體系為例): \
    發(fā)表于 11-03 22:31 ?53次下載

    RDA1846S初始化設(shè)置

    RDA1846S初始化設(shè)置RDA1846S初始化設(shè)置RDA1846S初始化設(shè)置
    發(fā)表于 01-15 17:08 ?0次下載

    UCOS_III_配置與初始化

    UCOS_III_配置與初始化
    發(fā)表于 12-20 22:53 ?5次下載

    Linux內(nèi)存初始化

    之前有幾篇博客詳細介紹了Xen的內(nèi)存初始化,確實感覺這部分內(nèi)容蠻復雜的。這兩天在看Linux內(nèi)核啟動中內(nèi)存初始化,也是看的云里霧里的,想嘗
    發(fā)表于 10-12 11:16 ?0次下載

    解析內(nèi)核初始化時根內(nèi)存盤的加載過程

    2006-12-12 13:54:41 來源:Linux 寶庫 分享到:標簽:loadlin gzip 作者:opera 概述 ==== 1)當內(nèi)核配置了內(nèi)存盤時, 內(nèi)核在初始化時可以將軟盤加載
    發(fā)表于 11-08 10:40 ?0次下載

    8253初始化程序分享_8253應用案例

    本文首先介紹了8253概念及8253各通道的工作方式,其次詳細介紹了8253初始化要求及編程,最后用一個例子介紹了8253的初始化程序。
    發(fā)表于 05-23 15:52 ?2.2w次閱讀
    8253<b class='flag-5'>初始化</b>程序分享_8253應用案例

    Linux內(nèi)核初始化過程中的調(diào)用順序

    所有的__init函數(shù)在區(qū)段.initcall.init中還保存了一份函數(shù)指針,在初始化時內(nèi)核會通過這些函數(shù)指針調(diào)用這些__init函數(shù)指針,并在整個初始化完成后,釋放整個init區(qū)段(包括.init.text,.initcall.init等)。
    發(fā)表于 05-12 08:40 ?1588次閱讀

    在51平臺下初始化文件的引入導致全局變量無法初始化的問題如何解決

    本文檔的主要內(nèi)容詳細介紹的是在51平臺下初始化文件的引入導致全局變量無法初始化的問題如何解決。
    發(fā)表于 08-20 17:31 ?0次下載
    在51平臺下<b class='flag-5'>初始化</b>文件的引入導致全局變量無法<b class='flag-5'>初始化</b>的問題如何解決

    C++之初始化列表學習的總結(jié)

    類中可以使用初始化列表對成員進行初始化
    的頭像 發(fā)表于 12-24 17:39 ?783次閱讀

    Linux內(nèi)存方面的初始化和常見的內(nèi)存分配方式

    | --- >mem_init linux4.14/init/main.c 在 mem_init 函數(shù)中會初始化伙伴系統(tǒng)和 slab 分配器。 先說兩個概念: 外部碎片 :有一段小內(nèi)存,夾在兩個大
    的頭像 發(fā)表于 09-28 16:13 ?719次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)存</b>方面的<b class='flag-5'>初始化</b>和常見的<b class='flag-5'>內(nèi)存</b>分配方式

    Linux終端初始化和tty驅(qū)動框架

    ,是難以想象的,我們自己寫的代碼要在多少個地方聲明。 而你如果采用initcall機制,意思就是說,你使用一個字符串聲明你的驅(qū)動初始化函數(shù),那么所有的驅(qū)動初始化函數(shù)都存在內(nèi)存中一個連續(xù)的段中,系統(tǒng)啟動以后,會從這個段的第一個函數(shù)
    的頭像 發(fā)表于 09-28 16:33 ?628次閱讀
    <b class='flag-5'>Linux</b>終端<b class='flag-5'>初始化</b>和tty驅(qū)動框架

    實戰(zhàn)經(jīng)驗 | Keil、IAR、CubeIDE 中變量不被初始化方法

    程中要求變量有連續(xù)性,或者現(xiàn)場保留,例如 Bootloader 跳轉(zhuǎn),某種原因的復位過程中我們有些關(guān)鍵變量不能被初始化,在不同的編譯環(huán)境下有不同的設(shè)置,本文就這個操作做總結(jié),分別介紹使用 Keil
    的頭像 發(fā)表于 11-24 18:05 ?3519次閱讀

    西門子博途示例:在塊上設(shè)置內(nèi)存保留

    下表描述了如何為下載設(shè)置內(nèi)存保留而不重新初始化。
    的頭像 發(fā)表于 01-15 10:42 ?636次閱讀
    西門子博途示例:在塊上設(shè)置<b class='flag-5'>內(nèi)存</b><b class='flag-5'>保留</b>