arch/arm/kernel/head-armv.S
该文件是内核最早履行的一个文件,包含内核进口ENTRY(stext)到start_kernel间的初始化代码,首要效果是查看CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数。在履行前,处理器应满意以下状况:
r0 – should be 0
r1 – unique architecture number
MMU – off
I-cache – on or off
D-cache – off
print?
- /*部分源代码剖析*/
- /*内核进口点*/
- ENTRY(stext)
- /*程序状况,制止FIQ、IRQ,设定SVC形式*/
- movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode
- /*置当时程序状况寄存器*/
- msrcpsr_c,r0@andallirqsdisabled
- /*判别CPU类型,查找运转的CPUID值与Linux编译支撑的ID值是否支撑*/
- bl__lookup_processor_type
- /*跳到__error*/
- teqr10,#0@invalidprocessor?
- moveqr0,#p@yes,errorp
- beq__error
- /*判别体系类型,查看R1寄存器的ArchitectureType值是否支撑*/
- bl__lookup_architecture_type
- /*不支撑,跳到犯错*/
- teqr7,#0@invalidarchitecture?
- moveqr0,#a@yes,errora
- beq__error
- /*创立中心页表*/
- bl__create_page_tables
- adrlr,__ret@returnaddress
- addpc,r10,#12@initialiseprocessor
- /*跳转到start_kernel函数*/
- bstart_kernel
1. start_kernel()函数剖析
下面临start_kernel()函数及其相关函数进行剖析。
1.1 lock_kernel()
print?
- /*Gettingthebigkernellock.
- *Thiscannothappenasynchronously,
- *soweonlyneedtoworryaboutother
- *CPUs.
- */
- extern__inline__voidlock_kernel(void)
- {
- if(!++current->lock_depth)
- spin_lock(&kernel_flag);
- }
kernel_flag 是一个内核大自旋锁,全部进程都经过这个大锁来完结向内核态的搬迁。只要取得这个大自旋
锁的处理器能够进入内核,如中止处理程序等。在任何一对 lock_kernel/unlock_kernel函数里至多能够有一个程序占用CPU。 进程的lock_depth成员初始化为-1,在 kerenl/fork.c文件中设置。在它小于0时
(恒为 -1),进程不具有内核锁;当大于或等于0时,进程得到内核锁。
1.2 setup_arch()
setup_arch()函数做体系相关的初始化作业,函数的界说在arch/arm/kernel/setup.c文件中,主
要触及下列首要函数及代码。
5.2.1 setup_processor()
该函数首要经过
print?
- 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文件
中设置。
1.2.2 setup_architecture(machine_arch_type)
该函数取得体系结构的信息,回来mach-xxx/arch.c 文件中界说的machine结构体的指针,包含以下内容
MACHINE_START (xxx, “xxx”)
MAINTAINER (“xxx”
BOOT_MEM (xxx, xxx, xxx)
FIXUP (xxx)
MAPIO (xxx)
INITIRQ (xxx)
MACHINE_END
1.2.3内存设置代码
print?
- if(meminfo.nr_banks==0)
- {
- meminfo.nr_banks=1;
- meminfo.bank[0].start=PHYS_OFFSET;
- meminfo.bank[0].size=MEM_SIZE;
- }
meminfo结构标明内存状况,是对物理内存结构meminfo的默许初始化。 nr_banks指定内存块的数量,
bank指定每块内存的规模,PHYS _OFFSET指定某块内存块的开端地址,MEM_SIZE指定某块内存块长度。
PHYS _OFFSET和MEM_SIZE都界说在include/asm-armnommu/arch-XXX/memory.h文件中,其间
PHYS _OFFSET是内存的开端地址,MEM_SIZE便是内存的完毕地址。这个结构在接下来内存的初始化代码中
起重要效果。
1.2.4 内核内存空间办理
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; 内核数据段完毕
每一个使命都有一个mm_struct结构办理其内存空间,init_mm 是内核的mm_struct。其间设置成员变量
* mmap指向自己, 意味着内核只要一个内存办理结构,设置 pgd=swapper_pg_dir,
swapper_pg_dir是内核的页目录,ARM体系结构的内核页目录巨细界说为16k。init_mm界说了整个内核的
内存空间,内核线程归于内核代码,相同运用内核空间,其拜访内存空间的权限与内核相同。
1.2.5 内存结构初始化
bootmem_init (&meminfo)函数依据meminfo进行内存结构初始化。bootmem_init(&meminfo)函数中调
用 reserve_node_zero(bootmap_pfn, bootmap_pages) 函数,这个函数的效果是保存一部分内存使之
不能被动态分配。这些内存块包含:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end – &_stext); /*内核所占用地址空间*/
reserve_bootmem_node(pgdat, bootmap_pfn<
1.2.6 paging_init(&meminfo, mdesc)
创立内核页表,映射全部物理内存和IO空间,关于不同的处理器,该函数不同比较大。下面简略描绘一下ARM
体系结构的存储体系及MMU相关的概念。
在ARM存储体系中,运用内存办理单元(MMU)完结虚拟地址到实践物理地址的映射。运用MMU,可把SDRAM的
地址彻底映射到0x0开端的一片接连地址空间,而把本来占有这片空间的FLASH或许ROM映射到其他不相抵触
的存储空间方位。例如,FLASH的地址从0x0000 0000~0x00FFFFFF,而SDRAM的地址规模是
0x3000 0000~0x3lFFFFFF,则可把SDRAM地址映射为0x0000 0000~0xlFFFFFF,而FLASH的地址能够
映射到0x9000 0000~0x90FFFFFF(此处地址空间为闲暇,未被占用)。映射完结后,假如处理器产生反常,
假定仍然为IRQ中止,PC指针指向0xl8处的地址,而这个时分PC实践上是从坐落物理地址的0x3000 0018处
读取指令。经过MMU的映射,则可完结程序彻底运转在SDRAM之中。在实践的运用中.或许会把两片不接连的
物理地址空间分配给SDRAM。而在操作体系中,习惯于把SDRAM的空间接连起来,便利内存办理,且运用程序
恳求大块的内存时,操作体系内核也可便利地分配。经过MMU可完结不接连的物理地址空间映射为接连的虚拟
地址空间。操作体系内核或许一些比较要害的代码,一般是不期望被用户运用程序拜访。经过MMU能够操控地
址空间的拜访权限,然后维护这些代码不被损坏。
MMU的完结进程,实践上便是一个查表映射的进程。树立页表是完结MMU功用不行短少的一步。页表坐落体系的
内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度便是一个字的长度(在ARM中,
一个字的长度被界说为4Bytes)。页表项除完结虚拟地址到物理地址的映射功用之外,还界说了拜访权限弛缓
冲特性等。
MMU的映射分为两种,一级页表的改换和二级页表改换。两者的不同之处便是完结的改换地址空间巨细不同。
一级页表改换支撑1 M巨细的存储空间的映射,而二级能够支撑64 kB,4 kB和1 kB巨细地址空间的映射。
动态表(页表)的巨细=表项数*每个表项所需的位数,即为整个内存空间树立索引表时,需求多大空间寄存索
引表自身。
表项数=虚拟地址空间/每页巨细
每个表项所需的位数=Log(实践页表数)+恰当操控位数
实践页表数 =物理地址空间/每页巨细
1.3 parse_options()
剖析由内核引导程序发送给内核的发动选项,在初始化进程中依照某些选项运转,并将剩下部分传送给init进
程。这些选项或许现已存储在装备文件中,也或许是由用户在体系发动时敲入的。但内核并不关怀这些,这些
细节都是内核引导程序重视的内容,嵌入式体系更是如此。
1.4 trap_init() (/kernel/traps.c do_trap)
这个函数用来做体系相关的中止处理的初始化,在该函数中调用__trap_init((void *)vectors_base())
函数将exception vector设置到vectors_base开端的地址上。 __trap_init函数坐落entry-armv.S文
件中,关于ARM处理器,共有复位、未界说指令、SWI、预取停止、数据停止、IRQ和FIQ 几种办法。SWI首要
用来完结体系调用,而产生了IRQ之后,经过exception vector进入中止处理进程,履行do_IRQ函数。
armnommu的trap_init()函数在arch/armnommu/kernel/traps.c文件中。vectors_base是写中止向
量的开端地址,在include/asm-armnommu/proc-armv/system.h文件中设置,地址为0或0XFFFF0000。
print?
- ENTRY(__trap_init)
- stmfdsp!,{r4-r6,lr}
- mrsr1,cpsr@codefrom2.0.38
- bicr1,r1,#MODE_MASK@clearmodebits/*设置svc形式,disableIRQ,FIQ*/
- orrr1,r1,#I_BIT|F_BIT|MODE_SVC@setSVCmode,disableIRQ,FIQ
- msrcpsr,r1
- adrr1,.LCvectors@setupthevectors
- ldmiar1,{r1,r2,r3,r4,r5,r6,ip,lr}
- stmiar0,{r1,r2,r3,r4,r5,r6,ip,lr}/*复制反常向量*/
- addr2,r0,#0x200
- adrr0,__stubs_start@copystubsto0x200
- adrr1,__stubs_end
- 1:ldrr3,[r0],#4
- strr3,[r2],#4
- cmpr0,r1
- blt1b
- LOADREGS(fd,sp!,{r4-r6,pc})
__stubs_start到__stubs_end的地址中包含了反常处理的代码,因而复制到vectors_base+0x200的方位上。
1.5 init_IRQ()
print?
- void__initinit_IRQ(void)
- {
- externvoidinit_dma(void);
- intirq;
- for(irq=0;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;
- }
- CSR_WRITE(AIC_MDCR,0x7FFFE);/*disableallinterrupts*/
- CSR_WRITE(CAHCNF,0x0);/*CloseCache*/
- CSR_WRITE(CAHCON,0x87);/*FlushCache*/
- while(CSR_READ(CAHCON)!=0);
- CSR_WRITE(CAHCNF,0x7);/*OpenCache*/
- init_arch_irq();
- init_dma();
- }
这个函数用来做体系相关的irq处理的初始化,irq_desc数组是用来描绘IRQ的恳求行列,每一个中止号分配
一个irq_desc结构,组成了一个数组。NR_IRQS代表中止数目,这儿仅仅对中止结构irq_desc进行了初始
化。在默许的初始化完结后调用初始化函数init_arch_irq,先履行arch/armnommu/kernel/irq-
arch.c文件中的函数genarch_init_irq(),然后就履行 include/asm-armnommu/arch-xxxx/irq.h中
的inline函数irq_init_irq,在这儿对irq_desc进行了本质的初始化。其间mask用堵塞中止;unmask用
来撤销堵塞;mask_ack的效果是堵塞中止,一起还回应ack给硬件表明这个中止现已被处理了,不然硬件将再
次产生同一个中止。这儿,不是全部硬件需求这个ack回应,所以许多时分mask_ack与mask用的是同一个函
数。
接下来履行init_dma()函数,假如不支撑DMA,能够设置include/asm-armnommu/arch-xxxx/dma.h中
的 MAX_DMA_CHANNELS为0,这样在arch/armnommu/kernel/dma.c文件中会依据这个界说运用不同的函
数。
1.6 sched_init()
初始化体系调度进程,首要对定时器机制和时钟中止的Bottom Half的初始化函数进行设置。与时刻相关的初
始化进程首要有两步:(1)调用 init_timervecs()函数初始化内核定时器机制;(2)调用init_bh()函
数将BH向量TIMER_BH、TQUEUE_BH和 IMMEDIATE_BH所对应的BH函数别离设置成timer_bh()、
tqueue_bh()和immediate_bh()函数
1.7 softirq_init()
内核的软中止机制初始化函数。调用tasklet_init初始化tasklet_struct结构,软中止的个数为32个。用
于bh的 tasklet_struct结构调用tasklet_init()今后,它们的函数指针func全都指向bh_action()。
bh_action便是tasklet完结bh机制的代码,但此刻详细的bh函数还没有指定。
HI_SOFTIRQ用于完结bottom half,TASKLET_SOFTIRQ用于公共的tasklet。
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); /* 初始化公共的tasklet_struct要
用到的软中止 */
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); /* 初始化tasklet_struct完结的
bottom half调用 */
1.8 time_init()
这个函数用来做体系相关的timer的初始化,armnommu的在arch/armnommu/kernel/time.c。这儿调用了
在 include/asm-armnommu/arch-xxxx/time.h中的inline函数setup_timer,setup_timer()函数
的规划与硬件规划严密相关,首要是依据硬件规划状况设置时钟中止号和时钟频率等。
print?
- void__inline__setup_timer(void)
- {
- /*—–disabletimer—–*/
- CSR_WRITE(TCR0,xxx);
- CSR_WRITE(AIC_SCR7,xxx);/*settingpriorityleveltohigh*/
- /*timer0:100ticks/sec*/
- CSR_WRITE(TICR0,xxx);
- timer_irq.handler=xxxxxx_timer_interrupt;
- setup_arm_irq(IRQ_TIMER,&timer_irq);/*IRQ_TIMERistheinterruptnumber*/
- INT_ENABLE(IRQ_TIMER);
- /*Clearinterruptflag*/
- CSR_WRITE(TISR,xxx);
- /*enabletimer*/
- CSR_WRITE(TCR0,xxx);
- }
1.9 console_init()
操控台初始化。操控台也是一种驱动程序,因为其特殊性,提早到该处完结初始化,首要是为了提早看到输出
信息,据此判别内核运转状况。许多嵌入式Linux操作体系因为没有在/dev目录下正确装备console设备,造
成发动时产生比方unable to open an initial console的过错。
/*******************************************************************************/
init_modules()函数到smp_init()函数之间的代码一般不需求作修正,
假如渠道具有特殊性,也只需对相关函数进行必要修正。
这儿简略注明晰一下各个函数的功用,以便了解。
/*******************************************************************************/
1.10 init_modules()
模块初始化。假如编译内核时使能该选项,则内核支撑模块化加载/卸载功用
1.11 kmem_cache_init()
内核Cache初始化。
1.12 sti()
使能中止,这儿开端,中止体系开端正常作业。
1.13 calibrate_delay()
近似核算BogoMIPS数字的内核函数。作为第一次预算,calibrate_delay核算出在每一秒内履行多少次
__delay循环,也便是每个定时器滴答(timer tick)―百分之一秒内延时循环能够履行多少次。这种核算只
是一种预算,成果并不能准确到纳秒,但这个数字供内核运用现已满足准确了。
BogoMIPS的数字由内核核算并在体系初始化的时分打印。它近似的给出了每秒钟CPU能够履行一个短推迟循环
的次数。在内核中,这个成果首要用于需求等候十分短周期的设备驱动程序――例如,等候几微秒并查看设备的
某些信息是否现已可用。
核算一个定时器滴答内能够履行多少次循环需求在滴答开端时就开端计数,或许应该尽或许与它挨近。大局变
量jiffies中存储了从内核开端坚持盯梢时刻开端到现在现现已过的定时器滴答数, jiffies坚持异步更
新,在一个中止内——每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后持续方才的作业。
1.14 mem_init()
内存初始化。本函数经过内存碎片的重组等办法符号当时剩下内存, 设置内存上下界和页表项初始值。
1.15 kmem_cache_sizes_init()
内核内存办理器的初始化,也便是初始化cache和SLAB分配机制。
1.16 pgtable_cache_init()
页表cache初始化。
1.17 fork_init()
这儿依据硬件的内存状况,假如核算出的max_threads数量太大,能够自行界说。
1.18 proc_caches_init();
为proc文件体系创立高速缓冲
1.19 vfs_caches_init(num_physpages);
为VFS创立SLAB高速缓冲
1.20 buffer_init(num_physpages);
初始化buffer
1.21 page_cache_init(num_physpages);
页缓冲初始化
1.22 signals_init();
创立信号行列高速缓冲
1.23 proc_root_init();
在内存中创立包含根结点在内的全部节点
1.24 check_bugs();
查看与处理器相关的bug
1.25 smp_init();
1.26 rest_init(); 此函数调用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函数。
1.26.1 kernel_thread()函数剖析
这儿调用了arch/armnommu/kernel/process.c中的函数kernel_thread,kernel_thread函数中经过
__syscall(clone) 创立新线程。__syscall(clone)函数拜见armnommu/kernel目录下的entry- common.S文件。
1.26.2 init()完结下列功用:
Init()函数经过kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回调
函数履行,完结下列功用。
do_basic_setup()
在该函数里,sock_init()函数进行网络相关的初始化,占用相当多的内存,假如所开发体系不支撑网络功
能,能够把该函数的履行注释掉。
do_initcalls()完结驱动的初始化, 这儿需求与vmlinux.lds联系起来看才干理解其间微妙。
print?
- staticvoid__initdo_initcalls(void)
- {
- initcall_t*call;
- call=&__initcall_start;
- do{
- (*call)();
- call++;
- }while(call<&__initcall_end);
- /*Makesurethereisnopendingstufffromtheinitcallsequence*/
- flush_scheduled_tasks();
- }
查看 /arch/i386/vmlinux.lds,其间有一段代码
print?
- __initcall_start=.;
- .initcall.init:{*(.initcall.init)}
- __initcall_end=.;
其意义是__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾。
do_initcalls所作的是体系中有关驱动部分的初始化作业,那么这些函数指针数据是怎样放到了.initcall.init节呢?在include/linux/init.h文件中有如下3个界说:
1. #define __init_call __attribute__ ((unused,__section__ (“.initcall.init” ))
__attribute__的意义便是构建一个在.initcall.init节的指向初始函数的指针。
2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn
##意思便是在可变参数运用宏界说的时分构建一个变量称号为所指向的函数的称号,而且在前面加上__initcall_
3. #define module_init(x) __initcall(x);
许多驱动中都有相似module_init(usb_init)的代码,经过该宏界说逐层解说寄存到.initcall.int节
中。
blkmem相关的修正(do_initcalls()初始化驱动时履行此代码)
在blkmem_init ()函数中,调用了blk_init_queue()函数,blk_init_queue()函数调用了blk_init_free_list()函数, blk_init_free_list()函数又调用了blk_grow_request_list()函
数,在这个函数中会 kmem_cache_alloc出nr_requests个request结构体。
这儿假如nr_requests的值太大,则将占用过多的内存,将形成硬件内存不行,因而能够依据实践状况将其替
换成了较小的值,比方32、16等。
free_initmem
这个函数在arch/armnommu/mm/init.c文件中,其效果便是对init节的开释,也能够经过修正代码指定为
不开释。
1.26.3 init履行进程
在内核引导完毕并发动init之后,体系就转入用户态的运转,在这之后创立的全部进程,都是在用户态进行。
这儿先要清楚一个概念:便是init进程虽然是从内核开端的,即在前面所讲的init/main.c中的init()函数
在发动后就现已是一个中心线程,但在转到履行init程序(如 /sbin/init)之后,内核中的init()就变成
了/sbin/init程序,状况也改动成了用户态,也便是说中心线程变成了一个一般的进程。这样一来,内核中
的init函数实践上仅仅用户态init进程的进口,它在履行execve(“/sbin/init”,argv_init,
envp_init)时改动成为一个一般的用户进程。这也便是exec函数的天地大移动法,在exec函数调用其他程
序时,当时进程被其他进程“魂灵附体”。
除此之外,它们的代码来历也有不同,内核中的init()函数的源代码在/init/main.c中,是内核的一部
分。而/sbin/init程序的源代码是运用程序。
init程序发动之后,要完结以下使命:查看文件体系,发动各种后台服务进程,最后为每个终端和虚拟操控台
发动一个getty进程供用户登录。因为全部其它用户进程都是由init派生的,因而它又是其它全部用户进程的
父进程。
init进程发动后,依照/etc/inittab的内容进程体系设置。许多嵌入式体系用的是BusyBox的init,
它与一般所运用的init不相同,会先履行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。