内核启动过程中对CPU型号的确认
Posted 代二毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核启动过程中对CPU型号的确认相关的知识,希望对你有一定的参考价值。
1、内核为什么要确认CPU型号
内核和CPU都是不断发展的,内核会不断的更新版本,CPU会不断的出新型号。每当厂商推出一款新的CPU都需要移植内核,使内核能在新款CPU上运行。如果我们将没有针对该款CPU移植过的内核放到该款CPU上运行,结果就是运行不起来。所以内核在内部维护了一张支持CPU型号的表,在启动时内核会确认当前CPU型号是否在表中,如果不在,说明内核不支持该款CPU,终止启动。
2、内核维护支持的CPU型号表
2.1、 * (.proc.info.init)段
/*摘抄自内核的链接文件*/
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
内核在链接时会将proc.info.init段属性的代码链接在一起,标号__proc_info_begin代表段的起始地址,标号__proc_info_end代表段的结束地址。proc.info.init段都是支持的CPU型号的信息,在启动时就根据段的起始地址和结束地址,在表格中遍历看是否能找到当前的CPU型号。
2.2、描述CPU信息的"proc_info_list"结构体
struct proc_info_list
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
;
proc_info_list结构体就是用来描述CPU信息的,
2.3、把CPU对应的proc_info_list结构体放入 * (.proc.info.init)段
.section ".proc.info.init", #alloc, #execinstr
/*
* Match any ARMv7 processor core.
*/
.type __v7_proc_info, #object
__v7_proc_info:
.long 0x000f0000 @ Required ID value
.long 0x000f0000 @ Mask for ID
.long PMD_TYPE_SECT | \\
PMD_SECT_AP_WRITE | \\
PMD_SECT_AP_READ | \\
PMD_FLAGS
.long PMD_TYPE_SECT | \\
PMD_SECT_XN | \\
PMD_SECT_AP_WRITE | \\
PMD_SECT_AP_READ
b __v7_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP
.long cpu_v7_name
.long v7_processor_functions
.long v7wbi_tlb_fns
.long v6_user_fns
.long v7_cache_fns
.size __v7_proc_info, . - __v7_proc_info
对于ARM架构的CPU,表示支持的CPU的源码定义在在arch/arm/mm/目录下。上面的proc_info_list结构体的信息摘抄自arch/arm/mm/proc-v7.S文件,任何ARMv7架构的CPU都是适用此结构体里的信息。
(1).section “.proc.info.init”:表示此处开始的内容属于".proc.info.init"段,也就是后面的proc_info_list结构体,在链接的程序的时候就会把proc_info_list结构体链接在一起,再根据标号的开始、结束地址去遍历proc_info_list结构体。
3.内核中启动时确认CPU型号
@摘抄自内核启动的汇编代码
mrc p15, 0, r9, c0, c0 @ get processor id, CPU的ID号
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
(1)从CP15协处理器的c0寄存器读出CPU的ID号到r9寄存器;
(2)调用__lookup_processor_type函数查询内核支持的CPU型号中是否有当前CPU;
(3)如果没有匹配到CPU型号,则r5寄存器的值是0;如果查询到则r5寄存器的值是CPU对应proc_info_list结构体的地址;
(5)没有匹配到CPU型号则报错,终止启动;
3.1、__lookup_processor_type汇编函数
@此时r9保存的是从协处理器读取到的processor id
__lookup_processor_type:
adr r3, 3f @将往后的标号3的地址加载到r3寄存器中,也就是__proc_info_begin
ldmia r3, r5 - r7
add r3, r3, #8 @r3=r3+8,此时r3寄存器的值的等于标号4的地址
sub r3, r3, r7 @ 得到虚拟地址和物理地址的差值,r3=r3-r7
add r5, r5, r3 @ 将r5中保存的虚拟地址转换成物理地址
add r6, r6, r3 @ 将r6中保存的虚拟地址转换成物理地址
1: ldmia r5, r3, r4 @ r3存的cpu_val, r4存的cpu_mask
and r4, r4, r9 @ 将r9存的processor id 和cpu_mask相与:r4=r4 & r9
teq r3, r4 @判断r3和r4是否相等
beq 2f @如果r3=r4,则跳转到标号2处
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) @r5=r5+ #PROC_INFO_SZ
cmp r5, r6 @判断r5是否等于r6,如果相等说明已经遍历完全部的proc_info_list结构体
blo 1b @r5!=r6则跳转到标号1处
mov r5, #0 @ r5=0,说明没有匹配到CPU型号
2: mov pc, lr @函数返回
ENDPROC(__lookup_processor_type)
3: .long __proc_info_begin @链接脚本里proc_info_list结构体的起始地址
.long __proc_info_end @链接脚本里proc_info_list结构体的结束地址
4: .long .
.long __arch_info_begin
.long __arch_info_end
汇编语言解析:
(1)adr r3, 3f:将标号3的地址加载到r3中,adr是加载的是运行时地址,也就是物理地址;
(2)ldmia r3, r5 - r7 :将r3寄存器的值已经往后的值一次存在r5 - r7寄存器中。该语句的作用就是把long __proc_info_begin存在r5,__proc_info_end存到r6,标号4的地址存到r7。
(3)sub r3, r3, r7:此时r3里是标号4的物理地址,r4存的是标号4的链接地址,也就是虚拟地址;
(4)ldmia r5, r3, r4:r5存的是proc_info_list结构体的地址,而结构体的前两个成员就是cpu_val和cpu_mask;
(5)add r5, r5, #PROC_INFO_SZ:指向下一个proc_info_list结构体,理解成C语言就是**(proc_info_list *)r5++**;
以上是关于内核启动过程中对CPU型号的确认的主要内容,如果未能解决你的问题,请参考以下文章
20135239 益西拉姆 linux内核分析 跟踪分析Linux内核的启动过程
Linux脚本练习之script026-显示当前主机系统信息,包括主机名,IPv4 地址,操作系统版本,内核版本,CPU 型号,内存大小,硬盘大小。