linux 内核启动代码,Linux内核启动代码--汇编部分解读(arm平台)
发布日期:2022-02-03 04:38:36 浏览次数:12 分类:技术文章

本文共 4966 字,大约阅读时间需要 16 分钟。

以下解读针对6.20.1的内核

内核被boot-loader装入内存,然后解压缩,跳到第一条指令处执行.此时mmu是关闭的,就是说此时指令寄存器pc中的地址不经过转换直接对应到物理地址.而内核映像文件的入口地址(stext)在编译时是被链接到0xC0008000处(见内核链接脚本).这个地址也被定义为内存中第一条内核指令的虚拟地址:

stext <==> KERNEL_RAM_VADDR

#define KERNEL_RAM_VADDR  (PAGE_OFFSET+TEXT_OFFSET) //0XC0000000+0X8000

相应的物理地址用KERNEL_RAM_PADDR变量指定,定义为

#define KERNEL_RAM_PADDR (PHYS_OFFSET+TXST_OFFSET)

//如对s3c2410,PHYS_OFFSET=0X30000000,物理地址为0X30008000

head.S中的大部分代码的地址都是位置无关的,即代码中的地址是相对于入口地址的偏移值,但打开mmu

后,程序中的地址都要通过页表转换,因此在这之前,首要任务是建立正确的页表,使得程序地址在转换时对应到相应内存物理地址上。这样当打开mmu后就可以正确执行内存地址处的指令了.打开MMU后的程序地址称为虚拟地址。因此进入内核入口后,很快就有一条指令为:bl __create_page_table既是跳到建立页表的地方执行.

在跳转到建立页表的指令之前,先要取处理器号以及根据处理器号找到该处理器信息结构的指针,

__lookup_processor_type标号处的程序完成这个任务,从它返回的时候:

r9--处理器号(cpuid),

r5--处理器信息结构(procinfo)的物理地址

r10--r5的复制.

然后跳到__lookup_machine_type处,取机器类型结构(machinfo)的物理地址,

返回时:

r5--结果

r8--r5的复制.

这两个子任务的细节后面分析.这些安排好以后就可以进入建立页表的过程了.

内核定义临时页表起始地址在内核入口地址以下16K处.一个全局变量swapper_pg_dir被定义为

表示这个一级页表起始(虚拟)地址:

.equ    swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000

首先取页表入口的物理地址到寄存器r4中,这是通过宏pgtbl实现的:pgtbl r4

---------

宏定义

.macro  pgtbl, rd

ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)

.endm

---------

接着以r4中的页表物理入口地址为参照,开始对16K的页表初始化.

第一步:清空页表

mov     r0, r4

mov     r3, #0

add     r6, r0, #0x4000

1:   str     r3, [r0], #4

str     r3, [r0], #4

str     r3, [r0], #4

str     r3, [r0], #4

teq     r0, r6

bne     1b

第二步:填写页表中对应内核所在页的页目录项(描述符).关于页表结构和描述符的

具体内容可参考arm手册.页表的内容是从r10指向的处理器信息结构中复制过来的.处理器信息结构

中用的是段(section)类型的页表,每个页表项描述1m内存区域,称为1段.16k页表有4k个表项,

覆盖4GB的虚拟内存空间

建立相同映射表项,即identity mapping,通俗的说即自己映射到自己的项.

ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

取处理器信息结构中的标志部分放入r7,作为描述符的标志部分.

mov     r6, pc, lsr #20           内核所在内存段开始地址(物理地址)在页表中

的索引号(当前地址的前12位)->r6

orr     r3, r7, r6, lsl #20      r6和r7内容合并->r3

str     r3, [r4, r6, lsl #2]      每个描述符占4字节,所以内核的起始页

目录项地址应该在[r4]+[r6]x4处,将r3的

描述符内容送入其中.

接着为当前内核所在直接可映射区域建立页表项

add     r0, r4,  #(TEXTADDR & 0xff000000) >> 18

内核入口处的虚拟地址所处的段开始地址对齐到16字节边界处(为内核区域分配了4个表项),右移20位得到对应的地址在页表中的索引号,每个表项占4个字位,左移2位(索引号x4)即共右移18位,得到表项距页表开始地址(r4)的总字节数,和r4相加就是这个页表项的物理地址.这个地址存入r0中.

str     r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!

前面r3的描述符内容存入内核入口地址对应的表项(这里就是4个表项的第一个表项.因为TEXTADDR=0XC00--)

ldr     r6, =(_end - PAGE_OFFSET - 1)   r6存入内核所占段大小-1

mov     r6, r6, lsr #20

1:      add     r3, r3, #1 << 20        根据内核的段大小相继填写完后面的表项.

str     r3, [r0, #4]!

subs    r6, r6, #1

bgt     1b

最后映射内存开始1m段,填写对应的表项,因为这里可能存放内核启动参数.

add     r0, r4, #PAGE_OFFSET >> 18               表项的物理地址->r0

orr     r6, r7, #(PHYS_OFFSET & 0xff000000)

orr     r6, r6, #(PHYS_OFFSET & 0x00e00000)     物理起始地址是2m对齐的.

str     r6, [r0]                               物理起始地址写入0XC0000000对应的表项中.

到此所有需要的映射已经完成.

从协处理器寄存器中(CP#15,CR0)读出cpuid到r9中(如arm720核的cpuid是0x41807200),

再到链接时建立的处理器列表中(在.proc.info.init节中)查找这个cpuid对应的类型和操作信息.

链接时定义了_proc_info_begin和_proc_info_end两个地址符号变量分别指向这个节的开始和

结束的虚拟地址.同样在.arch.info.init节中有一个体系结构相关信息表,它的开始和结束处的符号

地址是_arch_info_star和_arch_info_end,把这些符号地址放在文件中以便引用来计算地址偏移量.

.long   __proc_info_begin

.long   __proc_info_end

3:     .long   .                            这里放标号处的链接(虚拟)地址

.long   __arch_info_begin

.long   __arch_info_end

从_lookup_processor_type开始看起:

adr     r3, 3f                   用伪指令adr取相对地址,以保证位置无关性.

r3中存标号3处物理地址

ldmda   r3, {r5 - r7}            在r5中得标号3处的虚拟地址,r7中得符号地址

__proc_info_begin

sub     r3, r3, r7              标号3处的物理地址到__proc_info_begin

的差.

用3处的虚拟地址加上这个差得到是

__proc_info_begin的物理地址.

add     r5, r5, r3              r5+(r3-r7),有点不容易理解,那就变换一

下r3+(r5-r7),即标号3的物理地址(r3)+

标号3到__proc-info-begin的偏移(位置

无关量)(r5-r7)就是_proc_info_begin

的物理地址.

1:      ldmia   r5, {r3, r4}               现在找到了表的入口物理地址,传送它的前2项

内容即cpu值和掩码值到r3,r4中.然后比较从cpu中读出的id(r9中)和表中的id值,找到匹配就返回,否则r5中存入0返回.

and r4, r4, r9   @ mask wanted bits

teq r3, r4

beq 2f

add r5, r5, #PROC_INFO_SZ  @ sizeof(proc_info_list)

cmp r5, r6

blo 1b

mov r5, #0    @ unknown processor

2: mov pc, lr

__lookup_machine_type的过程和以上相同.

打开MMU

调用位置无关的cpu特定代码,这些代码在arch/arm/mm/proc-*.S文件中.

通过

add pc, r10, #PROCINFO_INITFUNC

跳到__arm920_setup标号的代码处.r10是由前面__lookup_machine_type选定的xxx_proc_info 结构的基地址,当从__arm920_setup返回时,初始化系统协处理器中有关寄存器的工作已完成,cpu做好了打开MMU的准备.r0中存放了cpu控制寄存器的值.

跳到标号__enable_mmu处执行打开mmu操作.

__arm920_setup:

mov r0, #0

mcr p15, 0, r0, c7, c7    @关闭数据和指令cache

mcr p15, 0, r0, c7, c10,4@泄放写缓冲的内容到内存

mcr p15, 0, r0, c8, c7   @禁止数据和指令TLBs

mcr p15, 0, r4, c2, c0    @装入页表起始指针

mov r0, #0x1f             @页域值,管理者,不受访问权限约束.

mcr p15, 0, r0, c3, c0    @装入页域访问寄存器

mrc p15, 0, r0, c1, c0    @取控制(配置)寄存器值

/*

*  关闭控制寄存器的某些位*/

@ VI ZFRS BLDP WCAM

bic r0, r0, #0x0e00

bic r0, r0, #0x0002

bic r0, r0, #0x000c

bic r0, r0, #0x1000   @ ...0 000. .... 000.

/*

* 打开需要的位

*/

orr r0, r0, #0x0031

orr r0, r0, #0x2100   @ ..1. ...1 ..11 ...1

/*根据内核配置选项决定要打开的位*/

#ifdef CONFIG_CPU_ARM920_D_CACHE_ON

orr r0, r0, #0x0004   @ .... .... .... .1..

#endif

#ifdef CONFIG_CPU_ARM920_I_CACHE_ON

orr r0, r0, #0x1000   @ ...1 .... .... ....

#endif

mov pc, lr @返回.其后,

@mcr p15, 0, r0, c1, c0使以上设置生效。

最后完成的工作:

1。初始化BSS段,全部清零,BSS是全局变量区域。

2。保存与系统相关的信息:如

.long SYMBOL_NAME(compat)

.long SYMBOL_NAME(__bss_start)

.long SYMBOL_NAME(_end)

.long SYMBOL_NAME(processor_id)

.long SYMBOL_NAME(__machine_arch_type)

.long SYMBOL_NAME(cr_alignment)

.long SYMBOL_NAME(init_task_union)+8192

不用讲,大家一看就明白意思

3。重新设置堆栈指针,指向init_task的堆栈。init_task是系统的第一个任务,init_task的堆栈在task structure的后8K,我们后面会看到。

4。最后跳到C代码的start_kernel。

b SYMBOL_NAME(start_kernel)

arm的数据cache必须和mmu一起打开,而指令cache可以单独打开

转载地址:https://blog.csdn.net/weixin_30596151/article/details/116621388 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:linux 内核不支持网卡,Linux怎么判断网卡是否支持netdump功能
下一篇:linux编辑关机脚本,Linux关机时执行指定脚本(亲测有效)

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月08日 23时11分52秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章