You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在广州市图书馆偶然间看到尹锡训 -《ARM Linux内核源码剖析》(코드로 알아보는 ARM 리눅스 커널)1,这个书我觉得非常适合我现在看。本书涉及Linux内核启动和处理,紧密和ARM架构机制相结合,一方面可以加深对ARM硬件机制的理解,另一方面能够科普Linux内核内设机制,作为Linux内核机制的入门,相当于从ARM迈入Linux的桥梁。本书需要结合ARM手册和Linux内核两本书同时参考。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j4
(....)
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
Kernel: arch/arm/boot/Image is ready
LDS arch/arm/boot/compressed/vmlinux.lds
AS arch/arm/boot/compressed/head.o
GZIP arch/arm/boot/compressed/piggy.gzip
CC arch/arm/boot/compressed/misc.o
CC arch/arm/boot/compressed/decompress.o
CC arch/arm/boot/compressed/string.o
AS arch/arm/boot/compressed/lib1funcs.o
AS arch/arm/boot/compressed/ashldi3.o
AS arch/arm/boot/compressed/bswapsdi2.o
AS arch/arm/boot/compressed/piggy.gzip.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
3. Decompress the kernel image
------------------------------
Requirement: OPTIONAL
The AArch64 kernel does not currently provide a decompressor and
therefore requires decompression (gzip etc.) to be performed by the boot
loader if a compressed Image target (e.g. Image.gz) is used. For
bootloaders that do not implement this requirement, the uncompressed
Image target is available instead.
/** Kernel startup entry point.*---------------------------** This is normally called from the decompressor code. The requirements* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,* r1 = machine nr, r2 = atags pointer.** This code is mostly position independent, so if you link the kernelat*0xc0008000, you call thisat__pa(0xc0008000).** See linux/arch/arm/tools/mach-types for the complete list of machine* numbers for r1.** We're trying to keep crap to a minimum; DO NOT add any machine specific* crap here - that's whatthe boot loader (orin extreme, well justified* circumstances, zImage) is for.*/ .section".text.head","ax"ENTRY(stext) msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15,0,r9, c0, c0 @ get processor idbl __lookup_processor_type @ r5=procinfo r9=cpuidmovsr10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p'bl __lookup_machine_type @ r5=machinfomovsr8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a'bl __vet_atagsbl __create_page_tables /** The following callsCPUspecific code in a position independent* manner. See arch/arm/mm/proc-*.S for details. r10 = base of* xxx_proc_info structure selected by __lookup_machine_type* above. On return, theCPUwill be ready for the MMU to be* turned on,and r0 will hold theCPUcontrol register value.*/ ldr r13, __switch_data @ address to jump to after @ mmu has been enabled adr lr, __enable_mmu @ return (PIC) addressadd pc,r10, #PROCINFO_INITFUNCENDPROC(stext)
Linux内核从启动加载项接受三个参数与。在ARM中循序AAPCS(procedure call stardard for the ARM architecture)标准的时候,少于4个的参数被分配在r0-r3之中,对于5个以上的参数,其前四个参数在r0-r3志宏,而之后的参数则进入栈。tagged list由struct tag数组组成,包含内存、视频、serial、initrd、revision、cmdline等信息。
/** Setupcommonbits before finally enabling the MMU. Essentially* this is just loading the page table pointer and domain access* registers.*/__enable_mmu:#ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #CR_A#else bic r0, r0, #CR_A#endif#ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C#endif#ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z#endif#ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I#endifmov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ domain_val(DOMAIN_IO, DOMAIN_CLIENT)) mcr p15,0, r5, c3, c0,0 @ load domain access register mcr p15,0, r4, c2, c0,0 @ load page table pointer b __turn_mmu_onENDPROC(__enable_mmu)/** Enable the MMU. This completely changes the structure of the visible* memory space. You will not be able to trace execution through this.* If you have an enquiry about this,*please* check the linux-arm-kernel* mailing list archives BEFORE sending another post to the list.** r0 = cp#15 control register*r13 = *virtual* address to jump to upon completion** other registers depend on the function called upon completion*/ .align5__turn_mmu_on:mov r0, r0 mcr p15,0, r0, c1, c0,0 @ write control reg mrc p15,0, r3, c0, c0,0 @ read id regmov r3, r3mov r3, r3mov pc,r13ENDPROC(__turn_mmu_on)
MMU和cache实际上有些关联的。在ARM64中,SCTLR,System control register 用来控制MMU的caches,虽然这几个控制bit是分开的,但是不意味着的mmu,dcache,icache的开关是彼此独立的。一般而言,MMU和d-cache是相互绑定的,这个在arm里面我们已经讨论过,启动d-cache是需要mmu做内存保护(比如内存属性、共享属性)的合法性检查都在MMU的页表中保存着,如果没有MMU对页表的翻译,那么dcache根本是无头苍蝇,因此MMU和d-cache在arm64里面成对出现。这样的设定也并非强制,但必须保证内存的非device,是non-shareable的的。
4.2.1 preserve_boot_args
保存boot loader传递过来的参数,根据文档要求,需要初始化CPU的通用寄存器设定:
x0 = 在系统RAM中的dtb的物理地址
x1、x2、x3 = 0,留着以后用
/** Preserve the arguments passed by the bootloader in x0 .. x3*/preserve_boot_args:mov x21, x0 // x21=FDT @保存dtb地址到x21寄存器 adr_l x0, boot_args // record the contents of @x0保存boot_args变量地址 stp x21, x1,[x0] // x0 .. x3atkernel entry @ stp x2, x3,[x0, #16] dmb sy // needed before dc ivac with // MMU off @ memory barriermov x1, #0x20 // 4 x 8 bytes @ 传递给inval_dcache_area参数 b __inval_dcache_area // tail callENDPROC(preserve_boot_args)
/** Sets the __boot_cpu_mode flag depending on theCPUboot mode passed*in x20. See arch/arm64/include/asm/virt.h for more info.*/ENTRY(set_cpu_boot_mode_flag) adr_l x1, __boot_cpu_modecmp w20, #BOOT_CPU_MODE_EL2 b.ne 1fadd x1, x1, #41: str w20,[x1] // ThisCPUhas booted in EL1 dmb sy @ x1还有别的CPU在用,需要保证一致性,确保x1被刷进来 dc ivac, x1 // Invalidate potentially stale cache lineretENDPROC(set_cpu_boot_mode_flag)
/** Determine validity of the x21 FDT pointer.* The dtb must be 8-byte aligned and live in the first 512M of memory.*/__vet_fdt: tst x21, #0x7 @是不是8byte对齐 b.ne 1fcmp x21, x24 @ 是否小于kernel space的首地址 b.lt 1fmov x0, #(1 << 29)add x0, x0, x24cmp x21, x0 @ 是否大于kernel space的首地址 + 512M b.ge 1fret1:mov x21, #M @ 传递fdt地址有误。retENDPROC(__vet_fdt)
0x01_Linux内核的启动(一)之启动前准备
在广州市图书馆偶然间看到尹锡训 -《ARM Linux内核源码剖析》(코드로 알아보는 ARM 리눅스 커널)1,这个书我觉得非常适合我现在看。本书涉及Linux内核启动和处理,紧密和ARM架构机制相结合,一方面可以加深对ARM硬件机制的理解,另一方面能够科普Linux内核内设机制,作为Linux内核机制的入门,相当于从ARM迈入Linux的桥梁。本书需要结合ARM手册和Linux内核两本书同时参考。
1. 内核构建系统
操作系统必然是一个非常庞大和复杂的系统2。由调度程序、文件系统、内存管理、网络系统等诸多子系统组成。这种内核十分庞大,但是其生成只需要一个二进制启动文件(zImage/bzImage)。可以将zImage文件就视为内核elf文件。
1.1 内核初始化和配置
内核初始化状态是从kernel.org下载的tar.gz内核源程序压缩包,解压之后为内核源码的代码树,这种状态叫做内核的初始状态。
使用
make mrproper
和make distclean
等指令可以内核源码恢复到内核初始状态。注意:内核在初始状态不可以直接编译,虽然可以生成vmlinux(没有压缩的内核文件3),但大部分情况会引起内核严重错误(kernel panic)。因此,需要执行最重要、最需要谨慎处理的**内核配置(kernel configuration)**的过程,也是生成编译必要.config文件的过程。注意:
kernel的配置的单位是 SoC级的
1.2 内核的构建和安装
生成.config文件之后,就可以开始构建内核(kernel building),相当于从kernel源代码 -> zImage的过程。如果從kernel編譯過程來看vmlinux是如何組成的話4:
這裡列出比較重要的幾個:
vmlinux
vmlinux
是ELF格式binary檔案,為最原始也未壓縮的kernel鏡像。./vmlinux
System.map
./System.map
Image
vmlinux
經過objcopy
處理,把代碼從中抽出(去除註解或debugging symbols)以用於形成可執行的機器碼。不過Image
此時還不能直接執行,需加入metadata資訊。./arch/arm/boot/Image
head.o
head.S
是用組語(arm-assembly)寫成。./arch/arm/boot/compressed/head.S
piggy.gzip
./arch/arm/boot/compressed/piggy.gzip
piggy.gzip.o
piggy.gzip
。./arch/arm/boot/compressed/piggy.gzip.S
misc.o
./arch/arm/boot/compressed/misc.c
compressed/vmlinux
System.map
等檔案並產生鏡像檔,意義跟一開始的vmlinux
不太一樣。./arch/arm/boot/compressed/vmlinux
zImage
./arch/arm/boot/zImage
vmlinux是ELF格式的二进制映像,由文件系统、网络、设备驱动及调度程序组成,是zimage之前的原始文件,构建内核过程均需要各个部件通过linker组合成一个名为vmlinux的较大的ELF格式对象文件。图源于链接5
编译后的内核会生成ELF格式的二进制文件vmlinux,具备内核的所有要素。vmlinux通过gzip压缩成为piggy.o和head.o, misc.o执行链接,最后生成zImage二进制文件。内核的内存加载及执行都能通过zImage提高速度,特别是head.o和misc.o相当于引导程序加载项。
内核最后安装生成与/boot文件夹下。
2. 解压内核 decompressed kernel (ARMv7 only)
2.1 ARMv8
在ARMv8中已经drop掉了关于对Linux内核解压的部分,实际上可以在arch/arm64/boot/的下面看不到了compressed/head.S的身影了。这部分原因我们可以在booting arch64里面可以看到说明6:
基本的意思是在arm64内核的下面不再默认提供解压工具,如果有需要需要在boot组件中实现。那么相应的,在AArch64 kernel image decompression的一封信件中,可以找到uboot的确是对aarch64这块重新做了支持7。更多的工程师也倾向于拿掉内核的压缩和解压部分,甚至对uboot的uImage压缩提出了挑战设计。笔者认为,在arch64这样的高性能架构,已经无需在对vmlinux的大小做太多的考虑,可能只有在arch64一些size sensitive的情境下面才需要进行压缩。
而且,在linux kernel里面存在两个head.S也十分让人费解。在ARMv7的手册里还对这部分做了解释8,arch/arm/boot/compressed/head.S和arch/arm/kernel/head.S,前者主要是对在early-boot阶段对zImage的解压,对vmlinux的还原。的确,去掉更好,更有助于消除内核歧义。
2.2 ARMv7
内核实际的启动节点是
start_kernel()
函数,在这个调用中再去调用100多个子函数执行linux的启动。但是调用start_kernel之前,也有一部分工作要处理:本章以ARMv7和ARMv8架构为例子,研究Linux内核启动前准备。
2.2.1 zImage pre-decompressed
zImage在开始前,需要对处理器进行一些初始化操作,为解压做一些准备。这个过程包含:中断禁止、分配动态内存、初始化BSS区、初始化页目录、打开cache等任务。然后后面会为解压数据进行空间数据(页目录空间)。
2.2.1.1 start标签
通过启动加载项完成对软硬件的默认初始化任务之后,最先执行的是
arch/arm/boot/compressed/head.S
,代码如下arch/arm/boot/compressed/head.S 。start标签是第一个执行的代码,在start标签中,从启动加载项接手ID和atags的信息。此外,还会禁用中断及初始化寄存器,并跳转到not_relocated标签(用于初始化bss)。2.2.1.2 bss系统域初始化- not_relocated标签
2.2.1.3 激活缓存-cache_on
如果已经完成了bss区域的初始化和动态区域的设置,解压内核的最后准备就是执行cache_on。Linux中的cacheon的实现方式根据arm的机构版本不同是不同的。在cache_on标签中查找并调用当前系统ARM结构版本相符程序的子程序,另外,调用__setup_mmu子程序对页目录进行初始化。
2.2.1.4 页表项初始化 - __setup_mmu
__setup_mmu标签在cache_on标签内部调用,用于初始化解压内核的页表项。特别是对内存的256MB区域设置cacheable, bufferable,这是因为解压内核的时候,使用cache和writebuffer提高解压性能。
3. 从zImage还原vmlinux(ARMv7 only)
Linux内核之前通过not_relocated和cache_on做好解压之前的准备工作,并且malloc了空间,做好了对vmlinux还原的准备。本节通过gunzip对压缩后的内核zImage执行解压,并调用与当前处理器的ARM结构对应的cache函数。最终将PC移动到zImage解压的位置,使解压之后的内核能够执行。
Note, gunzip位于内核源码arch/arm/boot/compressed/misc.c中。
3.1 wont_overwrite
wont_overwrite在zImage准备解压的最后一项工作,在这个步骤里面,上一个阶段的需要传递参数过来。这里面有个知识点,CONFIG_ZBOOT_ROM9。这项配置指示压缩的bootloader是不是在ROM/flash当中,根据文献10,分为了两种模式,一种是PIC模式和ROM模式,PIC模式只有在RAM里面有相应的程序,load内核之后可能会把这部分程序覆盖;而ROM模式,将RAM和ROM地址分开,load内核之后不会将这部分程序覆盖。使用PIC模式直接CONFIG_ZBOOT_ROM=n即可。
要注意到,这部分程序的时候对GOT全局表进行了展开。
decompress_kernel是解压zImage的子程序,这样引导加载项(bootstrap loader)的所有操作基本结束,跳转到call kernel标签执行内核代码初始化操作。查看代码:https://elixir.bootlin.com/linux/v2.6.30.4/source/arch/arm/boot/compressed/head.S#L537
这部分已经在linux2.6.30+中去掉了。banch target cache是流水线中的指令cache,清除btc实际上是刷流水线。
第一步,是cache失效并刷回到ram里面;
第二步,关闭开始mmu,使cache和tlb失效,清除btc(branch target cache,分支目标缓存)。
第三部**,控制权移动到arch/arm/kernel/head.S中,正式执行内核初始化,并且把结构ID和atags指针作为传递参数。**
对于
__cache_off
,在linux这样处理:真正意义的cache off还需要invalidate所有的cache和TLB。
4. 调用start_kernel(ARMv7/v8)
这个部分是调用start_kernel()函数的最后阶段。正式启动内核之前,先检查自身处理器的信息和机器信息的结构体的位置,确认从启动加载项接受的atag信息是不是有效。ARMv7和ARMv8有着不同的流程,我们需要在这里开始做出ARMv7和ARMv8两个处理器的分支划分。
4.1 ARMv7
如果自身处理器信息和机器有误,程序就立即结束。在检查完所有信息合法性的基础上,设置页表的MMU标识并激活,最终调用C代码编成start_kernel()内核起始函数。
4.1.1 初始化指向 --- stext标签
通过引导加载项(该程序负责将压缩的内核加载到内存,并执行解压等内核启动所需的操作)加载内核之后,首先执行的部分就是stext。执行该标签时要求如下状态。
在进入stext标签时候,首先转换armv7需要转换到SVC模式(SVC_MODE),并禁止IRQ。然后调用合法性检查程序,主要针对于CPU和平台信息,并检查atag信息,追加设置页表之后启动MMU。
切换SVC模式,armv7通过下面的指令进行。
4.1.2 processor / machine and atags
关键点:虚拟地址转物理地址
如何搜寻的过程,这里暂时不整理,这部分可以参考《ARMLinux内核源码剖析》的第73页。这里有比较重要的处理是在禁用MMU的情况下,虚拟地址转化为物理地址的一个方法。程序走到这里的时候,由于禁用了MMU,因此无法通过虚拟地址访问内存。但是,保存处理器信息的区域地址__proc_info_begin和 _proc_info_end是编译的时候指定的,所以都是虚拟地址。因此,只有将这些虚拟地址转换为物理地址,才能正确的访问处理器新的的proc_info_list的结构体。
这里的做法是做偏移处理,将偏移的量放在一个寄存器里面,然后作为数值运算:
关键点:atags
Linux内核从启动加载项接受三个参数与。在ARM中循序AAPCS(procedure call stardard for the ARM architecture)标准的时候,少于4个的参数被分配在r0-r3之中,对于5个以上的参数,其前四个参数在r0-r3志宏,而之后的参数则进入栈。tagged list由struct tag数组组成,包含内存、视频、serial、initrd、revision、cmdline等信息。
tagged list不得被内核解压器(decompresssor)或者bootp程序覆写(overwrite),因此主要位于RAM的第一个16KB。在uboot中可以查看bootm.c的setup_start_tag函数,已经setup_end_tag中设置的ATAG_CORE/NONE代码12。
4.1.3 __create_page_tables
在打开MMU之前,Linux内核需要为MMU建立好页表。虚拟内存的作用我们在ARMv8的MMU管理中已经把整个历史go-through了一遍,在这里我们可以简单的再去复习一下。在32位操作系统中,处理器具有4G的虚拟内存,ARMv7架构是32位的处理器架构,在不开large memory的情况下最高也只能支持4GB的虚拟内存访问,这里的4GB虚拟内存指的并不是所有进程合在一起的内存为4G,而是每一个进程看到的都是4GB的虚拟地址空间。
这就是MMU的作用,MMU让每一个进程都有独占了所有内存的幻象。而这种技术的底层支持,也是来源于现代内存管理的方式——分页管理及换入换出技术。例如图中两个进程,被MMU映射到不同的物理地址页帧上面,两个进程无法感知对方的映射。
而且这样的分页技术提供了一个好处,就是进程和进程之间的内存共享。通过MMU映射相同的物理页帧就可以达到这样的效果。__create_page_tables 中,需要创建一个16K大小的页表项(包含4096个虚拟地址和物理地址的映射关系),这个页表项也是在ram上面开辟的。
__create_page_tables
中,KERNEL_RAM_PADDR相聚0x4000的位置(KERNEL_RAM_PADDR-0x4000)到KERNEL_RAM_PADDR所有页表项执行循环,并初始化为0。对相当于内核区域的项设置节区基址和cacheable,bufferable值。执行__create_page_tables之后的页表如图所示:4.1.4 设置core(__v6_setup)标签
完成
__create_page_tables
之后,运行__v6_setup
程序设置当前处理器。根据不同的ARM架构,处理器初始化函数执行过程也是不一致的。vx的时候就调用vx_setup,调用之后就开始执行初始化任务。setup的流程是: config_smp -> config_mmu
4.1.4.1 config SMP
在ARM里面引入了cluster的概念,一个cluster内有多个相同处理器,在setup程序中会将SMP模式激活,并启动SCU(snoop control unit)(如果有的话)用于保持多个core之间的cache一致性。
除此之外,需要对cache机制进行使失效操作。哈佛体系结构和混合结构都可以执行初始化。清理所有的cache失效之后,清空写缓冲。清空操作中使用的协处理器命令是数据同步屏障(data synchronized barrier)。
使TLB失效之后,在寄存器TTB1中保存页表起始地址。最后,讲MMU、cache、分离预测这些功能设置值保存到寄存器r0中,并跳转到
__enable_mmu
。4.1.4.2 enable mmu
为了控制MMU的运行,使用协处理器15(CP15)的各个寄存器,控制CPc1寄存器将集成MMU控制系统中默认寄存器的作用。c1寄存器的部分位域如下所示:
在enable mmu的过程中,设置所需要的位激活MMU硬件,将访问寄存器和页表指针值加载到ARM处理器的寄存器。为寄存器r0设置与MMU相关的精简为,激活MMU硬件,使虚拟内存可用。
4.1.5 跳转start_kernel
从
__mmap_switched
标签开始,MMU处于激活装填,代码通过非PIC的绝对地址执行。从start_kernel开始,代码由C语言编写而成,因此需要__switch_data
标签中设置的data\bss\stack值,并调用start_kernel后。_switch_data
在arch/arm/kernel/head-common.S中。4.2 ARMv8
ARMv8的aarch32执行状态等效于ARMv7,这里ARMv8限定为aarch64执行状态。ARMv8相对来说比较复杂,因为ARMv8相比于ARMv7包含了多个异常等级(EL0-EL3),而EL0和EL1的counterpart有secure和non-secure(EL2是hypervisor的层级,只有non-secure的概念;而EL3是安全等级最高的层级,只有secure的概念)。对于ARMv8 aarch64而言,因此在kernel start的时候要做更多的更充分的准备。
对于aarch64而言,linux kernel在调用kernel start之前,需要boot loader(boot loader和boot中的bootloader意义不一致,这个广义性的包含启动内核之前所有的boot程序,而在bootloader中,仅仅指的是boot stage的第一阶段)至少要做完以下工作:
初始化RAM
kernel对于启动加载项的一个要求就是必须是找到并且初始化所有未来kernel内核将会用到的存储在系统上的volatile数据。这项初始化要求要根据不同的硬件平台(启动加载项可能是使用一些内部的算法自动的定位到所有的ram,亦或是使用平台级的初始化程序,这要看boot的设计者如何设计)
设定设备树
设备树blob (dtb)必须要被放置在内核映像的开始512Mbytes内的8字节边界的位置,并且不可以横跨2MBytes的界限。这个操作允许内核使用一个在初始状态页表内单独的数据段去映射dtb。
解压内核镜像
AArch64不再提供解压程序(gzip),如果一定要加载压缩的内核镜像,那么这部分工作需要在boot loader中执行解压操作。
调用内核的接口
在解压之后的内核镜像中,必须包含一个64byte的头文件:
这里引用linux kernel document下面的对于aarch64的一些建议13:
与armv7架构实际上是一致的,boot loader和kernel之间需要交互,以及kernel需要保存boot loader的参数并对参数进行校验,最后将控制权转换给kernel,并在此之前做一些SoC上的初始化工作。
根据在boot.txt文档中提到的概念,控制权移交给kernel至少在硬件上要follow以下的配置:
MMU和cache实际上有些关联的。在ARM64中,SCTLR,System control register 用来控制MMU的caches,虽然这几个控制bit是分开的,但是不意味着的mmu,dcache,icache的开关是彼此独立的。一般而言,MMU和d-cache是相互绑定的,这个在arm里面我们已经讨论过,启动d-cache是需要mmu做内存保护(比如内存属性、共享属性)的合法性检查都在MMU的页表中保存着,如果没有MMU对页表的翻译,那么dcache根本是无头苍蝇,因此MMU和d-cache在arm64里面成对出现。这样的设定也并非强制,但必须保证内存的非device,是non-shareable的的。
4.2.1 preserve_boot_args
保存boot loader传递过来的参数,根据文档要求,需要初始化CPU的通用寄存器设定:
这里面有几个比较好的点,从文献14,提到的以下几点值得注意:
MMU禁止后虚拟地址的处理
这部分在armv7也遇到过,在没有开MMU的情况下,访问的变量是虚拟地址的,armv7给的处理方式通过计算offset的方式来得到物理地址。armv8有着完全不同的处理方法,我们boot_args变量在内存中是虚拟地址,但是MMU 和 d-cache都是关闭状态,因此写入boot_args并非直接写入到cache中,而是直接写入RAM。访问这个变量是通过
adr_l
这个宏完成,内部是通过adrp指令来做的,adrp是和pc为相对位置的指令,这样就避开了armv7使用offset的方式来获取。4.2.2 el2_setup
在kernel启动的时候,根据boot.txt的要求,SoC状态可能是EL1也可能是EL2,如果在EL2我们就会拥有更大的权限并且有更高的责任(要保证EL2的机制完善)。如果是在EL1和传统的armv7架构的setup流程没有什么太大的差别,而在EL2场景就稍微复杂一点,还需要兼顾虚拟化状态的基本设定,然后将CPU降低到EL1进行设定。
https://github.com/carloscn/imx-linux-4.1.15/blob/master/arch/arm64/kernel/head.S#L504
从上述代码中可以得到el2_setup的流程可以表述为:
currentEL
中获取当前EL等级所处的位置sctlr_el2
配置在EL2时候地址翻译的大小端endian(如果是el1模式,那么就访问sctlr_el1
)hcr_el2
寄存器midr_el1
和vmpidr_el2
中读取arch信息,processor id信息为什么无法从el3开始setup? 14
当一个SoC设计支持了EL3的支持,理所应当的应该从EL3逐级开始进行初始化,而为什么限定最高为EL2等级。我们从secure boot的综述文章中可以拿到以下的图,可以看出在ARMv8之前由于trustzone的支持,optee要进行boot,此时optee作为secure platform firmware,它会进行硬件平台的初始化,然后将控制权限交给uboot次级引导,而并不是直接由linux kernel进行cpu的控制。对于linux kernel而言,它无法感知secure world。因此 linux kernel并不是el3开始启动。
4.2.3 set_cpu_boot_mode_flag
顾名思义,这个步骤是设定cpu启动模式。w20寄存器保存cpu启动时候的异常等级。
https://github.com/carloscn/imx-linux-4.1.15/blob/master/arch/arm64/kernel/head.S#L594
本质上我们希望所有的cpu在初始化 的时候都处于同样的mode,要么是EL2,要么都是EL1。所有的cpu core在启动的时候都处于EL2 mode表示支持虚拟化,只有在这种情况下,kvm模块才能被顺利启动。这个函数会在每一个cpu上面执行14
4.2.4 __vet_fdt
x21寄存器会被保存boot_args中传递进来的fdt的物理地址,x24被定义为
_PHYS_OFFSET
#define __PHYS_OFFSET (KERNEL_START - TEXT_OFFSET)
#define KERNEL_START _text
为kernel开始运行的虚拟地址,在https://github.com/carloscn/imx-linux-4.1.15/blob/master/arch/arm64/kernel/vmlinux.lds.S#L84 被设定为:因此,
_PHYS_OFFSET
为kernel image的首地址。__vet_fdt主要是对fdt参数进行校验附录
Ref
Footnotes
ARM Linux内核源码剖析 ↩
Linux Kernel Map ↩
几种linux内核文件的区别(vmlinux、zImage、bzImage、uImage、vmlinuz、initrd ) ↩
Chapter 5 : Kernel Initialization ↩
Decompressed vmlinux: linux kernel initialization from page table configuration perspective ↩
Booting AArch64 Linux ↩
AArch64 kernel image decompression ↩
ARM Cortex-A Series Programmer's Guide for ARMv7-A - Kernel entry ↩
CONFIG_ZBOOT_ROM: Compressed boot loader in ROM/flash ↩
[PATCH] Clean up ARM compressed loader ↩
Linux Config Help - Compressed boot loader in ROM/flash ↩
uboot以tag方式给内核传参 ↩
booting.txt ↩
ARM64的启动过程之(一):内核第一个脚印 ↩ ↩2 ↩3
The text was updated successfully, but these errors were encountered: