原创Linux arm 启动 c语言部分详解.docx
《原创Linux arm 启动 c语言部分详解.docx》由会员分享,可在线阅读,更多相关《原创Linux arm 启动 c语言部分详解.docx(47页珍藏版)》请在冰豆网上搜索。
原创Linuxarm启动c语言部分详解
Linuxarm启动c语言部分详解第一讲(fromStartkernel)
作为我们实验室的一个学术交流,我顺着fp的linuxarm启动汇编部分继续下去。
我们可以看到其实linux汇编部分的启动大量的工作是对zimage的解压,重定位等操作,如果是image(也就是zimage解压重定位结束后)来说,其实主要就做了以下这么几件事情:
1.建立启动时的一级页表,2.打开mmu,3.保存机器号等参数。
因此对于整个处理器系统来说还需要做大量的工作,对于移植内核来说,只有真正了解了这部分你才会明白在arch/arm/mach-sep4020这个目录中的文件为什么需要这样写。
进入正题:
1.进入start_kernel,详解setup_arch(处理器的移植,页表建立都在这里实现的)
asmlinkagevoid__initstart_kernel(void)
{
char*command_line;
externstructkernel_param__start___param[],__stop___param[];
/*
*Interruptsarestilldisabled.Donecessarysetups,then
*enablethem
*/
lock_kernel();
//这里是和高端内存相关的操作,arm中不涉及
page_address_init();
//这个只是printk的等级,但是为什么在控制台不显示,待看
//这时候控制台还没有初始化,因此所有的信息都是在log_buf里
printk(KERN_NOTICE);
printk(linux_banner);
setup_arch(&command_line);
……
……
到这里就碰到了我们详解startkernel的第一道坎,setup_arch(&command_line);别看就一句话,其实这个函数本身是非常庞大的,下面我们来具体看完整的setup_arch函数。
void__initsetup_arch(char**cmdline_p)
{
structtag*tags=(structtag*)&init_tags;
structmachine_desc*mdesc;
char*from=default_command_line;
//就是查找你是什么版本的处理器架构,最后就是调用
//了lookup_processor_type这个函数,它在汇编部分也提到过
setup_processor();
//machine_arch_type就是我们的机器号0xc2,这里就是通过调用lookup_machine_type函数来得到我们在4020.c中写的那个machine_start的结构体。
//machine_startmachine_end段,machine_start(type,name)后面跟的两个参数其实就是
//.nr=MACH_TYPE_##_type,.name=_name,
mdesc=setup_machine(machine_arch_type);
machine_name=mdesc->name;
//这个变量初始值为"h",如果这里设置成softboot,它会将这个初始值变为"s"
if(mdesc->soft_reboot)
reboot_setup("s");
//boot_params如果为0则表示bootloader没有传参数
//一般默认为0x30000100位置,我们代码也是填的这个位置,4020的uboot当然也要指定这个位置
if(mdesc->boot_params)
tags=phys_to_virt(mdesc->boot_params);
/*
*Ifwehavetheoldstyleparameters,convertthemto
*ataglist.
*/
if(tags->hdr.tag!
=ATAG_CORE)
convert_to_tag_list(tags);
if(tags->hdr.tag!
=ATAG_CORE)
tags=(structtag*)&init_tags;
if(mdesc->fixup)
mdesc->fixup(mdesc,tags,&from,&meminfo);
//是通过标签0x544100**来辨别的,因此uboot中有相应的标签字
if(tags->hdr.tag==ATAG_CORE){
//已经被fixup函数修改,则将atag中的mem段置为none
if(meminfo.nr_banks!
=0)
squash_mem_tags(tags);
//继续把atag的参数传递结束
parse_tags(tags);
}
//下面几个参数是由vmlinux.lds文件决定的
init_mm.start_code=(unsignedlong)&_text;
init_mm.end_code=(unsignedlong)&_etext;
init_mm.end_data=(unsignedlong)&_edata;
init_mm.brk=(unsignedlong)&_end;
//在arch/arm/kernel/setup.c这个文件中,定义如下:
staticchardefault_command_line[COMMAND_LINE_SIZE]__initdata=CONFIG_CMDLINE;
//在我们的配置中#defineCONFIG_CMDLINE"root=/dev/ram0rwconsole=ttyS0,115200"
memcpy(saved_command_line,from,COMMAND_LINE_SIZE);
saved_command_line[COMMAND_LINE_SIZE-1]='\0';
//分析commandline,看这个函数的代码你会发现为什么我们的commandline不同参数之间是通过空格来分开,同一参数之间的不同值是通过逗号来区分
parse_cmdline(cmdline_p,from);
//非常重要的部分,页表建立,根据meminfo,也就是我们在4020.c中的mache_start何mache_end之间的内容,它其实就是一个类型是machine_desc的结构体,这里就是把这个结构体中的内存信息(fixup函数中指定的),寄存器信息(sep4020_map_io指定的)建立相应的一二级页表
/*************************************/
paging_init(&meminfo,mdesc);
/*************************************/
request_standard_resources(&meminfo,mdesc);
#ifdefCONFIG_SMP
smp_init_cpus();
#endif
//设置不同模式的堆栈,包括3种模式,irq,abort,undefine,每种模式的堆栈是12个字节
cpu_init();
{
//gcc内嵌汇编的格式是__asm(汇编代码:
输入:
输出:
破坏描述部分);
//下面的限制字符"r""I"是把变量放进通用寄存器,立即数的意思
__asm__(
"msrcpsr_c,%1\n\t"
"addsp,%0,%2\n\t"
"msrcpsr_c,%3\n\t"
"addsp,%0,%4\n\t"
"msrcpsr_c,%5\n\t"
"addsp,%0,%6\n\t"
"msrcpsr_c,%7"
:
:
"r"(stk),//堆栈空间起始地址
"I"(PSR_F_BIT|PSR_I_BIT|IRQ_MODE),//切换到irq模式,并禁止irqfiq
"I"(offsetof(structstack,irq[0])),//irq[0]的偏移,一下同理
"I"(PSR_F_BIT|PSR_I_BIT|ABT_MODE),
"I"(offsetof(structstack,abt[0])),
"I"(PSR_F_BIT|PSR_I_BIT|UND_MODE),
"I"(offsetof(structstack,und[0])),
"I"(PSR_F_BIT|PSR_I_BIT|SVC_MODE)
:
"r14");//注意切换svc时,告诉系统,r14改变了
}
/*
*Setupvariousarchitecture-specificpointers
*/
//继续将020.c中的mache_start何mache_end之间的内容注册到系统中,这里是把系统初始化函数,中断,定时器注册(这几部分也就是在为一个新的处理器移植内核的时候非常重要的地方)
init_arch_irq=mdesc->init_irq;
system_timer=mdesc->timer;
init_machine=mdesc->init_machine;
#ifdefCONFIG_VT
#ifdefined(CONFIG_VGA_CONSOLE)
conswitchp=&vga_con;
#elifdefined(CONFIG_DUMMY_CONSOLE)
conswitchp=&dummy_con;
#endif
#endif
}
Linuxarm启动c语言部分详解第二讲(Startkernel->setup_arch->paging_init)
Writtenbyleeming
这一讲是主要讲setup_arch中那个没有解释的函数解释完毕,完成setup_arch的函数,好让我们的start_kernel继续下去。
/*
*paging_init()setsupthepagetables,initialisesthezonememory
*maps,andsetsupthezeropage,badpageandbadpagetables.
*这部分的主要工作建立页表,初始化内存。
*/
void__initpaging_init(structmeminfo*mi,structmachine_desc*mdesc)
{
void*zero_page;
//这个函数主要是用来建立各种类型的页表选项(比如内存是MEMORY类型,设备室DEVICE,中断向量表是HIGH_VECTORS)
build_mem_type_table();
{
structcachepolicy*cp;
//获取cp15处理器的c1寄存器位
unsignedintcr=get_cr();
unsignedintuser_pgprot,kern_pgprot;
//获取处理器架构版本
intcpu_arch=cpu_architecture();
inti;
//根据处理器版本号调整cache政策,不是写缓冲区的政策
#ifdefined(CONFIG_CPU_DCACHE_DISABLE)
if(cachepolicy>CPOLICY_BUFFERED)
cachepolicy=CPOLICY_BUFFERED;
#elifdefined(CONFIG_CPU_DCACHE_WRITETHROUGH)
if(cachepolicy>CPOLICY_WRITETHROUGH)
cachepolicy=CPOLICY_WRITETHROUGH;
#endif
if(cpu_archif(cachepolicy>=CPOLICY_WRITEALLOC)
cachepolicy=CPOLICY_WRITEBACK;
ecc_mask=0;//因为v5前的处理器的一级描述符没有定义第9位作为保护标志位
}
if(cpu_arch<=CPU_ARCH_ARMv5TEJ){
//mem_types是一个全局数组arch/arm/mm-armv.c,里面有所有类型
for(i=0;i//prot_l1prot_sect都是一级描述符的意思
//将一级描述符的第4位置1
if(mem_types[i].prot_l1)
mem_types[i].prot_l1|=PMD_BIT4;
if(mem_types[i].prot_sect)
mem_types[i].prot_sect|=PMD_BIT4;
}
}
//我们的cachepolicy是3,因此相应的配置如下
//.policy="writeback",
//.cr_mask=0,
//.pmd=PMD_SECT_WB,
//.pte=PTE_BUFFERABLE|PTE_CACHEABLE,
cp=&cache_policies[cachepolicy];
//kern_pgprotuser_pgprot是内核和用户空间的二级页表描述符
kern_pgprot=user_pgprot=cp->pte;
//以下删除了非v4t架构的高版本代码
for(i=0;i<16;i++){
//这里依次获取16个默认的保护类型的值
unsignedlongv=pgprot_val(protection_map[i]);
//(L_PTE_BUFFERABLE|L_PTE_CACHEABLE)这是linuxpte的定义
//内核中有linux和hardware两种定义方式,为了更好的兼容性
//这里两者间是匹配的,这里将值再加上我们的设置就是
//最新的16个值,将它写回更新
v=(v&~(L_PTE_BUFFERABLE|L_PTE_CACHEABLE))|user_pgprot;
protection_map[i]=__pgprot(v);
}
mem_types[MT_LOW_VECTORS].prot_pte|=kern_pgprot;
mem_types[MT_HIGH_VECTORS].prot_pte|=kern_pgprot;
mem_types[MT_MINICLEAN].prot_sect&=~PMD_SECT_TEX
(1);
pgprot_kernel=__pgprot(L_PTE_PRESENT|L_PTE_YOUNG|
L_PTE_DIRTY|L_PTE_WRITE|
L_PTE_EXEC|kern_pgprot);
mem_types[MT_LOW_VECTORS].prot_l1|=ecc_mask;
mem_types[MT_HIGH_VECTORS].prot_l1|=ecc_mask;
mem_types[MT_MEMORY].prot_sect|=ecc_mask|cp->pmd;
mem_types[MT_ROM].prot_sect|=cp->pmd;
switch(cp->pmd){
casePMD_SECT_WT:
mem_types[MT_CACHECLEAN].prot_sect|=PMD_SECT_WT;
break;
casePMD_SECT_WB:
casePMD_SECT_WBWA:
mem_types[MT_CACHECLEAN].prot_sect|=PMD_SECT_WB;
break;
}
//以上所有的操作都是为了给mem_types这个结构体中的各种类型中的页表参数添加上我们的要求,主要是一级页表,二级页表,ap(访问权限控制);至于domain是利用系统初始化时的值,不用我们再进行干预。
//系统的domain类型一共有四种,kernel——0;user——1;io——2
printk("Memorypolicy:
ECC%sabled,Datacache%s\n",
ecc_mask?
"en":
"dis",cp->policy);
}
bootmem_init(mi);
{
unsignedlongaddr,memend_pfn=0;
intnode,initrd_node,i;
/*
*Invalidatethenodenumberforemptyorinvalidmemorybanks
*/
for(i=0;inr_banks;i++)
if(mi->bank[i].size==0||mi->bank[i].node>=MAX_NUMNODES)
mi->bank[i].node=-1;
//将在4020.cfixup函数中定义的内存信息添加到meminfo结构体中
memcpy(&meminfo,mi,sizeof(meminfo));
//MODULE_START是0xc0000000-16M;以2M为单位,清除内核空间一下的用户空间
for(addr=0;addr//内核在进入保护模式前,还没有启用分页功能,在这之前内核要先建立一个临时内核页表,因为在进入保护模式后,内核继续初始化直到建
//立完整的内存映射机制之前,仍然需要用到页表来映射相应的内存地址。
临时页表的初始化是在arch/i386/kernel/head.S中进行的:
//swapper_pg_dir是临时页全局目录表,它是在内核编译过程中静态初始化的.
//pg0是第一个页表开始的地方,它也是内核编译过程中静态初始化的.
//pmd_off_k是获取虚拟地址为addr的页表项地址
//pmd_clear是将()中的页表项地址中的数据清0
pmd_clear(pmd_off_k(addr));
#ifdefCONFIG_XIP_KERNEL
/*TheXIPkernelismappedinthemodulearea--skipoverit*/
addr=((unsignedlong)&_etext+PGDIR_SIZE-1)&PGDIR_MASK;
#endif
//防止xip之后会有变化,检查,做一次用户空间的清除
for(;addrpmd_clear(pmd_off_k(addr));
/*
*Clearoutallthekernelspacemappings,exceptforthefirst
*memorybank,uptotheendofthevmallocregion.
*/
//清除内核空间,但是不清楚内存所在区域,也就是
//0xc2000000-0xd0000000的空间
for(addr=__phys_to_virt(mi->bank[0].start+mi->bank[0].size);
addrpmd_clear(pmd_off_k(addr));
/*
*Locatewhichnodecontainstheramdiskimage,ifany.
*/
//返回如果有initrd所在的内存节点
initrd_node=check_initrd(mi);
/*
*Runthrougheachnodeinitialisingthebootmemallocator.
*/
for_each_node(node){
unsignedlongend_pfn;
//为内存建立一级页表(多的话还有二级页表)
end_pfn=bootmem_init_node(node,initrd_node,mi);
/*
*RememberthehighestmemoryPFN.
*/
if(end_pfn>memend_pfn)
memend_pfn=end_pfn;
}
high_memory=__va(memend_pfn</*
*Thisdoesn'tseemtobeusedbytheLinuxmemorymanagerany
*more,butisusedbyll_rw_block.Ifwecangetridofit,we
*alsogetridofsomeofthestuffaboveaswell.
*
*Note:
max_low_pfnandmax_pfnreflectthenumberof_pages_in
*thesystem,notthemaximumPFN.
*/
max_pfn=max_low_pfn=memend_pfn-PHYS_PFN_OFFSET;
}
devicemaps_init(mdesc);
{
structmap_descmap;
unsignedlongaddr;
void*vectors;
/*
*Allocatethevectorpageearly.
*/
//为中断向量表申请一页的空间,申请的位置
//就是之前在内存中建立的页表的物理地址
vectors=alloc_bootmem_low_pages(PAGE_SIZE);
BUG_ON(!
vectors);
for(addr=VMALLOC_END;addr;addr+=PGDIR_SIZE)
//pmd_off_k是获取虚拟地址