링커 스크립터에 관한 문서 ( http://korea.gnu.org/manual/release/ld/ld-mahajjh/ld_3.html#SEC18 )
 

링커는 입력파일을 합쳐서 하나의 출력파일을 만든다. 링크 스크립터는 링킹 과정에서 링커의 동작을 제어 한다.

커널을 컴파일 하여  arch/arm/boot/compressed/vmlinux 이미지를 생성 할때, 다음과 같은 링킹 과정을 거친다.

/opt/codesourcery/bin/arm-linux-ld -EL --defsym zreladdr=0x40008000 \
  -p --no-undefined -X -T arch/arm/boot/compressed/vmlinux.lds \
  arch/arm/boot/compressed/head.o \
  arch/arm/boot/compressed/piggy.gzip.o \
  arch/arm/boot/compressed/misc.o \
  arch/arm/boot/compressed/decompress.o \
  arch/arm/boot/compressed/lib1funcs.o \
  -o arch/arm/boot/compressed/vmlinux


arm-linux-ld의 옵션 -T arch/arm/boot/compressed/vmlinux.lds 에 의해서 링커 스크립터를 지정 하였다.

-T FILE, --script FILE      Read linker script 


특별히 지정 하지 않았을 경우에는 default linker script를 사용하게 되는데, 이를 보는 옵션은 다음과 같다.
$ arm-linux-ld --verbose
GNU ld (Sourcery G++ Lite 2008q3-41) 2.18.50.20080215
  Supported emulations:
   armelf_linux_eabi
   armelfb_linux_eabi
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
              "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = 0x00008000); . = 0x00008000 + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
  .rel.dyn        :
    {
      *(.rel.init)
      *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
      *(.rel.fini)
      *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
      *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
      *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
      *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
      *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
      *(.rel.ctors)
      *(.rel.dtors)
      *(.rel.got)
      *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
    }
  .rela.dyn       :
    {
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
    }
  .rel.plt        : { *(.rel.plt) }
  .rela.plt       : { *(.rela.plt) }
  .init           :
  {
    KEEP (*(.init))
  } =0
  .plt            : { *(.plt) }
  .text           :
  {
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
    *(.glue_7t) *(.glue_7) *(.vfp11_veneer) *(.v4_bx)
  } =0
  .fini           :
  {
    KEEP (*(.fini))
  } =0
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) }
   __exidx_start = .;
  .ARM.exidx   : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
   __exidx_end = .;
  .eh_frame_hdr : { *(.eh_frame_hdr) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) }
  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  /* Thread Local Storage sections  */
  .tdata          : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array     :
  {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT(.init_array.*)))
     KEEP (*(.init_array))
     PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array     :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(.fini_array))
    KEEP (*(SORT(.fini_array.*)))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { KEEP (*(.jcr)) }
  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { *(.dynamic) }
  . = DATA_SEGMENT_RELRO_END (0, .);
  .got            : { *(.got.plt) *(.got) }
  .data           :
  {
    __data_start = . ;
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  __bss_start = .;
  __bss_start__ = .;
  .bss            :
  {
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we don't
      pad the .data section.  */
   . = ALIGN(. != 0 ? 32 / 8 : 1);
  }
  _bss_end__ = . ; __bss_end__ = . ;
  . = ALIGN(32 / 8);
  . = ALIGN(32 / 8);
  __end__ = . ;
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { *(.stab) }
  .stabstr       0 : { *(.stabstr) }
  .stab.excl     0 : { *(.stab.excl) }
  .stab.exclstr  0 : { *(.stab.exclstr) }
  .stab.index    0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment       0 : { *(.comment) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
  .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) }
}


==================================================


실제로 arch/arm/boot/compressed/vmlinux 이미지를 생성 할때 사용되는 링커 스크립터를 보자.
아래는 arch/arm/boot/compressed/vmlinux.lds 파일의 내용이다.
 

/*
 *  linux/arch/arm/boot/compressed/vmlinux.lds.in
 *
 *  Copyright (C) 2000 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.
 */
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
    /*
     * Discard any r/w data - this produces a link error if we have any,
     * which is required for PIC decompression.  Local data generates
     * GOTOFF relocations, which prevents it being relocated independently
     * of the text/got segments.
     */
    *(.data)
  }

  . = 0; /* 주소를 0x0 으로 지정한다. */
  _text = .; /* 이 곳에 심볼 _text 를 정의한다. 코드 영역의 시작 지정 */

  .text : { /* 섹션이름 .text 영역 정의 */
    _start = .; /* 이 곳에 심볼 _start 를 정의한다. */
    *(.start) /* .start 섹션으로 정의된 코드 부터 차례로 배열 된다. */
    *(.text)
    *(.text.*)
    *(.fixup)
    *(.gnu.warning)
    *(.glue_7t)
    *(.glue_7)
  }
  .rodata : { /* 섹션이름 .rodata 영역 정의 */
    *(.rodata)
    *(.rodata.*)
  }
  .piggydata : { /* 섹션이름 .piggydata 영역 정의 */
    *(.piggydata)
  }

  . = ALIGN(4);
  _etext = .; /* 이 곳에 심볼 _etext 를 정의한다. 코드 영역의 끝 지정 */

  .got.plt : { *(.got.plt) }
  _got_start = .;
  .got : { *(.got) } /* got 영역 */
  _got_end = .;
  _edata = .;

  . = ALIGN(8);
  __bss_start = .;
  .bss : { *(.bss) } /* bss 영역 */
  _end = .;

  . = ALIGN(8); /* the stack must be 64-bit aligned */
  .stack : { *(.stack) }

  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .stab.excl 0 : { *(.stab.excl) }
  .stab.exclstr 0 : { *(.stab.exclstr) }
  .stab.index 0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment 0 : { *(.comment) }

} 


스크립터에 사용 된 명령을 살펴 보자.
OUT_ARCH(arm) : 출력 아키텍쳐를 지정한다.
ENTRY(_start) : 프로그램에서 처음 실행되는 진입점이다. 심볼명을 지정한다.
SECTION  { } : 링커가 어떻게 입력 섹션을 출력 섹션으로 대응하고, 출력 섹션을 메모리에 배치하는지를 지정한다.

'/DISCARD/' : 특별한 출력 세션 이름으로 입력 섹션을 버리는 용도로 사용한다. 이 섹션으로 지정된 입력은 실제로 출력으로 배치되지 않는다.

objdump -h 명령으로 어떠한 섹션이 있는지 살펴 보자.

$ arm-linux-objdump -h arch/arm/boot/compressed/vmlinux

arch/arm/boot/compressed/vmlinux:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00003b50  00000000  00000000  00008000  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       00000c94  00003b50  00003b50  0000bb50  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .piggydata    0012bf8c  000047e4  000047e4  0000c7e4  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .got.plt      0000000c  00130770  00130770  00138770  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  4 .got          0000002c  0013077c  0013077c  0013877c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .bss          00000020  001307a8  001307a8  001387a8  2**2
                  ALLOC
  6 .stack        00001000  001307c8  001307c8  001387a8  2**0
                  ALLOC
  7 .comment      00000054  00000000  00000000  001387a8  2**0
                  CONTENTS, READONLY
  8 .ARM.attributes 00000029  00000000  00000000  001387fc  2**0
                  CONTENTS, READONLY
  9 .debug_line   0000107d  00000000  00000000  00138825  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_info   000018bc  00000000  00000000  001398a2  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_abbrev 00000633  00000000  00000000  0013b15e  2**0
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_aranges 00000088  00000000  00000000  0013b798  2**3
                  CONTENTS, READONLY, DEBUGGING
 13 .debug_ranges 00000548  00000000  00000000  0013b820  2**3
                  CONTENTS, READONLY, DEBUGGING
 14 .debug_frame  000001f8  00000000  00000000  0013bd68  2**2
                  CONTENTS, READONLY, DEBUGGING
 15 .debug_loc    00002791  00000000  00000000  0013bf60  2**0
                  CONTENTS, READONLY, DEBUGGING
 16 .debug_pubnames 000001a4  00000000  00000000  0013e6f1  2**0
                  CONTENTS, READONLY, DEBUGGING
 17 .debug_str    00000688  00000000  00000000  0013e895  2**0
                  CONTENTS, READONLY, DEBUGGING


출력되는 섹션의 배치를 간략하게 그림으로 표현했다.



코드상에서 정의된 심볼의 값

        .word   0x016f2818                                 |      24:   016f2818    .word   0x016f2818
        .word   start                                         |      28:   00000000    .word   0x00000000
        .word   _edata                                      |      2c:   001307a8    .word   0x001307a8


        .type   LC0, #object                               |0000017c <LC0>:
LC0:        .word   LC0                                    |     17c:   0000017c    .word   0x0000017c
        .word   __bss_start                                |     180:   001307a8    .word   0x001307a8
        .word   _end                                         |     184:   001307c8    .word   0x001307c8
        .word   _edata                                      |     188:   001307a8    .word   0x001307a8
        .word   input_data_end - 4                     |     18c:   0013076c    .word   0x0013076c
        .word   _got_start                                  |     190:   0013077c    .word   0x0013077c
        .word   _got_end                                   |     194:   001307a8    .word   0x001307a8
        .word   .L_user_stack_end                     |     198:   001317c8    .word   0x001317c8
        .size   LC0, . - LC0                               |     19c:   e1a00000    .word   0xe1a00000


Posted by blee
,
커널 분석의 시작 점은 크게 두가지 차이가 있다.
1. 커널를 압축해제 하는 부분
2. 순수 커널의 시작 부분 

또한 임베디드 시스템을 구성 할 경우 시스템의 저장 공간(일반적으로 ROM)에  퓨징 하는 커널도 두 가지로 구분 할 수 있다.
1. 압축된 커널을 퓨징해서 사용하는 경우
2. 압축되지 않은 커널을 퓨징해서 사용하는 경우

부트로더 입장에서는 압축 해제는 커널에서 하기 때문에 두가지의 차이가 없다.
저장 공간의 용량이 변수가 된다면, 1번 항을 선택 할 것이며, 좀 더 빠른 부팅을 원한다면, 2번 항을 선택하게 된다.

커널 분석을 시작하는 지점이 꼭 정해져 있는 것은 아니라고 본다. 
시간을 단축하고자 할때는 압축 해제 코드는 그냥 무시 해도 된다는 것이다.
스터디를 시작하는 단계에서 이러한 부분이 가끔 논쟁이 되기도 한다. 

이전 글의 make 로그 과정에서 이미지 생성 과정을 다시 살펴보면, 아래의 그림과 같이 요약 할 수 있다.



순수 커널 부분이 1) vmlinux 이며, 압축 해제 코드와 압축된 커널은 2) vmlinux 가 된다.

그럼 각각 시작 부분을 살펴 보자. 두번째 2) vmlinux 의 시작 부분을 링커스크립터를 통해서 살펴 보자.
이때 빌드 시 사용하는  링커스크립터는 arch/arm/boot/compressed/vmlinux.lds 이다.

$ head -n 20 arch/arm/boot/compressed/vmlinux.lds
/*
 *  linux/arch/arm/boot/compressed/vmlinux.lds.in
 *
 *  Copyright (C) 2000 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.
 */
OUTPUT_ARCH(arm)
ENTRY(_start) /* 시작 심볼 지정 */
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
    /*
     * Discard any r/w data - this produces a link error if we have any,
     * which is required for PIC decompression.  Local data generates
     * GOTOFF relocations, which prevents it being relocated independently
     * of the text/got segments.
     */
    *(.data)
  }

  . = 0;
  _text = .;

  .text : {
    _start = .;        /* [BLEE] _start 심볼을 정의함 , ENTRY(_start) 지정이 이곳이라고 본다. */
    *(.start)
    *(.text)
:


ENTRY 가 _start 로 지정되어 있다. 
다음으로 objdump 한 어셈블리 코드를 살펴 보자.
 

$ head -n 20 arch/arm/boot/compressed/vmlinux.objdump

vmlinux:     file format elf32-littlearm

Disassembly of section .text:

00000000 <start>:
       0:       e1a00000        nop                     (mov r0,r0)
       4:       e1a00000        nop                     (mov r0,r0)
       8:       e1a00000        nop                     (mov r0,r0)
       c:       e1a00000        nop                     (mov r0,r0)
      10:       e1a00000        nop                     (mov r0,r0)
      14:       e1a00000        nop                     (mov r0,r0)
      18:       e1a00000        nop                     (mov r0,r0)
      1c:       e1a00000        nop                     (mov r0,r0)
      20:       ea000002        b       30 <_text+0x30>
      24:       016f2818        .word   0x016f2818
      28:       00000000        .word   0x00000000
      2c:       001307a8        .word   0x001307a8
      30:       e1a07001        mov     r7, r1
      34:       e1a08002        mov     r8, r2

 
0 번지가 start 로 지정되어 있다. 여기서 한 가지 의문이 드는데, 링커스크립터에서는 _start 라고 되어 있는데, 실제로 objdump 한 파일에서는 start 라고 되어 있다. 링커스크리터에서 ENTRY()는 심볼를 지정하게 되었는데, 링커스크립터 내에서 _start 심볼을 .start 섹션의 처음 부분으로 지정하고 있다. 

이 부분의 시작 코드는 arch/arm/boot/compressed/head.S 의 start 심볼이다.

첫번째 1) vmlinux의 시작 부분을 링커스크립터를 통해서 살펴 보자.

$ vi arch/arm/kernel/vmlinux.lds

OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{


 ENTRY 가 stext 로 지정되어 있다. 
 다음으로 objdump 한 파일로 첫 시작 부분을 살펴 보자.
 

$ head -n 20 vmlinux.objdump

vmlinux:     file format elf32-littlearm

Disassembly of section .head.text:

c0008000 <stext>:
c0008000:       e321f0d3        msr     CPSR_c, #211    ; 0xd3
c0008004:       ee109f10        mrc     15, 0, r9, cr0, cr0, {0}
c0008008:       eb061b5a        bl      c018ed78 <__lookup_processor_type>
c000800c:       e1b0a005        movs    sl, r5
c0008010:       0a061b69        beq     c018edbc <__error_p>
c0008014:       e28f302c        add     r3, pc, #44     ; 0x2c
c0008018:       e8930110        ldm     r3, {r4, r8}
c000801c:       e0434004        sub     r4, r3, r4
c0008020:       e0888004        add     r8, r8, r4
c0008024:       eb00004b        bl      c0008158 <__vet_atags>
c0008028:       eb0873f4        bl      c0225000 <__init_begin>
c000802c:       eb000007        bl      c0008050 <__create_page_tables>
c0008030:       e59fd00c        ldr     sp, [pc, #12]   ; c0008044 <stext+0x44>
c0008034:       e28fe004        add     lr, pc, #4      ; 0x4

 

이 부분의 시작 코드는 arch/arm/kernel/head.S 의 stext 심볼이다.
Posted by blee
,
커널 버젼은 3.x , CPU는 ARM 멀티코어인 exynos4를 선택한다.

1. 커널 소스 받기
 
2. 툴체인 설치하기
  1) codesourcery 에서 다운로드 ( http://www.codesourcery.com/sgpp/lite/arm )
  2) root 권한으로 설치

$ cd /
$ tar zxvf /다운로드경로/codesourcery.tar.gz
$
  3) 툴체인은 codesourcery 것을 사용한다.
     일전에 3개의 툴체인을 동일한 환경에서 벤치마크를 한 적이 있다.
     예상대로 
codesourcery가 가장 빠른 성능을 보였다. 

  4) 예전에 받은 것을 그대로 사용한다. 만약 최근에 릴리즈 된 툴체인을 사용하려 한다면, 다운로드 URL과
      설치 경로가 지금 기록과 다를 수 있다.
 
3. 환경변수 설정 및 컴파일 테스트
  1) 툴체인의 설치 경로는 /opt/codesourcery/ 밑으로 된다.
  2) 환경변수 PATH에 컴파일러 및 실행 가능한 파일의 경로 /opt/codesourcery/bin 를 추가한다.
  3) 툴체인을 검증하기 위해서 테스트로 컴파일 해 본다.

$ export PATH=$PATH:/opt/codesourcery/bin
$ arm-none-linux-gnueabi-gcc -o test test.c
$

  4) 로그인시 자동 설정되도록 ~/.bash_profile 수정

$ vi ~/.bash_profile
PATH=$PATH:$HOME/bin:/sbin:/opt/codesourcery/bin
export PATH


4. 커널 컴파일

$ cd /KERNEL_ROOT/
$ export ARCH=arm
$ export CROSS_COMPILE=/opt/codesourcery/bin/arm-linux-
$ make distclean
$ make exynos4_defconfig
$ make V=1 > make.out 2>&1
$
  1) 환경변수 ARCH, CROSS_COMPILE를 설정 하는 이유는 별도의 Makefile 수정 없이
      해당 ARCH와 툴체인으로 컴파일 하기 위해서다. 
  2) 커널 설정은 exynos4_defconfig ( arch/arm/configs/exynos4_defconfig ) 를 기본으로
      특별한 변경없이 사용한다.
  3) make 의 V=1 옵션은 컴파일 내용을 자세히 출력하고, make.out 으로 리다이렉션 하는 것은
     혹, 나중에 컴파일 과정이 궁금해 질 수 있기 때문이다.
 
5. ctags, cscope 생성

$ export ARCH=arm
$ export CROSS_COMPILE=/opt/codesourcery/bin/arm-linux-
$ rm -f tags cscope.*
$ make tags
$ make cscope

  1) make 에 의한 ctags, cscope의 생성은 검색 되는 코드의 ARCH 가 arm에 존속되도록 한다.
 
6. 어셈블리 코드 생성
  1) 커널의 어셈블리 코드 생성

$ arm-linux-objdump -d vmlinux > vmlinux.objdump


  2) 압축된 커널의 어셈블리 코드 생성

$ cd arch/arm/boot/compressed/
$ arm-linux-objdump -d vmlinux > vmlinux.objdump
  3) 어셈블리 코드를 생성 해 두는 이유는 소스 분석 중에 복잡한 전처리 과정,
      코드의 살리고, 버리는 부분이 모호 할때, 상수 값이 모호 할때 참조 하면 유용하다.
 
Posted by blee
,
sizes[INDEX_AC].cs_cachep = kmem_cache_create(names[INDEX_AC].name,
                    sizes[INDEX_AC].cs_size,
                    ARCH_KMALLOC_MINALIGN,
                    ARCH_KMALLOC_FLAGS|SLAB_PANIC,
                    NULL, NULL);
 sizes[INDEX_L3].cs_cachep =
            kmem_cache_create(names[INDEX_L3].name,
                sizes[INDEX_L3].cs_size,
                ARCH_KMALLOC_MINALIGN,
                ARCH_KMALLOC_FLAGS|SLAB_PANIC,
                NULL, NULL);
하고, 난뒤 sizes->cs_size != ULONG_MAX 일때까지 while 돌면서 나머지 생성해 준다.
sizes->cs_cachep, sizes->cs_dmacachep...

malloc_sizes[]

0 cs_size=0x20
1 cs_size=0x40
2 cs_size=0x60
3 cs_size=0x80
4 cs_size=0xc0
5 cs_size=0x100
6 cs_size=0x200
7 cs_size=0x400
8 cs_size=0x800
9 cs_size=0x1000
10 cs_size=0x2000
11 cs_size=0x4000
12 cs_size=0x8000
13 cs_size=0x10000
14 cs_size=0x20000
15 cs_size=0xffffffff
 
cache_names[]
 
0 names.name=size-32
1 names.name=size-64
2 names.name=size-96
3 names.name=size-128
4 names.name=size-192
5 names.name=size-256
6 names.name=size-512
7 names.name=size-1024
8 names.name=size-2048
9 names.name=size-4096
10 names.name=size-8192
11 names.name=size-16384
12 names.name=size-32768
13 names.name=size-65536
14 names.name=size-131072
15 names.name=<NULL>
Posted by blee
,

[ IRQ 처리 ]

리눅스커널 2009. 10. 2. 14:51
CP15 c1, Control Register 의  V bit 에 의해 hign vector 를 사용하도록 설정된다.
arch/arm/mm/proc-v6.S
__v6_setup 에서 Control Register 를 읽고 v6_crval 값에 지정된 것으로 clear 후 set 한다.
여서기 V bit 가 설정됨으로 high vector 가 사용된다.

Control Register 를 저장 하지 않지만.. 나중에 __enable_mmu -> __turn_mmu_on 에서 저장함
__v6_setup:
:
adr r5, v6_crval
ldmia   r5, {r5, r6}
mrc p15, 0, r0, c1, c0, 0       @ read control register
bic r0, r0, r5          @ clear bits them
orr r0, r0, r6          @ set them
mov pc, lr              @ return to head.S:__ret
:
v6_crval:
    crval   clear=0x01e0fb7f, mmuset=0x00c0387d, ucset=0x00c0187c
:
 
arch/arm/kernel/entry-armv.S
 vector table 과 handler 가 정의되어 있다.
  
devicemaps_init
 high vector(0xffff0000) 페이지 테이블 생성
trap_init
 high vector(0xffff0000) 로 복사
 
init_IRQ
 kernel IRQ 스택 초기화
 
s3c_init_irq
 CPU 인터럽터 컨트롤러 초기화
 
동작은
arch/arm/kernel/entry-armv.S  의 vector table , handler 에서 처리후
asm_do_IRQ  호출한다.
Posted by blee
,
struct stack {
    u32 irq[3];
    u32 abt[3];
    u32 und[3];
} ____cacheline_aligned;
static struct stack stacks[NR_CPUS];
CPU 갯수만큼 stacks 이 만들어 진다.
IRQ_MODE, ABT_MODE, UND_MODE 의 sp 를 지정한다.
irq[0] <- IRQ_MODE 의 sp 시작
irq[1]
irq[2]
abt[0] <- ABT_MODE 의 sp 시작
abt[1]
abt[2]
und[0] <- UND_MODE 의 sp 시작
und[1]
und[2]

[ setup_per_cpu_areas ] init/main.c
 
DEFINE_PER_CPU 로 정의된 것들이 컴파일 되면서, __per_cpu_start ~__per_cpu_end 사이의 영역에 들어간다.
DEFINE_PER_CPU(struct cpuinfo_arm, cpu_data);

[ smp_prepare_boot_cpu ] arch/arm/kernel/smp.c
 
cpu_data.idle = current 를 저장한다.

 
[ sched_init ] kernel/sched.c
 
cpu_rq(cpu) 는 해당 cpu의 struct rq자료를 리턴한다.  각 cpu 의 run queue 자료를 초기화 한다.
 
[ preempt_disable ] include/linux/preempt.h
 
CONFIG_PREEMPT 가 정의 되어 있어야 한다. 정의 되어 있지 않다면, preempt_disable , preempt_enable 는 빈 함수로 구현된다.
 
// include/linux/preempt.h
#define preempt_disable() \
do { \
    inc_preempt_count(); \
    barrier(); \
} while (0)
#define preempt_enable() \
do { \
    preempt_enable_no_resched(); \
    barrier(); \
    preempt_check_resched(); \
} while (0)
#define add_preempt_count(val) do { preempt_count() += (val); } while (0)
#define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)
#define inc_preempt_count() add_preempt_count(1)
#define dec_preempt_count() sub_preempt_count(1)
#define preempt_count() (current_thread_info()->preempt_count)
 
[ build_all_zonelists ] mm/page_alloc.c
 
system_state = SYSTEM_BOOTING 이면
__build_all_zonelists(NULL); 를 호출한다.

 
[ sort_main_extable ] kernel/extable.c
 
섹션 __start___ex_table 부터 __stop___ex_table 에 까지 등록되어 있는 struct exception_table_entryinsn 값으로 정열한다.
struct exception_table_entry
{
    unsigned long insn, fixup;
};

[ trap_init ] arch/arm/kernel/traps.c
 
0xffff0000 에 __vectors_start 부터 __vectors_end  를 복사한다.
0xffff0000 + 0x200 에 __stubs_start 부터 __stubs_end 를 복사한다.
0xffff0000 + 0x1000 - kuser_sz 에 __kuser_helper_start 부터 kuser_sz 만큼 복사한다.
kuser_sz = __kuser_helper_end - __kuser_helper_start

'리눅스커널' 카테고리의 다른 글

[ kmem_cache_init ] mm/slab.c  (0) 2009.10.02
[ IRQ 처리 ]  (0) 2009.10.02
[ devicemaps_init ] arch/arm/mm/mmu.c  (0) 2009.10.02
[ bootmem_bootmap_pages ] mm/bootmem.c  (0) 2009.10.02
[ create_mapping ] arch/arm/mm/mmu.c  (0) 2009.10.02
Posted by blee
,
페이지 테이블에서 VMALLOC_END ~ 끝까지  영역을 0 으로 초기화 한다.
high-vectors ( 0xffff0000 ) 맵핑한다.
mdesc->map_io() 호출하여, device 맵핑한다.
SMDK2410 경우엔 smdk2410_map_io 호출한다.

swapper_pg_dir ( 0xC0004000 )

 offset   level 1 descriptor 
 0x000 * 4   0
  :   :
 0xF00 * 4 0x4a000452
 0xF01 * 4
0x48000452
 0xF02 * 4
0x4c000452
 0xF03 * 4 
0x4d000452
 0xF04 * 4 
0x50000452
 0xF05 * 4   0x51000452
 0xF06 * 4 
0
 0xF07 * 4 
0x53000452
  :  
 0xF64 * 4 
0x56000452
  :  
 0xFFE * 4 
0x30002031
 0xFFF * 4 0x30002431
 
[ free_area_init_node ] mm/page_alloc.c
contig_page_data 의 node_id, node_start_pfn 값을 설정하고
calculate_node_totalpages() 를 호출하여, node_spanned_pages, node_present_pages 값을 계산한다.
alloc_node_mem_map() 를 호출해서 page 배열을 할당하고, node_mem_map 에 시작 위치를 가리킨다.mem_map 값으로도 설정한다.
free_area_init_core() 를 호출하여, 각 page 들을 초기화 한다.
 
[ free_area_init_core ] mm/page_alloc.c
 
zone 를 초기화 한다.
 
[ devicemaps_init ] arch/arm/mm/mmu.c

vectors 를 bootmem 에서 PAGE_SIZE 만큼 할당 받는다.
페이지 테이블 0xffff0000 에 등록한다.
mdesc->map_io 를 호출한다.
MACHINE_START(INTEGRATOR, "ARM-Integrator")
    /* Maintainer: ARM Ltd/Deep Blue Solutions Ltd */
    .phys_io    = 0x16000000,
    .io_pg_offst    = ((0xf1600000) >> 18) & 0xfffc,
    .boot_params    = 0x00000100,
    .map_io     = ap_map_io,
    .init_irq   = ap_init_irq,
    .timer      = &ap_timer,
    .init_machine   = ap_init,
MACHINE_END

'리눅스커널' 카테고리의 다른 글

[ IRQ 처리 ]  (0) 2009.10.02
[ cpu_init ] arch/arm/kernel/setup.c  (0) 2009.10.02
[ bootmem_bootmap_pages ] mm/bootmem.c  (0) 2009.10.02
[ create_mapping ] arch/arm/mm/mmu.c  (0) 2009.10.02
[ bootmem_init_node ] arch/arm/mm/init.c  (0) 2009.10.02
Posted by blee
,
비트맵을 표시해 줄 page 몇 개가 필요한가?
 
[ find_bootmap_pfn ] arch/arm/mm/init.c
 부트맵이 위치할 pfn 를 찾는다. 

[ init_bootmem_core ] mm/bootmem.c
contig_bootmem_data 초기화 한다.
node_bootmem_map ( 부트맵 ) 을 모두 1 로 설정한다.

bootmem 할당
Posted by blee
,
인자로 넘어온 struct map_desc *md 해당하는 영역에 대한 페이지 테이블 맵핑 작업을 한다.
 
 while (length >= (PGDIR_SIZE / 2)) {
        alloc_init_section(virt, virt + off, prot_sect);
        virt   += (PGDIR_SIZE / 2);     // + 0x100000
        length -= (PGDIR_SIZE / 2);
    }
    while (length >= PAGE_SIZE) {
        alloc_init_page(virt, virt + off, prot_l1,prot_pte);
        virt   += PAGE_SIZE;
        length -= PAGE_SIZE;
    }
 
length 를 0x100000 감소 시키고, virt 는 0x100000 를 증가 시키며, alloc_init_section 를 호출해서 section 에 대한 맵핑을 한다.
length 가 0x100000 보다 작게 남는 경우엔, alloc_init_page 를 호출해서 1차 페이지 디스크립터 와 2차 페이지 디스크립터를 생성한다.
[ alloc_init_section ] arch/arm/mm/mmu.c
 
pmd_off_k 주어진 virtual address page directory index ( 2048 엔트리중에서 )
그리고, virtual address C01*4, C03*4 같이 홀수 이면, pmdp두번째 맴버에 값을 저장하도록 한다.
flush_pmd_entry 호출한다.
clear_pmd_entry, flush_pmd_entry 차이

[ alloc_init_page ] arch/arm/mm/mmu.c
 
pmd_off_k 주어진 virtual address page directory index ( 2048 엔트리중에서 )
*pmdp값이 비어 있는지 확인한다. 비어 있으면, 아직 pte 만들지 않았다
만들지 않았다면, alloc_bootmem_low_pages 부터 4K 할당 받는다.
할당 받은 address ptep 저장되고, 주소는 pmdp[0], 1024 만큼 더한 주소는 pmdp[1] 각각 저장된다.
 
 
    pgd                            pte
|            |
+--------+  +0
|            |--------> +------------+  +0
+--------+  +4          |  h/w pt 0    |
|            |--------> +------------+ +1024
+--------+  +8          |  h/w pt 1    |
|            |               +------------+ +2048
+--------+               |  Linux pt 0  |
|            |               +------------+ +3072
+--------+               |  Linux pt 1  |
|            |               +------------+ +4096
 
pmd_page_vaddr *pmdp 주어진 address 2048 만큼 더한다. 속성은 mask 날린다.
__pte_index 512 앤트리에 대한 인덱스이다.
pte_offset_kernel 커널 pte 주소를 반환하고, set_pte_ext 호출하여, 커널 pte 저장하고, 2048만큼 곳에 h.w pte 저장한다.

Posted by blee
,
meminfo bank 대해 map_memory_bank, create_mapping, alloc_init_section  차례로 호출하여 물리메모리에 대한 페이지 테이블 맵핑을 한다. SMDK2410 에서 노드가 1개이고, meminfo 정보가 아래와 같다면, 다음과 같은 페이지 테이블을 생성한다.

meminfo.nr_banks = 1
bank[0].start=0x30000000
bank[0].size=0x4000000
bank[0].node=0 

swapper_pg_dir ( 0xC0004000 )
 offset  level 1 descriptor
 0x000 * 4  0
  :  0
 0xC00 * 4  0x3000041e
 0xC01 * 4  0x3010041e
  :   :
 0xC3F * 4  0x33f0041e 
 0xC40 * 4  0 
  :  0 
  0xDFE * 4  0 
  :  
  0xFFF * 4  

bootmem_bootmap_pages 호출하여, 부트 allocator 표시 페이지 갯수를 표현하기 위해 얼마 만큼의 PAGE_SIZE 필요한지 계산한다.
pfn 갯수는 0x4000 , 비트 단위로 보기 때문에 byte 표현하면, 0x800 크기가 되는데, 이는 한개의 PAGE 표현 있다.
find_bootmap_pfn 호출하여, 부트 allocator 사용할 영역을 할당 받는다. 0x00030314 pfn 리턴하게 되는데, 기가 부트 allocator bitmap 영역이 것이다.
node_online_map 현재 node 대한 bit set 하고,
NODE_DATA(node) &contig_page_data 리턴한다.
init_bootmem_node , init_bootmem_core  차례로 실행하여, contig_page_data.bdata 가리키는 contig_bootmem_data 설정한다. 여기서 contig_bootmem_data.node_bootmem_map 에 할당받은 bitmap 주소가 지정된다. (0x3031400 가상주소 할당된다. 0xc0314000 ) 그리고, 0x800 만큼 0xff 로 초기화 한다.0x4000 만큼의 영역이 비트로 표현되기 때문에, byte로 변환된 사이즈 0x800 이 되는 것이다.
free_bootmem_node node 만큼 루프를 돌며 호출하는데, 하나의 노드이기 때문에 역쉬 한번만 호출한다.
free_bootmem_core 호출되어, 노드에 속한 물리메모리 만큼을 test_and_clear_bit 호출하여, 0 으로 초기화 한다. 모든 메모리 영역을 사용가능으로 설정하는 것이다.
reserved 영역은 다시 reserve_bootmem_node 호출하여 사용중 으로 표시 것이다.

Posted by blee
,