Chinaunix首页 | 论坛 | 博客
  • 博客访问: 411153
  • 博文数量: 118
  • 博客积分: 294
  • 博客等级: 二等列兵
  • 技术积分: 667
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-16 20:31
文章分类

全部博文(118)

文章存档

2014年(3)

2012年(25)

2011年(90)

分类:

2011-12-30 22:31:16

下面我们需要做的是创建页表了
通过前面的两步,我们已经确定了processor type 和 machine type.
此时,一些特定寄存器的值如下所示:
r8 = machine info  (struct machine_desc的基地址)
r9 = cpu id          (通过cp15协处理器获得的cpu id)
r10 = procinfo       (struct proc_info_list的基地址)


          这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表
将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(section entry). 
每个页表项是32 bits(4 bytes)因而L1主页表占用 4096 *4 = 16k的内存空间.
L1的格式为:
31                               20 19       12 11 109 8               5  4 3 2 1 0
+------------------------------+------------+-----+-+-----------+-+-+-+-+-+
|                                        |                 |        |   |                |   |   |   |   |   |
|      Base Address          | SBZ         | AP  |0 | Domain |1 |C|B |1|0 |
|                                        |                 |        |   |                |   |   |   |   |   |
+------------------------------+------------+-----+-+-----------+-+-+-+-+-+

B - Write Buffer Bit            C - Cache Bit

+------------------------------------------------------------------+
|                               Data Cache                                |
+--------------+---------------+--------------------------------+
| Cache Bit | Buffer Bit   |   Page attribute             |
+--------------+---------------+--------------------------------+
|        0         |        0          | not cached, not buffered |
+--------------+---------------+--------------------------------+
|        0         |        1          | not cached, buffered    |
+--------------+---------------+--------------------------------+
|        1         |        0          | cached, writethrough   |
+--------------+---------------+--------------------------------+
|        1         |        1          |   cached, writeback       |
+--------------+---------------+--------------------------------+


 * r8  = machinfo
* r9  = cpuid
* r10 = procinfo
* Returns:
*  r0, r3, r6, r7 corrupted
*  r4 = physical page table address


__create_page_tables:
pgtbl   r4                              @ page table address
.macro pgtbl, rd
ldr   \rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm
KERNEL_RAM_PADDR在本文件的前面有定义,为(PHYS_OFFSET + TEXT_OFFSET)
PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定义,为UL(0x30000000)
而TEXT_OFFSET在arch/arm/Makefile中定义,为内核镜像在内存中到内存开始位置的偏移为(
0x00008000)。
r4 = 30004000为临时页表的起始地址首先即是初始化16K的页表,高12位虚拟地址为页表索引,
所以为4K*4 = 16K,大页表,每一个页表项,映射1MB虚拟地址。


* Clear the 16K level 1 swapper page table
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

ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS]          @ mm_mmuflags
在arch/arm/kernel/asm-offset.c中PROCINFO_MM_MMUFLAGS为
DEFINE(PROCINFO_MM_MMUFLAGS, offsetof(struct proc_info_list, __cpu_mm_mmu_flags));
R10寄存器保存的指针指向是我们前面找到的proc_info_list结构嘛。

        /*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable.  This identity mapping
* will be removed by paging_init().  We use our current program
* counter to determine corresponding section base address.
*/

mov     r6, pc
mov     r6, r6, lsr #20                 @ start of kernel section
orr       r3, r7, r6, lsl #20             @ flags + kernel base
str        r3, [r4, r6, lsl #2]            @ identity mapping

上面这三行,设置了kernel的第一个section(物理地址所在的page entry)的页表项
1) 通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中.因为当前是通
过运行时地址得到的kernel的section,因而是物理地址.
2) r3 = r7 | (r6 << 20);             flags + kernel base,得到页表中需要设置的值.
3) 设置页表: mem[r4 + r6 * 4] = r3 这里,因为页表的每一项是32 bits(4 bytes),
所以要乘以4(<<2).因为每个section描述符占4个字节,这里把起始section数乘以4,
加上 r4中的页表起始地址,找到对应的页表入口。然后写入描述符。目的是使得MMU开
启后,pc 在未转换到虚地址0xc0008000的空间中之前,还能够继续映射原空间
/*  结果:
*  虚拟地址               物理地址
*  0x300000000     0x30000000。

         * Now setup the pagetables for our kernel direct
* mapped region.为内核占用的头4M地址准备好转换表(在这采用的就不是平板地址映射模式了)


add     r0, r4,  #(KERNEL_START & 0xff000000) >> 18
str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
        KERNEL_START在文件的前面定义,为KERNEL_RAM_VADDR,即内核的虚拟地址。
而KERNEL_RAM_VADDR在文件的前面定义为(PAGE_OFFSET + TEXT_OFFSET)映射完整
的内核代码段,初始化数据段。PAGE_OFFSET为内核镜像开始的虚拟地址,
在arch/arm/include/asm/memory.h中定义。在配置内核时选定具体值,默认为0xC0000000。
因为最高12位的值是页表中的偏移地址,而第三高的四位必然为0,每个页表项为4字节,右移20位
之后,还得再左移两位回来,所以,这里只是左移18位。R3寄存器在经过了上面的操作之后,实际
上是变成了指向内核镜像代码段的指针(物理地址),在这个地方,再一次为内核镜像的第一个MB做了映射。R6随后指向了内核镜像的尾部。R0为页表项指针。这里以1MB为单位来映射内核镜像。

           TEXTADDR是内核起始虚拟地址(c0008000),(TEXTADDR & 0xff000000) >> 18和
(TEXTADDR & 0x00f00000) >> 18获得了虚拟地址的高14位,这14位中最低两位为0,4字节
对齐,和ttb中页表基址一起索引到页表中的一个位置,然后将页描述符写入页表,这个
页描述符和上一个是一样的,这样在这第一个1MB空间内不管cpu发出的是虚拟地址还是
物理地址,取的都是同一个存储单元的数据。这就解决了mmu打开是pc中存的还是没打开前的
物理地址的问题。

ldr     r6, =(KERNEL_END - 1)
add     r0, r0, #4
add     r6, r4, r6, lsr #18        @得到页表的结束物理地址
1:      
cmp     r0, r6
add     r3, r3, #1 << 20
strls   r3, [r0], #4
bls     1b
     *  结果:
     *  虚拟地址                        物理地址
0xc0000000           0x30000000
0xc0100000           0x30100000
0xc0200000           0x30200000
0xc0300000           0x30300000

  * Then map first 1MB of ram in case it contains our boot params.
  为了使用启动参数,将物理内存的第一MB映射到内核虚拟地址空间的第一个MB,r4存放的
是页表的地址。这里的PAGE_OFFSET的虚拟地址比上面的KERNEL_START要小0x8000


add     r0, r4, #PAGE_OFFSET >> 18
orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
.if     (PHYS_OFFSET & 0x00f00000)
orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str     r6, [r0]

映射sdram的头1M以防boot params需要

ldr     r7, [r10, #PROCINFO_IO_MMUFLAGS]                   @ io_mmuflags
         * Map in IO space for serial debugging.
* This allows debug messages to be output
* via a serial console before paging_init.


ldr     r3, [r8, #MACHINFO_PGOFFIO]
add     r0, r4, r3
rsb     r3, r3, #0x4000                 @ PTRS_PER_PGD*sizeof(long)
cmp     r3, #0x0800                     @ limit to 512MB
movhi   r3, #0x0800
add     r6, r0, r3
ldr     r3, [r8, #MACHINFO_PHYSIO]
orr     r3, r3, r7
1:      
str     r3, [r0], #4
add     r3, r3, #1 << 20
teq     r0, r6
bne     1b

mov     pc, lr
ENDPROC(__create_page_tables)

总结一下,这个建立临时页表的过程:
1、为内核镜像的第一个MB建立直接映射
2、为内核镜像完整的建立从虚拟地址到物理地址的映射
3、为物理内存的第一个MB建立到内核的虚拟地址空间的第一个MB的映射。
OK,内核的临时页表建立完毕。整个初始化临时页表的过程都没有修改R8,R9和R10。


回到stext:

==> ldr   r13, __switch_data    @ address to jump to after
@ mmu has been enabled

         这个地方实际上是在r13中保存了另一个例程的地址。后面的分析中,遇到执行到这个例程
的情况时会有详细说明。


==> adr   lr, BSYM(__enable_mmu)      @ return (PIC) address

          BSYM()是一个宏,在文件arch/arm/include/asm/unified.h中定义,
为:
#define BSYM(sym) sym
也就是说这个语句也仅仅是把__enable_mmu例程的地址加载进lr寄存器中。
为了方便之后调用的函数返回时,直接执行__enable_mmu例程。


==>  add   pc, r10, #PROCINFO_INITFUNC

            R10中保存的是procinfo结构的地址。PROCINFO_INITFUNC符号在
arch/arm/kernel/asm-offsets.c文件中定义,为:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
也就是调用结构体proc_info_list的__cpu_flush成员函数。回去查看
arch/arm/mm/proc-arm920.S文件中struct proc_info_list结构体的变量的定义,
可以看到这个成员为:


b  __arm920_setup

也就是说,在设置好内核临时页表之后调用了例程__arm920_setup,
这个例程同样在arch/arm/mm/proc-arm920.S中:

__arm920_setup:
mov   r0, #0
mcr   p15, 0, r0, c7, c7    @ invalidate I,D caches on v4
mcr   p15, 0, r0, c7, c10, 4      @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr   p15, 0, r0, c8, c7    @ invalidate I,D TLBs on v4
#endif
adr   r5, arm920_crval
ldmia r5, {r5, r6}
mrc   p15, 0, r0, c1, c0    @ get control register v4
bic   r0, r0, r5
orr   r0, r0, r6
mov   pc, lr
这一段首先使i,d caches内容无效,然后清除write buffer,接着使TLB内容无效。接下来
加载变量arm920_crval的地址,我们看到arm920_crval变量的内容为:


rm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130

crval为一个宏,在arch/arm/mm/proc-macros.S中定义:


.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm

其实也就是定义两个变量而已。之后,在r0中,得到了我们想要往协处理器相应寄存器中
写入的内容。之后的 __arm920_setup返回,mov  pc, lr,即是调用例程__enable_mmu,
这个例程在文件arch/arm/kernel/head.S中:

__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
#endif
mov   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_on

         在这儿设置了页目录地址(r4寄存器中保存),然后设置domain的保护,在前面建立
页表的例程中,注意到,页表项的控制信息,是从struct proc_info_list结构体的某字段中取的,
其页目录项的 domain都是0,domain寄存器中的domain 0对应的是0b11,表示访问模式为
manager,不受限制。在这里同时也完成r0的某些位的进一步设置。

然后,__enable_mmu例程又调用了__turn_mmu_on,在同一个文件中定义:

__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 reg
mov   r3, r3
mov   r3, r13
mov   pc, r3
ENDPROC(__turn_mmu_on)

接下来写控制寄存器:
mcr p15, 0, r0, c1, c0 ,0
一切设置就此生效,到此算是完成了打开d,icache和mmu的工作。
注意:
arm的d cache必须和mmu一起打开,而i cache可以单独打开。其实,cache和mmu的
关系实在是紧密,每一个页表项都有标志标示是否是cacheable的,可以说本来就是设计一起
使用的.前面有提到过,r13中存放的其实是另外一个例程的地址,其值是变量__switch_data的
第一个字段,即一个函数指针的值,__switch_data变量是在arch/arm/kernel/head-common.S
中定义的:


__switch_data:
.long __mmap_switched
.long __data_loc         @ r4
.long _data           @ r5
.long __bss_start        @ r6
.long _end            @ r7
.long processor_id       @ r4
.long __machine_arch_type      @ r5
.long __atags_pointer       @ r6
.long cr_alignment       @ r7
.long init_thread_union + THREAD_START_SP @ sp
    前面的ldr r13 __switch_data,实际上也就是加载符号__mmap_switched的地址,
实际上__mmap_switched是一个arch/arm/kernel/head-common.S中定义的例程。
接着来看这个例程的定义,在arch/arm/kernel/head-common.S文件中:

__mmap_switched:
adr   r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp   r4, r5          @ Copy data segment if needed
1: 
cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne   1b
mov   fp, #0          @ Clear BSS (and zero fp)
1: 
cmp   r6, r7
strcc fp, [r6],#4
bcc   1b
ldmia r3, {r4, r5, r6, r7, sp}
str   r9, [r4]        @ Save processor ID
str   r1, [r5]        @ Save machine type
str   r2, [r6]        @ Save atags pointer
bic   r4, r0, #CR_A         @ Clear 'A' bit
stmia r7, {r0, r4}       @ Save control register values
b  start_kernel
ENDPROC(__mmap_switched)

这个例程完成如下工作:
1、使r3指向__switch_data变量的第二个字段(从1开始计数)。
2、执行了一条加载指令,也就是在r4, r5, r6, r7寄存器中分别加载4个符号__data_loc,_data,
__bss_start ,_end的地址,这四个符号都是在链接脚本arch/arm/kernel/vmlinux.lds.S中
出现的,标识了镜像各个段的地址,我们应该不难猜出他们所代表的段。
3、如果需要的话则复制数据段(数据段和BSS段是紧邻的)。
4、初始化BSS段,全部清零,BSS是未初始化的全局变量区域。
5、又看到一条加载指令,同样在一组寄存器中加载借个符号的地址,r4中为processor_id,r5
中为__machine_arch_type, r6中为__atags_pointer, r7中为cr_alignment ,sp中为
init_thread_union + THREAD_START_SP。
6、接着我们看到下面的几条语句,则是用前面获取的信息来初始化那些全局变量r9,机器号被
保存到processor_id处;r1寄存器的值,机器号,被保存到变量__machine_arch_type中,
其他的也一样。
7、重新设置堆栈指针,指向init_task的堆栈。init_task是系统的第一个任务,init_task的堆栈
在task structure的后8K,我们后面会看到。 
8、最后就要跳到C代码的 start_kernel。 
b  start_kernel
到此为止,汇编部分的初始化代码就结束了

现在让我们来回忆一下目前的系统状态: 
临时页表已经建立,在0X30004000处,映射了映像文件大小空间,虚地址0XC000000被映
射到0X30000000。CACHE,MMU 都已经打开。堆栈用的是任务init_task的堆栈。
阅读(815) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~