内核启动 startkernel.docx

上传人:b****0 文档编号:18356526 上传时间:2023-08-16 格式:DOCX 页数:27 大小:28.79KB
下载 相关 举报
内核启动 startkernel.docx_第1页
第1页 / 共27页
内核启动 startkernel.docx_第2页
第2页 / 共27页
内核启动 startkernel.docx_第3页
第3页 / 共27页
内核启动 startkernel.docx_第4页
第4页 / 共27页
内核启动 startkernel.docx_第5页
第5页 / 共27页
内核启动 startkernel.docx_第6页
第6页 / 共27页
内核启动 startkernel.docx_第7页
第7页 / 共27页
内核启动 startkernel.docx_第8页
第8页 / 共27页
内核启动 startkernel.docx_第9页
第9页 / 共27页
内核启动 startkernel.docx_第10页
第10页 / 共27页
内核启动 startkernel.docx_第11页
第11页 / 共27页
内核启动 startkernel.docx_第12页
第12页 / 共27页
内核启动 startkernel.docx_第13页
第13页 / 共27页
内核启动 startkernel.docx_第14页
第14页 / 共27页
内核启动 startkernel.docx_第15页
第15页 / 共27页
内核启动 startkernel.docx_第16页
第16页 / 共27页
内核启动 startkernel.docx_第17页
第17页 / 共27页
内核启动 startkernel.docx_第18页
第18页 / 共27页
内核启动 startkernel.docx_第19页
第19页 / 共27页
内核启动 startkernel.docx_第20页
第20页 / 共27页
亲,该文档总共27页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

内核启动 startkernel.docx

《内核启动 startkernel.docx》由会员分享,可在线阅读,更多相关《内核启动 startkernel.docx(27页珍藏版)》请在冰点文库上搜索。

内核启动 startkernel.docx

内核启动startkernel

内核启动start_kernel

一位大师级的人物写的,不看要后悔的哟!

!

如果以为到了c代码可以松一口气的话,就大错特措了,linux的c也不比汇编好懂多少,相反到掩盖了汇编的一些和机器相关的部分,有时候更难懂。

其实作为编写操作系统的c代码,只不过是汇编的另一种写法,和机器代码的联系是很紧密的。

start_kernel在/linux/init/main.c中定义:

asmlinkagevoid__initstart_kernel(void)

{

char*command_line;

unsignedlongmempages;

externcharsaved_command_line[];

lock_kernel();

printk(linux_banner);

setup_arch(&command_line);//arm/kernel/setup.c

printk("Kernelcommandline:

%s\n",saved_command_line);

parse_options(command_line);

trap_init();//arm/kernle/traps.cinstall

start_kernel中的函数个个都是重量级的,首先用printk(linux_banner);打出

系统版本号,这里面就大有文章,系统才刚开张,你让他打印到哪里去呢?

先给大家交个底,以后到console的部分自然清楚,printk和printf不同,他首先输出到系统的一个缓冲区内,大约4k,如果登记了console,则调用console->wirte函数输出,否则就一直在buffer里呆着。

所以,用printk输出的信息,如果超出了4k,会冲掉前面的。

在系统引导起来后,用dmesg看的也就是这个buffer中的东东。

下面就是一个重量级的函数:

setup_arch(&command_line);//arm/kernel/setup.c

完成内存映像的初始化,其中command_line是从bootloader中传下来的。

void__initsetup_arch(char**cmdline_p)

{

structparam_struct*params=NULL;

structmachine_desc*mdesc;//archstructure,foryourads,definedininclude/arm-asm/mach/arch.hverylong

structmeminfomeminfo;

char*from=default_command_line;

memset(&meminfo,0,sizeof(meminfo));

首先把meminfo清零,有个背景介绍一下,从linux2.4的内核开始,支持内存的节点(node),也就是可支持不连续的物理内存区域。

这一点在嵌入式系统中很有用,例如对于SDRAM和FALSH,性质不同,可作为不同的内存节点。

meminfo结构定义如下:

/******************************************************/

#defineNR_BANKS4

//definethesystenmemregion,notconsistent

structmeminfo{

intnr_banks;

unsignedlongend;

struct{

unsignedlongstart;

unsignedlongsize;

intnode;

}bank[NR_BANKS];

};

/******************************************************/

下面是:

ROOT_DEV=MKDEV(0,255);

ROOT_DEV是宏,指明启动的设备,嵌入式系统中通常是flashdisk.

这里面有一个有趣的悖论:

linux的设备都是在/dev/下,访问这些设备文件需要设备驱动程序支持,而访问设备文件才能取得设备号,才能加载驱动程序,那么第一个设备驱动程序是怎么加载呢?

就是ROOT_DEV,不需要访问设备文件,直接指定设备号。

下面我们准备初始化真正的内核页表,而不再是临时的了。

首先还是取得当前系统的内存映像:

mdesc=setup_architecture(machine_arch_type);

//findthemachinetypeinmach-integrator/arch.c

//theadsname,memmap,iomap

返回如下结构:

mach-integrator/arch.c

MACHINE_START(INTEGRATOR,"MotorolaMX1ADS")

MAINTAINER("ARMLtd/DeepBlueSolutionsLtd")

BOOT_MEM(0x08000000,0x00200000,0xf0200000)

FIXUP(integrator_fixup)

MAPIO(integrator_map_io)

INITIRQ(integrator_init_irq)

MACHINE_END

我们在前面介绍过这个结构,不过这次用它可是玩真的了。

书接上回,

下面是init_mm的初始化,init_mm定义在/arch/arm/kernel/init_task.c:

structmm_structinit_mm=INIT_MM(init_mm);

从本回开始的相当一部分内容是和内存管理相关的,凭心而论,操作系统的

内存管理是很复杂的,牵扯到处理器的硬件细节和软件算法,

限于篇幅所限制,请大家先仔细读一读armmmu的部分,

中文参考资料:

linux内核源代码情景对话,

linux2.4.18原代码分析。

init_mm.start_code=(unsignedlong)&_text;

内核代码段开始

init_mm.end_code=(unsignedlong)&_etext;

内核代码段结束

init_mm.end_data=(unsignedlong)&_edata;

内核数据段开始

init_mm.brk=(unsignedlong)&_end;

内核数据段结束

每一个任务都有一个mm_struct结构管理任务内存空间,init_mm

是内核的mm_struct,其中设置成员变量*mmap指向自己,

意味着内核只有一个内存管理结构,设置*pgd=swapper_pg_dir,

swapper_pg_dir是内核的页目录,在arm体系结构有16k,

所以init_mm定义了整个kernel的内存空间,下面我们会碰到内核

线程,所有的内核线程都使用内核空间,拥有和内核同样的访问

权限。

memcpy(saved_command_line,from,COMMAND_LINE_SIZE);

//clearcommandarray

saved_command_line[COMMAND_LINE_SIZE-1]='\0';

//settheendflag

parse_cmdline(&meminfo,cmdline_p,from);

//将bootloader的参数拷贝到cmdline_p,

bootmem_init(&meminfo);

定义在arm/mm/init.c

这个函数在内核结尾分一页出来作位图,根据具体系统的内存大小

映射整个ram

下面是一个非常重要的函数

paging_init(&meminfo,mdesc);

定义在arm/mm/init.c

创建内核页表,映射所有物理内存和io空间,

对于不同的处理器,这个函数差别很大,

void__initpaging_init(structmeminfo*mi,structmachine_desc*mdesc)

{

void*zero_page,*bad_page,*bad_table;

intnode;

//staticstructmeminfomeminfo__initdata={0,};

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

/*

*allocatewhatweneedforthebadpages.

*notethatwecountonthisgoingok.

*/

zero_page=alloc_bootmem_low_pages(PAGE_SIZE);

bad_page=alloc_bootmem_low_pages(PAGE_SIZE);

bad_table=alloc_bootmem_low_pages(TABLE_SIZE);

分配三个页出来,用于处理异常过程,在armlinux中,得到如下

地址:

zero_page=0xc0000000

badpage=0xc0001000

bad_table=0xc0002000

上回我们说到在paging_init中分配了三个页:

zero_page=0xc0000000

badpage=0xc0001000

bad_table=0xc0002000

但是奇怪的很,在更新的linux代码中只分配了一个

zero_page,而且在源代码中找不到zero_page

用在什么地方了,大家讨论讨论吧。

paging_init的主要工作是在

void__initmemtable_init(structmeminfo*mi)

中完成的,为系统内存创建页表:

meminfo结构如下:

structmeminfo{

intnr_banks;

unsignedlongend;

struct{

unsignedlongstart;

unsignedlongsize;

intnode;

}bank[NR_BANKS];

};

是用来纪录系统中的内存区段的,因为在嵌入式

系统中并不是所有的内存都能映射,例如sdram只有

64m,flash32m,而且不见得是连续的,所以用

meminfo纪录这些区段。

void__initmemtable_init(structmeminfo*mi)

{

structmap_desc*init_maps,*p,*q;

unsignedlongaddress=0;

inti;

init_maps=p=alloc_bootmem_low_pages(PAGE_SIZE);

其中map_desc定义为:

structmap_desc{

unsignedlongvirtual;

unsignedlongphysical;

unsignedlonglength;

intdomain:

4,//页表的domain

prot_read:

1,//保护标志

prot_write:

1,//写保护标志

cacheable:

1,//是否cache

bufferable:

1,//是否用writebuffer

last:

1;//空

};init_maps

map_desc是区段及其属性的定义,属性位的意义请

参考ARMMMU的介绍。

下面对meminfo的区段进行遍历,同时填写init_maps

中的各项内容:

for(i=0;inr_banks;i++){

if(mi->bank.size==0)

continue;

p->physical=mi->bank.start;

p->virtual=__phys_to_virt(p->physical);

p->length=mi->bank.size;

p->domain=DOMAIN_KERNEL;

p->prot_read=0;

p->prot_write=1;

p->cacheable=1;//可以CACHE

p->bufferable=1;//使用writebuffer

p++;//下一个区段

}

如果系统有flash,

#ifdefFLUSH_BASE

p->physical=FLUSH_BASE_PHYS;

p->virtual=FLUSH_BASE;

p->length=PGDIR_SIZE;

p->domain=DOMAIN_KERNEL;

p->prot_read=1;

p->prot_write=0;

p->cacheable=1;

p->bufferable=1;

p++;

#endif

其中的prot_read和prot_write是用来设置页表的domain的,

下面就是逐个区段建立页表:

q=init_maps;

do{

if(addressvirtual||q==p){

clear_mapping(address);

address+=PGDIR_SIZE;

}else{

create_mapping(q);

address=q->virtual+q->length;

address=(address+PGDIR_SIZE-1)&PGDIR_MASK;

q++;

}

}while(address!

=0);

上次说到memtable_init中初始化页表的循环,

这个过程比较重要,我们看仔细些:

q=init_maps;

do{

if(addressvirtual||q==p){

//由于内核空间是从c0000000开始,所以c0000000

//以前的页表项全部清空

clear_mapping(address);

address+=PGDIR_SIZE;

//每个表项增加1m,这里感到了section的好处

}

其中clear_mapping()是个宏,根据处理器的

不同,在920下被展开为

cpu_arm920_set_pmd(((pmd_t*)(((&init_mm)->pgd+

((virt)>>20)))),((pmd_t){(0)}));

其中init_mm为内核的mm_struct,pgd指向

swapper_pg_dir,在arch/arm/kernel/init_task.c中定义

ENTRY(cpu_arm920_set_pmd)

#ifdefCONFIG_CPU_ARM920_WRITETHROUGH

eorr2,r1,#0x0a

tstr2,#0x0b

biceqr1,r1,#4

#endif

strr1,[r0]

把pmd_t填写到页表项中,由于pmd_t=0,

实际等于清除了这一项,由于dcache打开,

这一条指令实际并没有写回内存,而是写到cache中

mcrp15,0,r0,c7,c10,1

把cache中地址r0对应的内容写回内存中,

这一条语句实际是写到了writebuffer中,

还没有真正写回内存。

mcrp15,0,r0,c7,c10,4

等待把writebuffer中的内容写回内存。

在这之前core等待

movpc,lr

在这里我们看到,由于页表的内容十分关键,为了确保写回内存,

采用了直接操作cache的方法。

由于在armcore中,打开了dcache

则必定要用writebuffer.所以还有wb的回写问题。

由于考虑到效率,我们使用了cache和buffer,

所以在某些地方要用指令保证数据被及时写回。

下面映射c0000000后面的页表

else{

create_mapping(q);

address=q->virtual+q->length;

address=(address+PGDIR_SIZE-1)&PGDIR_MASK;

q++;

}

}while(address!

=0);

create_mapping也在mm-armv.c中定义;

staticvoid__initcreate_mapping(structmap_desc*md)

{

unsignedlongvirt,length;

intprot_sect,prot_pte;

longoff;

prot_pte=L_PTE_PRESENT|L_PTE_YOUNG|L_PTE_DIRTY|

(md->prot_read?

L_PTE_USER:

0)|

(md->prot_write?

L_PTE_WRITE:

0)|

(md->cacheable?

L_PTE_CACHEABLE:

0)|

(md->bufferable?

L_PTE_BUFFERABLE:

0);

prot_sect=PMD_TYPE_SECT|PMD_DOMAIN(md->domain)|

(md->prot_read?

PMD_SECT_AP_READ:

0)|

(md->prot_write?

PMD_SECT_AP_WRITE:

0)|

(md->cacheable?

PMD_SECT_CACHEABLE:

0)|

(md->bufferable?

PMD_SECT_BUFFERABLE:

0);

由于arm中section表项的权限位和page表项的位置不同,

所以根据structmap_desc中的保护标志,分别计算页表项

中的AP,domain,CB标志位。

有一段时间没有写了,道歉先,前一段时间在做armlinux的xip,终于找到了

在flash中运行kernel的方法,同时对系统的存储管理

的理解更深了一层,我们继续从上回的create_mapping往下看:

while((virt&0xfffff||(virt+off)&0xfffff)&&length>=PAGE_SIZE){

alloc_init_page(virt,virt+off,md->domain,prot_pte);

virt+=PAGE_SIZE;

length-=PAGE_SIZE;

}

while(length>=PGDIR_SIZE){

alloc_init_section(virt,virt+off,prot_sect);

virt+=PGDIR_SIZE;

length-=PGDIR_SIZE;

}

while(length>=PAGE_SIZE){

alloc_init_page(virt,virt+off,md->domain,prot_pte);

virt+=PAGE_SIZE;

length-=PAGE_SIZE;

}

这3个循环的设计还是很巧妙的,create_mapping的作用是设置虚地址virt

到物理地址virt+off的映射页目录和页表。

arm提供了4种尺寸的页表:

1M,4K,16K,64K,armlinux只用到了1M和4K两种。

这3个while的作用分别是“掐头“,“去尾“,“砍中间“。

第一个while是判断要映射的地址长度是否大于1m,且是不是1m对齐,

如果不是,则需要创建页表,例如,如果要映射的长度为1m零4k,则先要将“零头“

去掉,4k的一段需要中间页表,通过第一个while创建中间页表,

而剩下的1M则交给第二个while循环。

最后剩下的交给第三个while循环。

alloc_init_page分配并填充中间页表项

staticinlinevoid

alloc_init_page(unsignedlongvirt,unsignedlongphys,intdomain,intprot)

{

pmd_t*pmdp;

pte_t*ptep;

pmdp=pmd_offset(pgd_offset_k(virt),virt);//返回页目录中virt对应的表项

if(pmd_none(*pmdp)){//如果表项是空的,则分配一个中间页表

pte_t*ptep=alloc_bootmem_low_pages(2*PTRS_PER_PTE*

sizeof(pte_t));

ptep+=PTRS_PER_PTE;

//设置页目录表项

set_pmd(pmdp,__mk_pmd(ptep,PMD_TYPE_TABLE|PMD_DOMAIN(domain)));

}

ptep=pte_offset(pmdp,virt);

//如果表项不是空的,则表项已经存在,只需要设置中间页表表项

set_pte(ptep,mk_pte_phys(phys,__pgprot(prot)));

}

alloc_init_section只需要填充页目录项

alloc_init_section(unsignedlongvirt,unsignedlongphys,intprot)

{

pmd_tpmd;

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 经管营销

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2