• 内存管理初始化源码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 是 "224M@0x0"
    * 第二次:p 是 "256M@0x30000000"
    * 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: "); strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE); strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
    // arcs_cmdline : console=tty3,115200n8 mem=224M@0x0 mem=256M@0x30000000 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:
    ");
            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 
    	"
            "mtc0  $2, $5, 4      
    	"
            "nop                  
    	"
            ::"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)
    ");
                break;
            case BOOT_MEM_ROM_DATA:
                printk(KERN_CONT "(ROM data)
    ");
                break;
            case BOOT_MEM_RESERVED:
                printk(KERN_CONT "(reserved)
    ");
                break;
            default:
                printk(KERN_CONT "type %lu
    ", 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之后的几个调用,具体分析见下篇文章

  • 相关阅读:
    第二十二章 CLR寄宿和AppDomain
    第二十一章 托管堆和垃圾回收
    第二十章 异常和状态管理
    第十九章 可空值类型
    第十八章 定制特性
    第十七章 委托
    第十六章 数组
    第十五章 枚举类型和位标志
    html标签溢出问题
    Java 路径问题
  • 原文地址:https://www.cnblogs.com/ronnydm/p/5885588.html
Copyright © 2020-2023  润新知