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,所以~~~呵呵~~~略
}
在這個文件中定義了你編譯好的內核的入口的地址和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,所以~~~呵呵~~~略
}
全站熱搜