yiffer的个人空间 https://blog.eetop.cn/edesign [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

Linux内核启动代码--汇编部分解读(arm平台)

热度 1已有 4040 次阅读| 2011-6-30 11:22 |个人分类:linux驱动开发

以下解读针对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可以单独打开
1

点赞

刚表态过的朋友 (1 人)

发表评论 评论 (1 个评论)

回复 wushibin 2011-7-3 22:26
很好。。。 :loveliness: :handshake

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 3

    粉丝
  • 0

    好友
  • 20

    获赞
  • 69

    评论
  • 3705

    访问数
关闭

站长推荐 上一条 /1 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-3-29 19:49 , Processed in 0.017036 second(s), 8 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部