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

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

3天內不再提示

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

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

1. 概述

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

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

本文只介紹基本的保留內存,不涉及CMA部分內容

保留內存的初始化流程如下圖所示:

圖片

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

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

2. 保留內存初始化流程

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

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

2.1 解析內核中的保留內存空間

在各平臺初始化過程中調用early_init_fdt_scan_reserved_mem()進行保留內存的初始化。

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中,需要內核配置打開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()解析設備樹中的/memreserve/fields,例如樹莓派處理器的設備樹中定義了該屬性,通常來講,這部分內存區(qū)域是存放和rom或者多核啟動相關的程序,需要注意的是內核無法使用這部分內存。這是和reserver memory的區(qū)別。

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

若在設備樹中查找到了memreserve且未進行映射,則通過memblock_reserve()將這部分內存區(qū)域加入到memblock.reserved,當進行memblock到buddy轉換時,釋放掉memblock.reserved所標記的內存區(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()解析設備樹中保留內存相關的結點信息。

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ù)首先解析設備樹中reserved-memory結點并確認是否有效。若有效,繼續(xù)檢查regsize屬性定義的內存區(qū)域,通過fdt_reserved_mem_save_node()將內存信息更新到數(shù)據(jù)結構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 保留內存初始化

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

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

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ù)結構struct reserved_mem獲取保留內存的信息。

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. 設備樹中保留內存的定義方式

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

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;
        };
};

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

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

  • #address-cells、#size-cells

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

/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

    必須項,且定義為空

子結點包括如下信息:

  • 空間大小

    可以通過regsize來指定保留內存空間大小,若二者同時存在,以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

    可選項,通常可以和size同時使用。

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。

    主要關注shared-dma-pool,當驅動程序需要申請DMA空間時,可以從這里進行申請內存空間。

  • no-map

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

  • no-map-fixup

    保持內存映射。

  • reusable

    當驅動程序不使用這些內存的時候,OS可以使用這些內存。

  • linux,cma-default

    定義該段保留內存空間是默認的CMA內存池。

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. 保留內存的使用

4.1 設備樹編碼

定義保留內存:

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,將保留內存指定給特定設備,如下:

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后,保留內存相關的打印信息如下:

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 驅動程序編碼

驅動程序中調用of_reserved_mem_device_init()申請保留內存空間。

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()在保留內存空間申請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í)行結果:

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

結果表明,DMA申請的內存落于保留內存空間0x90000000-0x90100000。

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

    關注

    68

    文章

    19100

    瀏覽量

    228815
  • DDR
    DDR
    +關注

    關注

    11

    文章

    704

    瀏覽量

    65163
  • Linux系統(tǒng)

    關注

    4

    文章

    590

    瀏覽量

    27317
  • CMA
    CMA
    +關注

    關注

    0

    文章

    26

    瀏覽量

    9784
  • 樹莓派
    +關注

    關注

    116

    文章

    1688

    瀏覽量

    105456
收藏 人收藏

    評論

    相關推薦

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

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

    手機模塊初始化向導

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

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

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

    RDA1846S初始化設置

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

    UCOS_III_配置與初始化

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

    Linux內存初始化

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

    解析內核初始化時根內存盤的加載過程

    2006-12-12 13:54:41 來源:Linux 寶庫 分享到:標簽:loadlin gzip 作者:opera 概述 ==== 1)當內核配置了內存盤時, 內核在初始化時可以將軟盤加載
    發(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內核初始化過程中的調用順序

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

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

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

    C++之初始化列表學習的總結

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

    Linux內存方面的初始化和常見的內存分配方式

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

    Linux終端初始化和tty驅動框架

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

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

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

    西門子博途示例:在塊上設置內存保留

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