close
uClinux初始化
第零個文件 arch/armnommu/Makefile
在這個文件中定義了你編譯好的內核的入口的地址和MACHINE類型,一般是通過條件編譯
TEXTADDR     = 0x10008000   這個值完全是根據你的cpu和內存狀況確定的
MACHINE      = your_processor

指定連接文件
LINKFLAGS :=-p -X -T arch/armnommu/vmlinux.lds

替換vmlinux-$(PROCESSOR).lds.in中的TEXTADDR,生成實際需要的連接腳本
arch/armnommu/vmlinux.lds: arch/armnommu/vmlinux-$(PROCESSOR).lds.in dummy
ifeq ($(CONFIG_ARCH_DSC21),y)
 @sed 's/TEXTADDR/$(TEXTADDR)/' <$< >tmp.ld
 @sed 's/DATAADDR/$(DATAADDR)/' <tmp.ld >$@
 $(RM) tmp.ld
else
 @sed 's/TEXTADDR/$(TEXTADDR)/' <$< >$@ 
endif

一般連接腳本的前面幾句是這樣的
ENTRY(stext)---------指定入口
SECTIONS
{
 . = $(TEXTADDR);-----設定入口的絕對地址


第一個文件linux/arch/armnommu/kernel/head-armv.S

#define K(a,b,c) ((a) << 24 | (b) << 12 | (c))


#ifndef CONFIG_UCLINUX
設置符號swapper_pg_dir的地址
  .globl SYMBOL_NAME(swapper_pg_dir)
  .equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000 
定義了一個宏,該宏可以獲得swapper_pg_dir的地址
  .macro pgtbl, reg, rambase
  adr \reg, stext
  sub \reg, \reg, #0x4000
  .endm

/*
 * Since the page table is closely related to the kernel start address, we
 * can convert the page table base address to the base address of the section
 * containing both.
 */
  .macro krnladr, rd, pgtable, rambase
  bic \rd, \pgtable, #0x000ff000
  .endm
#endif

/*
 *  Kernel startup entry point.
 *
 * The rules are:
 *  r0      - should be 0
 *  r1      - unique architecture number
 *  MMU     - off
 *  I-cache - on or off
 *  D-cache - off
 *
 * See linux/arch/arm/tools/mach-types for the complete list of numbers
 * for r1.
 */
.section ".text.init",#alloc,#execinstr
  .type stext, #function 
  
  請注意,這裡是入口點,而且此時r1中應該保存唯一的一個architecture number
ENTRY(stext)

保存r0寄存器的值?????
  mov r12, r0
 
 靠~~Rebel.COM  NetWinder是什麼東東,這個條件編譯可以不予理會
 #if defined(CONFIG_ARCH_NETWINDER)
。。。。。
#endif


#if defined(CONFIG_ARCH_L7200)
  mov r1, #MACH_TYPE_L7200
#elif defined(CONFIG_ARCH_INTEGRATOR)
  mov r1, #MACH_TYPE_INTEGRATOR
#elif defined(CONFIG_ARCH_P52)
  mov r1, #MACH_TYPE_P52
#elif defined(CONFIG_ARCH_SWARM)
  mov r1, #MACH_TYPE_SWARM
#elif defined(CONFIG_ARCH_SAMSUNG)
  mov r1, #MACH_TYPE_SAMSUNG
#endif
前面預定義了部分的ARCH,如果匹配的上,R1中包含了architecture number

其實系統復位之後,ARM就處於SVC 模式,而且禁止了FIQ IRQ, 的確如注視所說--make sure
  mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
  msr cpsr_c, r0   @ and all irqs disabled

使用的是atmel的arm處理器嗎???跳過去吧
#if defined(CONFIG_ARCH_ATMEL)
。。。。。
#endif
又來,使用的是samsumg的arm處理器嗎???跳過去吧
#if defined(CONFIG_ARCH_SAMSUNG)
。。。。。
#endif

重點還是分析通用的arm處理器的內容吧
  bl __lookup_processor_type ----尋找處理器類型,R10中保存指向表示處理器類型結構的指針
  teq r10, #0    @ invalid processor?  ----R10 == 0????靠,發生錯誤
  moveq r0, #'p'   @ yes, error 'p'
  beq __error
  
  bl __lookup_architecture_type----尋找體系結構類型, R7中保存指向表示體系結構類型結構的指針
  teq r7, #0    @ invalid architecture?----好像不用說什麼了吧
  moveq r0, #'a'   @ yes, error 'a'
  beq __error
  
跳過,我們一般是會定義CONFIG_UCLINUX這個宏的
__create_page_tables函數建立一個初始化階段的頁表,映射4M內存,當然足夠了
#ifndef CONFIG_UCLINUX
  bl __create_page_tables
#endif
  adr lr, __ret   @ return address
看看你的proc_info_list結構,第三個成員變量是一個跳轉指令,進行一些CPU初始化的操作
返回則跳到__ret
  add pc, r10, #12   @ initialise processor
       @ (return control reg)

__switch_data: .long __mmap_switched
  .long SYMBOL_NAME(compat)
  .long SYMBOL_NAME(__bss_start)
  .long SYMBOL_NAME(_end)
  .long SYMBOL_NAME(processor_id)
  .long SYMBOL_NAME(__machine_arch_type)
  .long SYMBOL_NAME(cr_alignment)
  .long SYMBOL_NAME(init_task_union)+8192

__ret:  ldr lr, __switch_data
執行__switch_data
  mov pc, lr

  /*
   * This code follows on after the page
   * table switch and jump above.
   *
   * r0  = processor control register
   * r1  = machine ID
   * r9  = processor ID
   */
  .align 5
在這個函數中,主要作用是清bss,設置sp到(init_task_union)+8192,
給compat  processor_id  __machine_arch_type  __machine_arch_type  cr_alignment  賦值,
後來,在setup_processor和setup_architecture中需要用到這些變量。
__mmap_switched:
  adr r3, __switch_data + 4
  ldmia r3, {r2, r4, r5, r6, r7, r8, sp}@ r2 = compat
                             @ sp = stack pointer
  str r12, [r2]

  mov fp, #0    @ Clear BSS (and zero fp)
1:  cmp r4, r5
  strcc fp, [r4],#4
  bcc 1b

  str r9, [r6]   @ Save processor ID
  str r1, [r7]   @ Save machine type
#ifdef CONFIG_ALIGNMENT_TRAP
  orr r0, r0, #2   @ ...........A.
#endif
  bic r2, r0, #2   @ Clear 'A' bit
  stmia r8, {r0, r2}   @ Save control register values
  b SYMBOL_NAME(start_kernel)   進入start_kernel



/*
 * Exception handling.  Something went wrong and we can't
 * proceed.  We ought to tell the user, but since we
 * don't have any guarantee that we're even running on
 * the right architecture, we do virtually nothing.
 * r0 = ascii error character:
 * a = invalid architecture
 * p = invalid processor
 * i = invalid calling convention
 *
 * Generally, only serious errors cause this.
 */
 對出錯的處理,打印消息,進入死循環
__error:
#ifdef CONFIG_DEBUG_LL
  mov r8, r0    @ preserve r0
  adr r0, err_str
  bl printascii
  mov r0, r8
  bl printch
#endif

1:  mov r0, r0
  b 1b

#ifdef CONFIG_DEBUG_LL
err_str: .asciz "\nError: "
  .align
#endif

/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 * Returns:
 * r5, r6, r7 corrupted
 * r8  = page table flags
 * r9  = processor ID
 * r10 = pointer to processor structure
 */
在編譯生成的內核影像的.proc.info段中保存了處理器類型的信息
下面的代碼是在..proc.info段中尋找proc_info_list結構
__lookup_processor_type:
  adr r5, 2f
  ldmia r5, {r7, r9, r10}
  sub r5, r5, r10   @ convert addresses 調整地址
  add r7, r7, r5   @ to our address space
  add r10, r9, r5

r10-----proc_info_list first addr
r7------proc_info_list end addr
找到合適的proc_info_list,則R10指向了該結構

1:  ldmia r10, {r5, r6, r8}  @ value, mask, mmuflags
  and r6, r6, r9           @ mask wanted bits
  teq r5, r6
  moveq pc, lr-------------找到了適當的proc_info_list返回
  add r10, r10, #36   @ sizeof(proc_info_list) 下一個proc_info_list
  cmp r10, r7                      是否到達結尾
  blt 1b
  mov r10, #0    @ unknown processor
  mov pc, lr
  

/*
 * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
 * more information about the __proc_info and __arch_info structures.
 */
2:  .long __proc_info_end           .proc.info段的END
  .long __proc_info_begin       .proc.info段的BEGIN
  .long 2b
  .long __arch_info_begin
  .long __arch_info_end

/*
 * Lookup machine architecture in the linker-build list of architectures.
 * Note that we can't use the absolute addresses for the __arch_info
 * lists since we aren't running with the MMU on (and therefore, we are
 * not in the correct address space).  We have to calculate the offset.
 *
 *  r1 = machine architecture number
 * Returns:
 *  r2, r3, r4 corrupted
 *  r5 = physical start address of RAM
 *  r6 = physical address of IO
 *  r7 = byte offset into page tables for IO
 */
 尋找體系結構的類型
 
__lookup_architecture_type:
  adr r4, 2b
  ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3
  sub r5, r4, r5   @ convert addresses
  add r4, r6, r5   @ to our address space
  add r7, r7, r5
至此我們得到了.arch.info段的起始地址
r4-----__arch_info_begin
r7-----__arch_info_end
1:  ldr r5, [r4]   @ get machine type
  teq r5, r1       -----------architecture number是否匹配
  beq 2f
  add r4, r4, #SIZEOF_MACHINE_DESC
  cmp r4, r7
  blt 1b
  mov r7, #0    @ unknown architecture
  mov pc, lr-------------error 返回

找到了architecture number返回
2:  ldmib r4, {r5, r6, r7}  @ found, get results
  mov r7, r7, lsr #18   @ pagetable byte offset
  mov pc, lr

L_AT91_SF_CIDR: .long 0xfff00000

具體實現尋找體系結構其實和尋找cpu info 是一樣的,不過是信息是保存在了.arch.info段
 在文件linux/include/asm-arm/mach/arch.h中定義了如下結構
 struct machine_desc {
 /*
  * Note! The first four elements are used
  * by assembler code in head-armv.S
  */
 unsigned int  nr;  /* architecture number */
 unsigned int  phys_ram; /* start of physical ram */
 unsigned int  phys_io; /* start of physical io */
 unsigned int  virt_io; /* start of virtual io */

 const char  *name;  /* architecture name */
 unsigned int  param_offset; /* parameter page */

 unsigned int  video_start; /* start of video RAM */
 unsigned int  video_end; /* end of video RAM */

 unsigned int  reserve_lp0 :1; /* never has lp0 */
 unsigned int  reserve_lp1 :1; /* never has lp1 */
 unsigned int  reserve_lp2 :1; /* never has lp2 */
 unsigned int  soft_reboot :1; /* soft reboot  */
 const struct tagtable * tagtable; /* tag table  */
 int   tagsize; /* tag table size */
 void   (*fixup)(struct machine_desc *,
      struct param_struct *, char **,
      struct meminfo *);
 void   (*map_io)(void);/* IO mapping function */
 void   (*init_irq)(void);
};

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)  \
const struct machine_desc __mach_desc_##_type \
 __attribute__((__section__(".arch.info"))) = { \---------放入.arch.info段
 nr:  MACH_TYPE_##_type, \
 name:  _name,

#define MAINTAINER(n)

#define INITIRQ(_func)    \
 init_irq: _func,

而在linux/arch/arm/mach-***/arch.c文件中定義了你的machine_desc結構
當然我們這裡只是定義了machine_desc中的init_irq結構
MACHINE_START(ATMEL, "EB01")
       MAINTAINER("Erwin Authried")
       INITIRQ(genarch_init_irq)
MACHINE_END



第二個文件linux/include/asm-armnommu/procinfo.h

#ifndef __ASM_PROCINFO_H
#define __ASM_PROCINFO_H

#ifndef __ASSEMBLY__

#include <asm/proc-fns.h>

struct proc_info_item {
 const char  *manufacturer;
 const char  *cpu_name;
};


基本上,這是一個定義處理器類型的數據結構,你可以在arch/arm/mm目錄下建立關於自己processor的信息,
具體方法是,創建一個proc-myproc.S,當然這個文件修改自proc-arm*.S
struct proc_info_list {
 unsigned int  cpu_val;
 unsigned int  cpu_mask;
 unsigned long  __cpu_mmu_flags; /* used by head-armv.S */
 unsigned long  __cpu_flush;  /* used by head-armv.S */
 const char  *arch_name;
 const char  *elf_name;
 unsigned int  elf_hwcap;
 struct proc_info_item *info;
#ifdef MULTI_CPU
 struct processor *proc;
#else
 void   *unused;
#endif
};

#endif /* __ASSEMBLY__ */

#define HWCAP_SWP  1
#define HWCAP_HALF  2
#define HWCAP_THUMB  4
#define HWCAP_26BIT  8 /* Play it safe */
#define HWCAP_FAST_MULT  16
#define HWCAP_FPA        32
#define HWCAP_VFP        64
#define HWCAP_EDSP  128

#endif


第三個文件 linux/arch/armnommu/mm/proc-***.S
這個文件具體是和你的cpu相關的
在這個文件中定義了你的proc_info_list,
你可以定義若干,讓程序根據ID掃描,如果專門應用在特定的CPU上,也可以直接指定proc_info_list
  .align
  .section ".proc.info", #alloc, #execinstr

  .type __arm6_proc_info, #object
__arm6_proc_info:
  .long 0x41560600
  .long 0xfffffff0
  .long 0x00000c1e
  b __arm6_setup
  .long cpu_arch_name
  .long cpu_elf_name
  .long HWCAP_SWP | HWCAP_26BIT
  .long cpu_arm6_info
  .long arm6_processor_functions
  .size __arm6_proc_info, . - __arm6_proc_info

asmlinkage void __init start_kernel(void)
{
 char * command_line;
 extern char saved_command_line[];

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
 lock_kernel();
 printk(linux_banner);
 setup_arch(&command_line);
 printk("Kernel command line: %s\n", saved_command_line);
 parse_options(command_line);
 .....
 。。。
 
}
下面我們開始分析start_kernel這段代碼,主要是三個函數
/*
 * Getting the big kernel lock.
 *
 * This cannot happen asynchronously,
 * so we only need to worry about other
 * CPU's.
 */
extern __inline__ void lock_kernel(void)
{
 if (!++current->lock_depth)
  spin_lock(&kernel_flag);
}
kernel_flag是一個內核大自旋鎖,所有進程都通過這個大鎖來實現向內核態的遷移。
只有獲得這個大鎖的處理器可以進入內核(如中斷處理)。
這個函數相當簡單,它獲得全局內核鎖--在任何一對lock_kernel/unlock_kernel函數里至多可以有一個CPU。
進程的lock_depth成員初始化為-1(參見kerenl/fork.c)。在它小於0時(若小於0則恆為-l),進程不擁有內核鎖;當大於或等於0時,進程得到內核鎖。
對於單cpu,該函數為空


void __init setup_arch(char **cmdline_p)
{
 struct param_struct *params = NULL;
 struct machine_desc *mdesc;
 char *from = default_command_line;
 int bootmap_size;
 unsigned long memory_start = (unsigned long)&_end_kernel;
 
 ROOT_DEV = MKDEV(0, 255);

 setup_processor();
 在該函數中,主要通過
for (list = &__proc_info_begin; list < &__proc_info_end ; list++)
  if ((processor_id & list->cpu_mask) == list->cpu_val)
   break;
這樣一個循環來在..proc.info段中尋找匹配的processor_id,processor_id在前面的head_armv.S中設置

 mdesc = setup_architecture(machine_arch_type);//獲得體系結構的信息
 machine_name = mdesc->name;

 if (mdesc->soft_reboot)
  reboot_setup("s");

 if (mdesc->param_offset)
  params = (struct param_struct*)
   (phys_to_virt(mdesc->param_offset));

 /*
  * Do the machine-specific fixups before we parse the
  * parameters or tags.
  */
 if (mdesc->fixup)
  mdesc->fixup(mdesc, params, &from, &meminfo);

分析傳入的參數
 if (params) {
  struct tag *tag = (struct tag *)params;

  /*
   * Is the first tag the CORE tag?  This differentiates
   * between the tag list and the parameter table.
   */
  if (tag->hdr.tag == ATAG_CORE)
   parse_tags(mdesc->tagtable, mdesc->tagsize, tag);
  else
   parse_params(params);
 } 

meminfo結構表明了uclinux的內存情況
 if (meminfo.nr_banks == 0) {
  meminfo.nr_banks      = 1;
  meminfo.bank[0].start = PAGE_OFFSET;//PHYS_OFFSET;
  meminfo.bank[0].size  = MEM_SIZE;
 }
初始化為一個bank,起始地址PAGE_OFFSET,大小MEM_SIZE

 init_mm.start_code = (unsigned long) &_text;
 init_mm.end_code   = (unsigned long) &_etext;
 init_mm.end_data   = (unsigned long) &_edata;
 init_mm.brk    = (unsigned long) &_end;

 memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
 saved_command_line[COMMAND_LINE_SIZE-1] = '\0';
如果命令行中有參數mem=size@start,那麼我們將更新meminfo,之後,cmdline_p  ==   "root=/dev/rom0"
 parse_cmdline(&meminfo, cmdline_p, from);


 bootmem_init(&meminfo); 初始化bootmem

 paging_init(&meminfo, mdesc);  // mem_map is set up here! 
 request_standard_resources(&meminfo, mdesc);//建立資源鏈表

 /*
  * Set up various architecture-specific pointers
  */
  設定初始化irq的函數
 init_arch_irq = mdesc->init_irq;

是否支持keyboard和顯示設備,他們是用來做虛擬終端
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
 conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
 conswitchp = &dummy_con;
#endif
#endif
}


void __init bootmem_init(struct meminfo *mi)
{
 struct node_info node_info[NR_NODES], *np = node_info;
 unsigned int bootmap_pages, bootmap_pfn, map_pg;
 int node, initrd_node;

 bootmap_pages = find_memend_and_nodes(mi, np);
註:通過上述操作,bootmap_pages設置了描述所有內存使用情況所需要的pages,並且在該函數中初始化了np[0]的內容。

 bootmap_pfn   = find_bootmap_pfn(0, mi, bootmap_pages);
註:通過上述操作,bootmap_pfn設置了bootmem所在的初始頁號。也就是說從bootmap_pfn到bootmap_pfn + bootmap_pages的內存頁被用來描述初始化的時候的內存的用用情況
 initrd_node   = check_initrd(mi);  //do nothing 

 map_pg = bootmap_pfn;

 np += numnodes - 1;
 for (node = numnodes - 1; node >= 0; node--, np--) {
  /*
   * If there are no pages in this node, ignore it.
   * Note that node 0 must always have some pages.
   */
  if (np->end == 0) {
   if (node == 0)
    BUG();
   continue;
  }

  /*
   * Initialise the bootmem allocator.
   */
  init_bootmem_node(NODE_DATA(node), map_pg, np->start, np->end);
註:在該函數中初始化contig_page_data中的struct bootmem_data *bdata
bdata->node_bootmem_map = bootmem的啟示地址
bdata->node_boot_start = 本節點物理內存的開始
bdata->node_low_pfn =本節點物理內存的結尾
同時保留了所有的頁面,使得所有內存不能使用,不過在下面的函數中將會放開。

但是在free_bootmem_node_bank(node, mi);中設置所有內存可以分配
  free_bootmem_node_bank(node, mi);
  map_pg += np->bootmap_pages;

  /*
   * If this is node 0, we need to reserve some areas ASAP -
   * we may use bootmem on node 0 to setup the other nodes.
   */
  if (node == 0)
   reserve_node_zero(bootmap_pfn, bootmap_pages);
注意:在該函數中,保留了部分內存不能被分配,這些內存快包括:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);  內核佔用
reserve_bootmem_node(pgdat, bootmap_pfn << PAGE_SHIFT,
        bootmap_pages << PAGE_SHIFT);   bootmem佔用


 if (map_pg != bootmap_pfn + bootmap_pages)
  BUG();
}

概要:本文主要講解了內存初始化所涉及到的三個函數
bootmem_init
paging_init
mem_init


第一節   內存管理概述
uclinux把整個連續物理地址空間看作是一個由許多物理頁幀(Physical Page Frame)組成的"頁幀數組",一般來說,頁幀的大小則為4KB。
在文件linux/include/asm-arm/proc-armv/page.h中
#define PAGE_SIZE       (1UL << PAGE_SHIFT)
#define PAGE_SHIFT 12

因此每個物理頁幀的起始物理地址的低12為必定全部為0,而它的高20位在右移12位後就是該頁幀的序號,也就是該物理頁幀在數組中的下標索引。 
ucLinux在頭文件include/linux/mm.h中定義了數據結構page來描述一個物理頁幀: 
typedef struct page {
 struct list_head list; //鏈接入各種page list的結構
 。。。。。。 

} mem_map_t;

uclinux在系統初始化時根據系統物理內存的實際大小建立起page結構數組mem_map,每個物理頁幀都在該數組中對應有一個成員來描述它,物理頁幀的序號就是它在mem_map數組中的下標索引。 對於UMA來說,mem_map描述了所有的物理內存頁面,

      在傳統的計算機系統中,整個物理地址空間中的存儲器的屬性都是一致的,比如CPU對任意物 理地址的訪問速度都是一樣的,因此這種結 構也稱為"勻質存儲結構"(Uniform Memory A rchitecture,簡稱UMA)。 而在多CPU體系結構的計算機中,物理存儲空間 的屬性卻往往不盡相同。每個CPU都可能有自 己的本地存儲器,系統中還可以有公用的共享存儲器,這些存儲器一起構成一個物理地址連 續的系統存儲空間; 每個CPU訪問其本地存儲器是最快的,而訪問其他CPU節點的存儲器或共 享存儲器則較慢,因此這就使得不同物理地址處的存儲器的屬性可能不一樣。這種存 儲結構 也就稱為"非均質存儲結構"(Non-Uniform Memory Architecture,簡稱NUMA)。在NUMA 
結構的系統中,屬於同一個CPU節點的存儲器模塊通常是勻質的,因此也被稱為"存儲節點" (Memory Node)。 
Linux內核2.4.0版開始增加對NUMA的支持,內存區就不再是全局的最高層結 
構,而是從屬於某一個具體的存儲節點。

ucLinux在頭文件include/linux/mmzone.h中定義了數 據結構pglist_data來描述一個存儲節點: 
typedef struct pglist_data { 
zone_t node_zones[MAX_NR_ZONES]; //3種分配區 
zonelist_t node_zonelists[NR_GFPINDEX]; //256種分配類型 
struct page *node_mem_map; // 分配域的頁結構表 
unsigned long *valid_addr_bitmap; 
struct bootmem_data *bdata; //內核自舉時所使用的分配域,大家在初始化的時候要極度注意該區域,在初始化的時候,是又改結構負責管理內存的使用d
unsigned long node_start_paddr; //分配域的起始物理地址 
unsigned long node_start_mapnr; //分配域的起始物理頁號 
unsigned long node_size; //分配域頁表總數 
int node_id; //分配域編號 
struct pglist_data *node_next; // 分配域鏈表 
} pg_data_t; //分配域結構

1. 若干存儲節點的pglist_data數據結構通過指針node_next形成一個單向鏈接的存儲節點隊 列。不過一般的嵌入式的環境我估計也就一個這樣的結構啦。 
2. 系統仍然維護一個全局的page結構數組mem_map,但是每個存儲節點都通過pglist_data結 構中的指針node_mem_map指向mem_map數組中屬於該存儲節點的起始物理頁幀的page結構。 
3. 數組node_zones[MAX_NR_ZONES]定義了屬於該存儲節點的內存區。 
4. 數組node_zonelists[NR_GFPINDEX]定義了擁有該存儲節點的CPU在分配連續的物理頁幀塊 時可以選擇使用物理內存區。
顯然,我們的板子暫時不支持努NUMA,但是內核的數據結構確實按照NUMA的方式構建的
在文件---linux/mmnommu/numa.c中定義了存儲節點,顯然,我們只有一個這樣的結構。
static bootmem_data_t contig_bootmem_data;
pg_data_t contig_page_data = { bdata: &contig_bootmem_data };它是一個代表本節點的內存信息的一個數據結構


數據結構zonelist_struct的定義如下: 
typedef struct zonelist_struct{ 
zone_t *zones[MAX_NR_ZONES+1]; 
unsigned long gfp_mask; 
}zonelist_t; 
顯然,一個zonelist_struct結構為存儲節點定義了一種可供選擇的分配策略。也即當從一個 存儲節點分配連續的物理頁幀塊時,可以從zones指針數組中尋找可滿足此次分配需求的物理 內存區。

ucLinux在頭文件include/linux/mmzone.h中定義了數 據結構zone_struct
typedef struct zone_struct {
 spinlock_t  lock;
 unsigned long  free_pages;  //空閒頁面的數量
 unsigned long  pages_min, pages_low, pages_high; //判斷頁面使用情況的閾值
 int   need_balance;

 free_area_t  free_area[MAX_ORDER];  //描述空閒頁面
注意: uclinux通過zone_struct數據結構中一組"空閒區"(free area)隊列來管理包含在其中的物理頁幀,也即 free_area[MAX_ORDER]數組。為什麼是通過一組"空閒區"(free area)隊列,而不是一個"空閒區"(free area)隊 列呢?這是因為我們常常需要成"塊"地分配物理地址連續的多個物理頁幀。因此,在zone_struct數據結構中即要有一個隊列來保持一些離散(連續長 度為1)的物理頁幀,還要有其他多個隊列來保持連續長度為2、4、8、16、…、2MAX_ORDER的頁幀塊。常數MAX_ORDER值為10,因此在 Linux中最大的連續頁幀塊可以達到210=1024個頁幀,即4M字節大小。

 wait_queue_head_t * wait_table;
 unsigned long  wait_table_size;
 unsigned long  wait_table_shift;

 struct pglist_data *zone_pgdat; //指向該內存區(zone)屬於的節點信息。
 struct page  *zone_mem_map;   //指向本zone的第一個page結構
 unsigned long  zone_start_paddr;   //本zone開始的物理地址
 unsigned long  zone_start_mapnr;  //用來表示該內存區所包含的物理頁幀在page數組中的起始頁幀序號

 char   *name;
 unsigned long  size;
} zone_t;
該數據結構描述了物理內存區的信息。為了更加方便地管理和使用物理地址空間,Linux將整個連續的物理地址空間劃分為三段,每一段物理地址空間 形成一個物理內存區(zone),分別是:ZONE_DMA區、ZONE_NORMAL區和ZONE_HIGHMEM區。其中,ZONE_DMA區用於管 理低端的16M物理內存範圍,這一段物理內存是轉供DMA使用的。ZONE_HIGHMEM區用於管理高端的1GB以上的物理內存(如果有的話)。 每一 個物理頁幀根據其頁序號(也即它的物理地址範圍)決定它隸屬於哪一個物理內存區,而且這種隸屬關係是永久不變的。 
在我們的板子上,共有三個zone,定義如下:
#define ZONE_DMA  0
#define ZONE_NORMAL  1
#define ZONE_HIGHMEM  2
#define MAX_NR_ZONES  3
但是實際上,所有的內存頁都保存在了ZONE_NORMAL中
前面囉嗦了這麼久,其實好大部分都是一些內核前輩的觀點,這裡羅列出來,以便使下面的分析會順暢點





第二節   bootmem_init 和 paging_init



系統初始化時對內存的初始化源自下列代碼(刪掉了若干不相關代碼)
void __init setup_arch(char **cmdline_p)
{

1 if (meminfo.nr_banks == 0) {
2  meminfo.nr_banks      = 1;
3  meminfo.bank[0].start = PAGE_OFFSET;  
4  meminfo.bank[0].size  = MEM_SIZE;
5 }
6 bootmem_init(&meminfo);  //初始化bootmem
7 paging_init(&meminfo, mdesc);  // mem_map is set up here! 


}

解釋:
1------5行
在這裡,我們設定meminfo結構,用此結構描述我們的內存基本情況,之後,會用該變量初始化contig_page_data的數據
#define PHYS_OFFSET (DRAM_BASE)
#define PAGE_OFFSET PHYS_OFFSET
具體的內存設置的值來自config.h文件,具體的板子具體分析

正常的內存管理應該由pg_data_t、  zone、  mem_map 等數據結構來管理,但此時這些結構還沒有建立,bootmem是在初始化的時候用來描述內存使用情況。

void __init bootmem_init(struct meminfo *mi)
{
 struct node_info node_info[NR_NODES], *np = node_info;
 unsigned int bootmap_pages, bootmap_pfn, map_pg;
 int node, initrd_node;

 bootmap_pages = find_memend_and_nodes(mi, np);後面會詳細分析這個函數,為了在啟動階段描述 內存使用情況我們需要一些內存空間,這些空間叫做bootmem,此時bootmap_pages表明了bootmem所需要的pages的數目
 bootmap_pfn   = find_bootmap_pfn(0, mi, bootmap_pages); 後面會詳細分析這個函 數,通過這個函數,bootmap_pfn設置了bootmem所在的初始頁號。也就是說從bootmap_pfn到bootmap_pfn +  bootmap_pages的內存頁被用來描述初始化的時候的內存的用用情況
 initrd_node   = check_initrd(mi);//俺們的板子沒有用

 map_pg = bootmap_pfn;


 np += numnodes - 1;
 初始化node結構
 for (node = numnodes - 1; node >= 0; node--, np--) {
 
  if (np->end == 0) {
   if (node == 0)
    BUG();
   continue;
  }

 
  init_bootmem_node(NODE_DATA(node), map_pg, np->start, np->end); 後面會詳細分析這個函數
  free_bootmem_node_bank(node, mi);//釋放所有內存,也就是把bootmem的區域全部設置為0
  map_pg += np->bootmap_pages;

 我們有可能會保留一些內存以便使值不能被動態分配,具體要保留什麼內容,後面會詳細分析
  if (node == 0)
   reserve_node_zero(bootmap_pfn, bootmap_pages);
 }


 if (map_pg != bootmap_pfn + bootmap_pages)
  BUG();
}


void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
 void *zero_page;
 int node;

 memcpy(&meminfo, mi, sizeof(meminfo));

 分配一個頁面的內存,很顯然,我們剛才做的工作起了作用,我們會在表示該頁面的那個bootmem中的bit設置值為1,以此表示該頁面已經被分配出去拉
 zero_page = alloc_bootmem_low_pages(PAGE_SIZE);

   unsigned long zone_size[MAX_NR_ZONES] = {0,0,0};
   
   zone_size[ZONE_DMA] = 0;
   zone_size[ZONE_NORMAL] = (END_MEM - PAGE_OFFSET) >> PAGE_SHIFT;

   free_area_init_node(0, NULL, NULL, zone_size, PAGE_OFFSET, NULL);後面會詳細分析這個函數
 
 memzero(zero_page, PAGE_SIZE);
 empty_zero_page = virt_to_page(zero_page);
 flush_dcache_page(empty_zero_page);//抱歉,我也搞不太懂,應該是和cpu體系相關的
}


void __init free_area_init_node(int nid, pg_data_t *pgdat, struct page *pmap,
 unsigned long *zones_size, unsigned long zone_start_paddr, 
 unsigned long *zholes_size)
{
 free_area_init_core(0, &contig_page_data, &mem_map, zones_size, 
    zone_start_paddr, zholes_size, pmap);
}


/*
 * Set up the zone data structures:
 *   - mark all pages reserved
 *   - mark all memory queues empty
 *   - clear the memory bitmaps
 */
void __init free_area_init_core(int nid, pg_data_t *pgdat, struct page **gmap,
 unsigned long *zones_size, unsigned long zone_start_paddr, 
 unsigned long *zholes_size, struct page *lmem_map)
{
 unsigned long i, j;
 unsigned long map_size;
 unsigned long totalpages, offset, realtotalpages;
 const unsigned long zone_required_alignment = 1UL << (MAX_ORDER-1);

 if (zone_start_paddr & ~PAGE_MASK)
  BUG();

 totalpages = 0;
 for (i = 0; i < MAX_NR_ZONES; i++) {
  unsigned long size = zones_size;
  totalpages += size;
 }
 realtotalpages = totalpages;
 if (zholes_size)
  for (i = 0; i < MAX_NR_ZONES; i++)
   realtotalpages -= zholes_size;
   
 printk("On node %d totalpages: %lu\n", nid, realtotalpages);

 /*
  * Some architectures (with lots of mem and discontinous memory
  * maps) have to search for a good mem_map area:
  * For discontigmem, the conceptual mem map array starts from 
  * PAGE_OFFSET, we need to align the actual array onto a mem map 
  * boundary, so that MAP_NR works.
  */
給表示所有物理內存的page結構數組分配空間
 map_size = (totalpages + 1)*sizeof(struct page);
 if (lmem_map == (struct page *)0) {
  lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);
  lmem_map = (struct page *)(PAGE_OFFSET + 
   MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));
 }
 *gmap = pgdat->node_mem_map = lmem_map;
 pgdat->node_size = totalpages;
 pgdat->node_start_paddr = zone_start_paddr;
 pgdat->node_start_mapnr = (lmem_map - mem_map);
 pgdat->nr_zones = 0;

 offset = lmem_map - mem_map; 
下面給zone_struct賦值
 for (j = 0; j < MAX_NR_ZONES; j++) {
  zone_t *zone = pgdat->node_zones + j;
  unsigned long mask;
  unsigned long size, realsize;

  zone_table[nid * MAX_NR_ZONES + j] = zone;
  realsize = size = zones_size[j];
  if (zholes_size)
   realsize -= zholes_size[j];

  printk("zone(%lu): %lu pages.\n", j, size);
  zone->size = size;
  zone->name = zone_names[j];
  zone->lock = SPIN_LOCK_UNLOCKED;
  zone->zone_pgdat = pgdat;
  zone->free_pages = 0;
  zone->need_balance = 0;
  if (!size)
   continue;

  /*
   * The per-page waitqueue mechanism uses hashed waitqueues
   * per zone.
   */
  zone->wait_table_size = wait_table_size(size);
  zone->wait_table_shift =
   BITS_PER_LONG - wait_table_bits(zone->wait_table_size);
  zone->wait_table = (wait_queue_head_t *)
   alloc_bootmem_node(pgdat, zone->wait_table_size
      * sizeof(wait_queue_head_t));

  for(i = 0; i < zone->wait_table_size; ++i)
   init_waitqueue_head(zone->wait_table + i);

  pgdat->nr_zones = j+1;

  mask = (realsize / zone_balance_ratio[j]);
  if (mask < zone_balance_min[j])
   mask = zone_balance_min[j];
  else if (mask > zone_balance_max[j])
   mask = zone_balance_max[j];
  zone->pages_min = mask;
  zone->pages_low = mask*2;
  zone->pages_high = mask*3;

  zone->zone_mem_map = mem_map + offset;
  zone->zone_start_mapnr = offset;
  zone->zone_start_paddr = zone_start_paddr;

  if ((zone_start_paddr >> PAGE_SHIFT) & (zone_required_alignment-1))
   printk("BUG: wrong zone alignment, it will crash\n");

  /*
   * Initially all pages are reserved - free ones are freed
   * up by free_all_bootmem() once the early boot process is
   * done. Non-atomic initialization, single-pass.
   */
處理該zone_struct上的所有頁面,具體含義由函數名可猜出
  for (i = 0; i < size; i++) {
   struct page *page = mem_map + offset + i;
   set_page_zone(page, nid * MAX_NR_ZONES + j);
   set_page_count(page, 0);
   SetPageReserved(page);
   INIT_LIST_HEAD(&page->list);
   if (j != ZONE_HIGHMEM)
    set_page_address(page, __va(zone_start_paddr));
   zone_start_paddr += PAGE_SIZE;
  }

  //初始化zone->free_area
  offset += size;
//為zone的buddy bitmap分配內存
  for (i = 0; ; i++) {
   unsigned long bitmap_size;

   INIT_LIST_HEAD(&zone->free_area.free_list);
   if (i == MAX_ORDER-1) {
    zone->free_area.map = NULL;
    break;
   }
  //求出buddy bitmap的字節數
   bitmap_size = (size-1) >> (i+4);
   bitmap_size = LONG_ALIGN(bitmap_size+1);
   zone->free_area.map = 
     (unsigned long *) alloc_bootmem_node(pgdat, bitmap_size);
  }
 }
//構建頁面分配的策略,也就是填充zonelists結構
 build_zonelists(pgdat);
}

asmlinkage void __init start_kernel(void)
{
 .....
 trap_init();
 init_IRQ();
 ....
 
中斷的初始化主要和這兩個函數相關

1. trap_init
在該函數中調用__trap_init((void *)vectors_base());將exception vector設置到 vectors_base開始的地址上,該函數位於entry-armv.S文件中,對於我們的板子,是把異常向量拷貝到0x100000000的位置 上。
對於arm處理器,共有復位、未定義指令、SWI、預取終止、數據終止、IRQ、FIQ,SWI主要用來實現系統調用,而產生了IRQ之後,通過exception vector進入中斷處理過程,主要執行do_IRQ函數。
void __init trap_init(void)
{
 extern void __trap_init(void *);
vectors_base是異常向量的基地址,不同的設計會有不同的值,
 __trap_init((void *)vectors_base());
 if (vectors_base() != 0)
  printk(KERN_DEBUG "Relocating machine vectors to 0x%08x\n",
   vectors_base());
#ifdef CONFIG_CPU_32
 modify_domain(DOMAIN_USER, DOMAIN_CLIENT);-----不知道什麼意思
#endif
}
在文件 linux/arch/armnommu/kernel/entry-armv.S
ENTRY(__trap_init)
  stmfd sp!, {r4 - r6, lr}

  mrs r1, cpsr  @ code from 2.0.38
  bic r1, r1, #MODE_MASK @ clear mode bits  坦白講,這段代碼很無聊,我們reset之後一直就是svc模式,disable IRQ,FIQ
  orr r1, r1, #I_BIT|F_BIT|MODE_SVC @ set SVC mode, disable IRQ,FIQ
  msr cpsr, r1

  adr r1, .LCvectors   @ set up the vectors
  ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
  stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}拷貝異常向量

  add r2, r0, #0x200
  adr r0, __stubs_start  @ copy stubs to 0x200
  adr r1, __stubs_end
1:  ldr r3, [r0], #4
  str r3, [r2], #4
  cmp r0, r1
  blt 1b
  LOADREGS(fd, sp!, {r4 - r6, pc})
從__stubs_start到__stubs_end中,包含了異常處理的代碼,所以拷貝到了vectors_base+0x200的位置上


irq_desc數組是用來描述IRQ的請求隊列,每一個中斷號分配一個irq_desc結構,組成了一個數組。
NR_IRQS代表中斷數目。我們的板子共有11箇中斷,這裡只是進行初始化,串口、時鐘等具體中斷將在下面具體講述。
void __init init_IRQ(void)
{
 extern void init_dma(void);
 int irq;

 for (irq = 0; irq < NR_IRQS; irq++) {
  irq_desc[irq].probe_ok = 0;
  irq_desc[irq].valid    = 0;
  irq_desc[irq].noautoenable = 0;
  irq_desc[irq].mask_ack = dummy_mask_unmask_irq;
  irq_desc[irq].mask     = dummy_mask_unmask_irq;
  irq_desc[irq].unmask   = dummy_mask_unmask_irq;
 }

 init_arch_irq();----前面已近講過,此處執行linux/arch/armnommu/mach-***/irq.c文件中的irq_init_irq函數,每個cpu都是自己初始化irq的方式,具體就要看你cpu了,我的處理是這裡開時間中斷
 init_dma();---也是和cpu相關的,我們沒有使用dma,所以~~~呵呵~~~略
}

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 Bluelove1968 的頭像
    Bluelove1968

    藍色情懷

    Bluelove1968 發表在 痞客邦 留言(0) 人氣()