内存管理初始化源码1:setup_arch

Posted 若离相惜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存管理初始化源码1:setup_arch相关的知识,希望对你有一定的参考价值。

源码声明:基于Linux kernel 3.08

1. 在kernel/arch/mips/kernel/head.S中会做一些特定硬件相关的初始化,然后会调用内核启动函数:start_kernel;

2. start_kernel是通用的内核启动函数,但是在初始化内核过程中,必然有一些参数是特定于硬件体系结构的,这些特定于硬件体系结构的设置通过调用函数setup_arch函数;

3. 我们看看MIPS架构的setup_arch函数做了哪些特定于MIPS的设置:

/* kernel/arch/mips/kernel/setup.c */
void __init setup_arch(char **cmdline_p)
{
    cpu_probe();
    prom_init();

#ifdef CONFIG_EARLY_PRINTK
    setup_early_printk();
#endif
    cpu_report();
    check_bugs_early();

#if defined(CONFIG_VT)
#if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif

   /* 这个是内存管理相关的,我们只关注该函数,其他的看看函数名,意淫一下就可以了 */
    arch_mem_init(cmdline_p);

    resource_init();
    plat_smp_setup();
}

4. arch_mem_init

/* 这么关键的注释,一定要仔细阅读 */
/*
* arch_mem_init - initialize memory management subsystem(初始化 memory management subsystem) * * o plat_mem_setup() detects the memory configuration and will record detected * memory areas using add_memory_region.
* plat_mem_setup():探测memory配置,然后使用add_memory_region记录探测到的内存区域【低端内存,高端内存,DMA的区域】。 * * At this stage the memory configuration of the system is known to the * kernel but generic memory management system is still entirely uninitialized.
* 此时,kernel知道memory的配置,但是generic memory management sysetm依然没有被初始化。 * * o bootmem_init() * o sparse_init() * o paging_init() * * At this stage the bootmem allocator is ready to use.
* 此时,bootmem allocator可以使用了。 * * NOTE: historically plat_mem_setup did the entire platform initialization. * This was rather impractical because it meant plat_mem_setup had to * get away without any kind of memory allocator. To keep old code from * breaking plat_setup was just renamed to plat_setup and a second platform * initialization hook for anything else was introduced.
*
* NOTE:从历史上看,plat_mem_setup完成整个platform的初始化。
* 这是不切实际啊的,因为这意味着plat_mem_setup必须在不使用memory allocator时完成所有工作。......
*/ static int usermem __initdata; static int __init early_parse_mem(char *p) { unsigned long start, size;

  /* 该函数会被调用2次:
* 第一次:p 是 "[email protected]"
* 第二次:p 是 "[email protected]"
* early_parse_mem的参数是如何传递的需要深入了解两个函数:early_parm和parse_eraly_param,这个还是有些复杂,我们就不去深入了解了。但是我们可以大体猜测出这些函数的目的,就是:
* 从commandline中根据关键字"mem"提取物理内存的信息,即起始地址和大小。
* 这里我们看到该函数被调用两次,是由于我们的command line包含两个"mem"信息,这是由于我们的板子使用是4+8的EMCP,那么RAM大小是512M,刚好是两个256M。
   *  第一个变成了224M,可能是由于RAM不是精确的512M,也可能是uboot占用了一部分。具体就不太了解了。
*/
/* * If a user specifies memory size, we * blow away any automatically generated * size. */ if (usermem == 0) { boot_mem_map.nr_map = 0; usermem = 1; } start = 0; size = memparse(p, &p); if (*p == @) start = memparse(p + 1, &p); // 解析”mem“字符串,提取memory信息 add_memory_region(start, size, BOOT_MEM_RAM); // 记录memory信息,就是对boot_mem_map结构体的赋值,代码我就不粘了 return 0; } early_param("mem", early_parse_mem); #ifdef CONFIG_android_PMEM #ifdef CONFIG_SOC_4775 //unsigned long start, size; static unsigned long g_pmem_total_size=0; static unsigned long g_pmem_start=0; /* called in arch/mips/xburst/soc-4775/common/setup.c */ unsigned long set_reserved_pmem_total_size(unsigned long size) { g_pmem_total_size = size; return 0; } unsigned long get_reserved_pmem_size(void) { return g_pmem_total_size; } unsigned long get_reserved_pmem_start(void) { return g_pmem_start; } #endif /* CONFIG_SOC_4775 */ #endif /* CONFIG_ANDROID_PMEM */ static void __init arch_mem_init(char **cmdline_p) { extern void plat_mem_setup(void); /* call board setup routine */ plat_mem_setup(); pr_info("Determined physical RAM map:\n"); strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE); strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
// arcs_cmdline : console=tty3,115200n8 [email protected] [email protected] ip=off root=/dev/ram0 rw rdinit=/init rd_start=0x81A00000 rd_size=0x0012E720
// 这个command line是由uboot传递过来的一些启动参数,具体如何过来的不再深入追了,最后kernel会从这里提取内存信息,串口信息,其他信息(不懂)
*cmdline_p = command_line;

    parse_early_param();  // 内核一种看起来很牛逼的函数调用,具体流程忽略,最后结果就是调到了:early_parse_mem

    if (usermem) {
        pr_info("User-defined physical RAM map:\n");
        print_memory_map(); // 打印内存信息,如下

  /*
    memory: 0e000000 @ 00000000 (usable)
   memory: 10000000 @ 30000000 (usable)

    第一个字段是是size,第二个字段是起始地址
  */
bootmem_init(); device_tree_init(); sparse_init(); plat_swiotlb_setup(); paging_init(); }

 

5. plat_mem_setup

  该函数是针对每种不同的CPU进行的配置。/* kernel/arch/mips/xburst/soc-m200/common/setup.c *//* 针对m200这种mips架构的CPU,进行的配置 */


void __init plat_mem_setup(void)
{
    /* jz mips cpu special */
    __asm__ (
        "li    $2, 0xa9000000 \n\t"
        "mtc0  $2, $5, 4      \n\t"
        "nop                  \n\t"
        ::"r"(2));

    /* use IO_BASE, so that we can use phy addr on hard manual
     * directly with in(bwlq)/out(bwlq) in io.h.
     */
    set_io_port_base(IO_BASE);      // kernel/arch/mips/include/asm/mach-generic/spaces.h: #define IO_BASE _AC(0xa0000000, UL)

  /*
    kernel/kernel/resouce.c
    struct resource ioport_resource = {
      .name = "PCI_IO",
      .start = 0,
      .end = IO_SPACE_LIMIT,
      .flags = IORESOURCE_IO,
    };
    EXPORT_SYMBOL(ioport_resource);
  */
  // 记录 io resource ioport_resource.start
= 0x00000000; ioport_resource.end = 0xffffffff; iomem_resource.start = 0x00000000; iomem_resource.end = 0xffffffff; setup_init(); // 一些硬件相关的初始化 init_all_clk(); // 初始化所有的始终。如:CCLK:1200MHZ L2CLK:300MHZ H0CLK:200MHZ H2CLK:200MHz PCLK:100MHZ #ifdef CONFIG_ANDROID_PMEM /* reserve memory for pmem. */ board_pmem_setup(); #endif return; }

   set_io_port_base:

/*
 * Gcc will generate code to load the value of mips_io_port_base after each
 * function call which may be fairly wasteful in some cases.  So we don‘t
 * play quite by the book.  We tell gcc mips_io_port_base is a long variable
 * which solves the code generation issue.  Now we need to violate the
 * aliasing rules a little to make initialization possible and finally we
 * will need the barrier() to fight side effects of the aliasing chat.
 * This trickery will eventually collapse under gcc‘s optimizer.  Oh well.
 */
/*
* GCC会在每个函数的调用之后,产生Code来加载mips_io_port_base的值,这有时完全是浪费的。因此,我们不完全按照手册。我们告诉gcc,mips_io_port_base是一个长整型变量,这将会解决code generation问题。
* 现在,我们需要违反aliasing(别名)规则,来完成初始化,最后我们需要barrier()来fight side effects of the aliasing chat.
* 这种欺诈行为最终将会随着gcc的优化而崩塌。Oh well。
*/
static inline void set_io_port_base(unsigned long base) { * (unsigned long *) &mips_io_port_base = base; // 简单地赋值语句 barrier(); // 内存屏障,下边我们简单看看 }
/* kernel/include/linux/compiler-gcc.h */

/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")

/*
* 1. __asm__用于指示编译器在此插入汇编语句
* 2. __volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
* 3. memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
* 4. "":::表示这是个空指令。
*/

 

6. print_memory_map

  再次回顾我们的主线:start_kernel—>setup_arch—>arch_mem_init。

  刚才我们分析了arch_mem_init调用的第一个函数:plat_mem_setup。

    该函数就是和具体的CPU相关,该函数做了这么几件事:1. set_io_port_base: mips_io_port_base = 0xa0000000.

                             2. 记录io resource.

                               3. 一些硬件初始化和始终初始化

  接着,我们继续看arch_mem_init调用的第二个函数:print_memory_map。

  很明显,这个函数是打印当前的内存信息,但是我们也详细去看看,因为内部有很重要的数据结构。

static void __init print_memory_map(void)
{
    int i;
    const int field = 2 * sizeof(unsigned long);

    for (i = 0; i < boot_mem_map.nr_map; i++) {    // boot_mem_map:是一个类型为struct boot_mem_map的全局变量
        printk(KERN_INFO " memory: %0*Lx @ %0*Lx ",
               field, (unsigned long long) boot_mem_map.map[i].size,
               field, (unsigned long long) boot_mem_map.map[i].addr);

        switch (boot_mem_map.map[i].type) {
        case BOOT_MEM_RAM:
            printk(KERN_CONT "(usable)\n");
            break;
        case BOOT_MEM_ROM_DATA:
            printk(KERN_CONT "(ROM data)\n");
            break;
        case BOOT_MEM_RESERVED:
            printk(KERN_CONT "(reserved)\n");
            break;
        default:
            printk(KERN_CONT "type %lu\n", boot_mem_map.map[i].type);
            break;
        }
    }
}

  没错,我们说的重要的数据结构就是:boot_mem_map.

/* kernel/arch/mips/include/asm/bootinfo.h */
/*
 * A memory map that‘s built upon what was determined
 * or specified on the command line.
 */
struct boot_mem_map {
    int nr_map;
    struct boot_mem_map_entry {
        phys_t addr;    /* start of memory segment */
        phys_t size;    /* size of memory segment */
        long type;        /* type of memory segment */
    } map[BOOT_MEM_MAP_MAX];
};

 

7. 在arch_mem_init我们还有bootmem_init之后的几个调用,具体分析见下篇文章

以上是关于内存管理初始化源码1:setup_arch的主要内容,如果未能解决你的问题,请参考以下文章

内存管理初始化源码3:bootmem

内存管理初始化源码5:free_area_init_nodes

启动期间的内存管理之pagging_init初始化分页机制--Linux内存管理(十四)

内存管理初始化源码4:add_active_range

Linux 内核 内存管理虚拟地址空间布局架构 ② ( 用户虚拟地址空间组成 | 内存描述符 mm_struct 结构体源码 )

Linux 内核 内存管理Linux 内核内存布局 ④ ( ARM64 架构体系内存分布 | 内核启动源码 start_kernel | 内存初始化 mm_init | mem_init )