stack 위의 메모리에 64KB를 할당하는데 stack은
4.3.3절에서 지정된 것 처럼 커널 이미지의 마지막에
위치한다. 즉, stack으로 할당한곳 뒤의 메모리는 사용하지 않는 영역이므로 여기를 할당해
사용한다. 새로 할당한 영역은 압축을 풀 때 사용하는 영역이다.
압축 풀린 커널을 _load_addr로 옮기기 위해 리로케이션 하는 코드를 압축 풀린 커널의
뒤에 복사한다. 다시 말해 압축 풀린 커널을 그대로 _load_addr로 옮기면 현재 실행되고 있는
head.S를 엎어 쓰기 때문에 계속해서 제대로 실행되지 않는다. 그러므로 재배치 해주는 코드를
먼저 대피해 놓고 그 코드를 실행해 압축 풀린 커널을 옮긴다.
4.3.2절와 4.4.1절에서 살펴본 것 처럼
커널 이미지의 압축이 풀리고 재배치 후에 실행되는 코드는 stext로
$(TOPDIR)/arch/arm/kernel/head-armv.S에 정의되어 있다.
/*
* linux/arch/arm/kernel/head-armv.S
*
* Copyright (C) 1994-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 32-bit kernel startup code for all architectures
*/
#include <linux/config.h>
#include <linux/linkage.h>
#include <asm/assembler.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#define K(a,b,c) ((a) << 24 | (b) << 12 | (c))
/*
* We place the page tables 16K below TEXTADDR. Therefore, we must make sure
* that TEXTADDR is correctly set. Currently, we expect the least significant
* "short" to be 0x8000, but we could probably relax this restriction to
* TEXTADDR > PAGE_OFFSET + 0x4000
*
* Note that swapper_pg_dir is the virtual address of the page tables, and
* pgtbl gives us a position-independent reference to these tables. We can
* do this because stext == TEXTADDR
*
* swapper_pg_dir, pgtbl and krnladr are all closely related.
*/
#if (TEXTADDR & 0xffff) != 0x8000
#error TEXTADDR must start at 0xXXXX8000
#endif
.globl SYMBOL_NAME(swapper_pg_dir)
.equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000
.macro pgtbl, reg, rambase
adr \reg, stext
sub \reg, \reg, #0x4000
.endm
/*
* Since the page table is closely related to the kernel start address, we
* can convert the page table base address to the base address of the section
* containing both.
*/
.macro krnladr, rd, pgtable, rambase
bic \rd, \pgtable, #0x000ff000
.endm
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
(1)
.section ".text.init",#alloc,#execinstr
.type stext, #function
ENTRY(stext)
mov r12, r0
/*
* NOTE! Any code which is placed here should be done for one of
* the following reasons:
*
* 1. Compatability with old production boot firmware (ie, users
* actually have and are booting the kernel with the old firmware)
* and therefore will be eventually removed.
* 2. Cover the case when there is no boot firmware. This is not
* ideal, but in this case, it should ONLY set r0 and r1 to the
* appropriate value.
*/
#if defined(CONFIG_ARCH_NETWINDER)
/*
* Compatability cruft for old NetWinder NeTTroms. This
* code is currently scheduled for destruction in 2.5.xx
*/
.rept 8
mov r0, r0
.endr
adr r2, 1f
ldmdb r2, {r7, r8}
and r3, r2, #0xc000
teq r3, #0x8000
beq __entry
bic r3, r2, #0xc000
orr r3, r3, #0x8000
mov r0, r3
mov r4, #64
sub r5, r8, r7
b 1f
.word _stext
.word __bss_start
1:
.rept 4
ldmia r2!, {r6, r7, r8, r9}
stmia r3!, {r6, r7, r8, r9}
.endr
subs r4, r4, #64
bcs 1b
movs r4, r5
mov r5, #0
movne pc, r0
mov r1, #MACH_TYPE_NETWINDER @ (will go in 2.5)
mov r12, #2 << 24 @ scheduled for removal in 2.5.xx
orr r12, r12, #5 << 12
__entry:
#endif
#if defined(CONFIG_ARCH_L7200)
/*
* FIXME - No bootloader, so manually set 'r1' with our architecture number.
*/
mov r1, #MACH_TYPE_L7200
#endif
(2)
mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
msr cpsr_c, r0 @ and all irqs disabled
bl __lookup_processor_type
teq r10, #0 @ invalid processor?
moveq r0, #'p' @ yes, error 'p'
beq __error
bl __lookup_architecture_type
teq r7, #0 @ invalid architecture?
moveq r0, #'a' @ yes, error 'a'
beq __error
bl __create_page_tables
(3)
adr lr, __ret @ return address
add pc, r10, #12 @ initialise processor
@ (return control reg)
.type __switch_data, %object
__switch_data: .long __mmap_switched
.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
(4)
.type __ret, %function
__ret: ldr lr, __switch_data
mcr p15, 0, r0, c1, c0
mov r0, r0
mov r0, r0
mov r0, r0
mov pc, lr
/*
* This code follows on after the page
* table switch and jump above.
*
* r0 = processor control register
* r1 = machine ID
* r9 = processor ID
*/
.align 5
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3, {r2, r4, r5, r6, r7, r8, sp}@ r2 = compat
@ sp = stack pointer
str r12, [r2]
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r4, r5
strcc fp, [r4],#4
bcc 1b
str r9, [r6] @ Save processor ID
str r1, [r7] @ Save machine type
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #2 @ ...........A.
#endif
bic r2, r0, #2 @ Clear 'A' bit
stmia r8, {r0, r2} @ Save control register values
(5)
b SYMBOL_NAME(start_kernel)
(6)
/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* We only map in 4MB of RAM, which should be sufficient in
* all cases.
*
* r5 = physical address of start of RAM
* r6 = physical IO address
* r7 = byte offset into page tables for IO
* r8 = page table flags
*/
__create_page_tables:
(7)
pgtbl r4, r5 @ page table address
(8)
/*
* Clear the 16K level 1 swapper page table
*/
mov r0, r4
mov r3, #0
add r2, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r2
bne 1b
(9)
/*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable. This identity mapping
* will be removed by paging_init()
*/
krnladr r2, r4, r5 @ start of kernel
add r3, r8, r2 @ flags + kernel base
str r3, [r4, r2, lsr #18] @ identity mapping
(10)
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary.
*/
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel
bic r2, r3, #0x00f00000
str r2, [r0] @ PAGE_OFFSET + 0MB
add r0, r0, #(TEXTADDR & 0x00f00000) >> 18
str r3, [r0], #4 @ KERNEL + 0MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 1MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 2MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 3MB
(11)
/*
* Ensure that the first section of RAM is present.
* we assume that:
* 1. the RAM is aligned to a 32MB boundary
* 2. the kernel is executing in the same 32MB chunk
* as the start of RAM.
*/
bic r0, r0, #0x01f00000 >> 18 @ round down
and r2, r5, #0xfe000000 @ round down
add r3, r8, r2 @ flags + rambase
str r3, [r0]
bic r8, r8, #0x0c @ turn off cacheable
@ and bufferable bits
#ifdef CONFIG_DEBUG_LL
/*
* Map in IO space for serial debugging.
* This allows debug messages to be output
* via a serial console before paging_init.
*/
add r0, r4, r7
rsb r3, r7, #0x4000 @ PTRS_PER_PGD*sizeof(long)
cmp r3, #0x0800
addge r2, r0, #0x0800
addlt r2, r0, r3
orr r3, r6, r8
1: str r3, [r0], #4
add r3, r3, #1 << 20
teq r0, r2
bne 1b
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
/*
* If we're using the NetWinder, we need to map in
* the 16550-type serial port for the debug messages
*/
teq r1, #MACH_TYPE_NETWINDER
teqne r1, #MACH_TYPE_CATS
bne 1f
add r0, r4, #0x3fc0
mov r3, #0x7c000000
orr r3, r3, r8
str r3, [r0], #4
add r3, r3, #1 << 20
str r3, [r0], #4
1:
#endif
#endif
#ifdef CONFIG_ARCH_RPC
/*
* Map in screen at 0x02000000 & SCREEN2_BASE
* Similar reasons here - for debug. This is
* only for Acorn RiscPC architectures.
*/
add r0, r4, #0x80 @ 02000000
mov r3, #0x02000000
orr r3, r3, r8
str r3, [r0]
add r0, r4, #0x3600 @ d8000000
str r3, [r0]
#endif
(12)
mov pc, lr
/*
* Exception handling. Something went wrong and we can't
* proceed. We ought to tell the user, but since we
* don't have any guarantee that we're even running on
* the right architecture, we do virtually nothing.
* r0 = ascii error character:
* a = invalid architecture
* p = invalid processor
* i = invalid calling convention
*
* Generally, only serious errors cause this.
*/
__error:
#ifdef CONFIG_DEBUG_LL
mov r8, r0 @ preserve r0
adr r0, err_str
bl printascii
mov r0, r8
bl printch
#endif
#ifdef CONFIG_ARCH_RPC
/*
* Turn the screen red on a error - RiscPC only.
*/
mov r0, #0x02000000
mov r3, #0x11
orr r3, r3, r3, lsl #8
orr r3, r3, r3, lsl #16
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
#endif
1: mov r0, r0
b 1b
#ifdef CONFIG_DEBUG_LL
err_str: .asciz "\nError: "
.align
#endif
(13)
/*
* Read processor ID register (CP#15, CR0), and look up in the linker-built
* supported processor list. Note that we can't use the absolute addresses
* for the __proc_info lists since we aren't running with the MMU on
* (and therefore, we are not in the correct address space). We have to
* calculate the offset.
*
* Returns:
* r5, r6, r7 corrupted
* r8 = page table flags
* r9 = processor ID
* r10 = pointer to processor structure
*/
__lookup_processor_type:
adr r5, 2f
(14)
ldmia r5, {r7, r9, r10}
sub r5, r5, r10 @ convert addresses
add r7, r7, r5 @ to our address space
add r10, r9, r5
mrc p15, 0, r9, c0, c0 @ get processor id
(15)
1: ldmia r10, {r5, r6, r8} @ value, mask, mmuflags
and r6, r6, r9 @ mask wanted bits
teq r5, r6
moveq pc, lr
add r10, r10, #36 @ sizeof(proc_info_list)
cmp r10, r7
blt 1b
mov r10, #0 @ unknown processor
mov pc, lr
(16)
/*
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
2: .long __proc_info_end
.long __proc_info_begin
.long 2b
.long __arch_info_begin
.long __arch_info_end
(17)
/*
* Lookup machine architecture in the linker-build list of architectures.
* Note that we can't use the absolute addresses for the __arch_info
* lists since we aren't running with the MMU on (and therefore, we are
* not in the correct address space). We have to calculate the offset.
*
* r1 = machine architecture number
* Returns:
* r2, r3, r4 corrupted
* r5 = physical start address of RAM
* r6 = physical address of IO
* r7 = byte offset into page tables for IO
*/
__lookup_architecture_type:
adr r4, 2b
ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3
sub r5, r4, r5 @ convert addresses
add r4, r6, r5 @ to our address space
add r7, r7, r5
1: ldr r5, [r4] @ get machine type
teq r5, r1
beq 2f
add r4, r4, #SIZEOF_MACHINE_DESC
cmp r4, r7
blt 1b
mov r7, #0 @ unknown architecture
mov pc, lr
2: ldmib r4, {r5, r6, r7} @ found, get results
mov pc, lr
페이지 테이블을 만든 후 무언가를 실행하는데 돌아오는 위치는 __ret가 되도록 한다.
실행되는 그 무엇은 r10 + #12로 Assabet보드의 경우 __sa1100_setup 이란 곳이된다.
이 곳은 $(TOPDIR)/arch/arm/mm/proc-sa110.S에
정의되어 있다.
페이지 테이블에 들어갈 디스크립터 값을 설정한다. krnladr은 커널의 시작이 위치한 곳을
1MB 단위로 끊어준 값이다. 여기선 페이지 테이블을 섹션 디스크립터로 채우기 때문에 1MB
단위로 끊을 필요가 있는 것이다. r2=0xc0000000이 된다(램의 시작점).
섹션 디스크립터의 값은 AP=11, Domain=0, IMP=0, C=1, B=1의 속성을 갖는다(0xC0E).
[r4, r2, lsr #18]이 의미하는 것은 r2를 18bit Left Shift하고 r4에 더한다는 뜻으로,
최종 값은 0xc0007000이 된다.
이 값이 의미하는 것은 페이지 테이블 내의 커널 시작 어드레스를 가리키는 위치가 된다.
섹션 디스크립터는 1MB단위로 메모리를 관리하고 32bit ARM 프로세서의 최대 가능 용량인
4GB를 다루기 위해선 4G/1M=4K 만큼의 디스크립터가 필요하다. 또 각 디스크립터는 4Bytes로
구성되므로 페이지 테이블은 4K*4Bytes=16KB 만큼의 크기가 필요하다.
그렇다면 커널이 시작하는 곳의 디스크립터는 16KB내의 어디에 위치하는 것일까?
커널의 시작이 포함된 1MB 단위의 램 시작점이 0xc0000000 이므로
0xc0000000/1M*4=0x3000이 된다. 페이지 테이블의 시작이 0xc0004000이므로 여기에 0x3000을
더한 0xc0007000에 위치한 디스크립터가 바로 커널의 시작 위치를 가리키는 디스크립터가 된다.
4.4. 소스 분석
$(TOPDIR)/vmlinux가 실행되기 전 까지의 소스 코드를 분석한다.
4.4.1. arch/arm/boot/compressed/head.S
커널 이미지의 제일 처음 실행되는 부분으로 head.S, misc.S, head-sa1100.S가 같이 실행된다. 이 중 head.S는 메모리 초기화에 해당하는 중요한 역할을 담당한다. 아래 소스 코드를 보고 분석해보자.
커널 이미지가 메모리에 어떤 위치에 올려지고 압축 풀린 것이 어디에 위치하고 다시 어디로 옮겨지는지 등에 관해 도식적으로 나타내봤다. 그림 4-9을 참조하면서 head.S를 분석한다.
그림 4-9. ARM 리눅스 커널 이미지 메모리 맵
attachment:arm-kernel-mmap.png
Assabet 보드에 올린 커널은 실행될 때 angelboot에 의해 실행되는데 angelboot로부터 r0=0, r1=0x19가 넘어온다. 즉 r1이 아키텍쳐 넘버가 된다. 4.2절 참조.
ulg decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id);
head.S에서 r0에 넘긴 값이 output_start가 r1은 free_mem_ptr_p, r2는 free_mem_ptr_end_p 그리고 r3이 arch_id가 된다.
각 섹션 디스크립터는 AP=11, IMP=1, Domain=00이란 속성을 갖는다. Assabet 보드의 경우 0xc0004000 ~ 0xc0008000에 페이지 테이블이 만들어진다.
4.4.2. arch/arm/kernel/head-armv.S
4.3.2절와 4.4.1절에서 살펴본 것 처럼 커널 이미지의 압축이 풀리고 재배치 후에 실행되는 코드는 stext로 $(TOPDIR)/arch/arm/kernel/head-armv.S에 정의되어 있다.
섹션 디스크립터의 값은 AP=11, Domain=0, IMP=0, C=1, B=1의 속성을 갖는다(0xC0E).
[r4, r2, lsr #18]이 의미하는 것은 r2를 18bit Left Shift하고 r4에 더한다는 뜻으로, 최종 값은 0xc0007000이 된다.
이 값이 의미하는 것은 페이지 테이블 내의 커널 시작 어드레스를 가리키는 위치가 된다. 섹션 디스크립터는 1MB단위로 메모리를 관리하고 32bit ARM 프로세서의 최대 가능 용량인 4GB를 다루기 위해선 4G/1M=4K 만큼의 디스크립터가 필요하다. 또 각 디스크립터는 4Bytes로 구성되므로 페이지 테이블은 4K*4Bytes=16KB 만큼의 크기가 필요하다.
그렇다면 커널이 시작하는 곳의 디스크립터는 16KB내의 어디에 위치하는 것일까? 커널의 시작이 포함된 1MB 단위의 램 시작점이 0xc0000000 이므로 0xc0000000/1M*4=0x3000이 된다. 페이지 테이블의 시작이 0xc0004000이므로 여기에 0x3000을 더한 0xc0007000에 위치한 디스크립터가 바로 커널의 시작 위치를 가리키는 디스크립터가 된다.
그리고 이 값은 $(TOPDIR)/arch/arm/mm/proc-sa110.S에 다음과 같이 정의 되어 있다.
위 정의에 대한 매크로는 $(TOPDIR)/include/asm-arm/mach/arch.h에 다음과 같이 정의 되어 있다.
Recent Posts
Archive Posts
Tags