[Linker] GNU linker LD 사용하기 – Linker Scripts (.ld)

그냥 내용이나 조금 보려고 했는데, 보다보니 번역을 하고 있다.. 왜지…..

찾아보니 korea.gnu.org에 이미 번역된 문서(http://korea.gnu.org/manual/release/ld/ld-mahajjh/ld_3.html#SEC6)가 있다.
부끄러우니 이걸 보도록 하자.

원문: Using LD, the GUN linker – Linker Scripts

번역: 양정석 (dasomoli@gmailREMOVETHIS.com)

모든 link는 linker script로 제어된다. 이 스크립트는 linker script language로 쓰여진다.

linker script의 목적은 input file 내의 section을 output file로 어떻게 맵핑(map)해야 하는지, 그리고 output file의 memory layout을 어떻게 해야 하는지 설명하는 거다. 대부분의 linker script는 이 이상을 하지 않는다. 그러나 필요하면 linker script는 linker에게 아래에 설명된 command들을 사용해서 다른 동작을 수행할 수 있다.

linker는 언제나 linker script를 사용한다. 제공되지 않으면 linker는 linker executable 내부에 compile된 default script를 사용한다. '--verbose' command line option을 사용해서 default linker script를 볼 수 있다. '-r' 이나 '-N' 같은 command line option은 default linker script에 영향을 준다.

자신만의 linker script를 '-T' command line option을 사용해서 줄 수 있다. 이렇게 하면 그 linker script가 default linker script를 대신한다. 또한, input 파일처럼 이름을 붙여서 링커에게 줄 수도 있다.

기본적인 개념

링커는 input file들을 하나의 output file로 합친다. input file과 각 output file은 object file format으로 되어 있다. 그래서 각 파일을 object file이라고 부른다. output file은 executable이라고 부르기도 한다. 각 object file은 sections의 리스트를 가진다. input file 안에 있는 section을 input section이라고 하고, output file 안에 있는 section을 output section이라고 한다.

각 object file 안의 section은 하나의 name과 하나의 size를 갖는다. 거의 모든 섹션은 section contents라고 하는 data의 associated block을 갖는다. loadable로 표시된 section은 output file이 실행될 때 memory로 load된다. content가 없는 section은 allocatable일 수 있는데 이는 메모리는 set되지만 거기에 특별한 게 load되지는 않는다(특정 경우에 이 메모리는 0으로 초기화된다)는 뜻이다. loadable도 allocatable도 아닌 섹션은 일반적으로 디버깅 정보를 갖는다.

모든 loadable 또는 allocatable output section은 두가지 address를 갖는다. 먼저 VMA(virtual memory address)는 output file이 실행될 때 갖을 address이다. 다음으로 LMA(load memory address)는 그 section이 load될 address이다. 대부분의 경우에 두 address는 같다. 다를 수 있는 예는 data section이 ROM에 load되고 program이 시작될 때 RAM으로 복사될 때이다(이 테크닉은 ROM based system에서 global variables을 초기화할 때 자주 쓰인다). 이 경우 ROM address는 LMA가 되고, RAM address는 VMA가 될 것이다.

objdump -h 로 object file의 section들을 볼 수 있다.

모든 object file은 symbol table이라고 부르는 symbol의 list를 갖는다. symbol은 defined 되거나 undefined 될 수 있다. 각 symbol은 이름을 갖고, 각 defined symbol은 다른 정보들과 함께 address를 갖는다. C/C++을 object file로 컴파일하면 모든 defined function과 global/static variable의 defined symbol을 얻게 된다. input file에서 참조되는 모든 undefined function과 global variable은 undefined symbol이 될 거다.

nm이나 objdump -t로 object file의 symbol을 볼 수 있다.

Linker Script 형식

Linker script는 command로 이뤄진 text file이다. 각 command는 argument를 갖는 keyword이거나 또는 symbol로의 assignment 둘 중 하나다. ';'으로 각 command를 구분할 수 있다. white space는 일반적으로 무시된다.

file이나 format name같은 string은 직접 들어갈 수 있다. file name에 다른 file name을 구별할 때 쓰이는 ','같은 문자가 포함되어 있으면, ""로 감쌀 수 있다. “”를 파일 이름으로 쓸 수 있는 방법은 없다.

C처럼 linker script안에 '/*', '*/'로 comment를 달 수 있다.

간단한 linker script 예제

많은 linker script들은 정말 간단하다.

가능한 가장 간단한 linker script는 딱 하나의 'SECTIONS' command를 갖는 것이다. 'SECTIONS' command는 output file의 memory layout을 설명하는데 쓰인다.

'SECTIONS' command는 강력한 command이다. 여기서 우리는 가장 간단히 사용하는 것을 설명할거다. 여러분의 프로그램이 code, initialized data, uninitialized data로만 구성되어 있다고 하면 거기에는 '.text', '.data', '.bss' section이 있게 된다. 이에 더해서 여러분의 input files안에 있는 section들만 있다고 하자.

이 예제를 위해 그 code가 address 0x10000에 load되어야 하고, 그 data는 address 0x8000000에서 시작되어야 한다고 하자.

아래가 이를 위한 link script이다.

SECTIONS
{
  . = 0x10000;
  .text : { *(.text) }
  . = 0x8000000;
  .data : { *(.data) }
  .bss : { *(.bss) }
}

'SECTIONS' command는 'SECTIONS' 키워드, 그 뒤에 symbol assignments과 output section 설명을 ‘{‘, ‘}’로 감싸서 쓴다.

위 예제의 첫 줄은 location counter인 special symbol ‘.’을 지정한다. 다른 방법(이건 뒤에 설명)으로 output section의 주소를 지정하지 않으면, location counter의 현재 값으로부터 그 address가 지정된다. 그러고 나면 location counter는 output section의 size만큼 더해진다. 'SECTIONS' command를 시작할 때  location counter는 값 '0'을 갖는다.

두번째 줄은 output section ‘.text’를 define한다. ':'이 필요하다. '{', '}' 안에, output section name 뒤에 이 output section에 두어야 할 input section들의 이름을 나열한다. '*'는 모든 파일 이름에 매치되는 wildcard이다. 표현식 '*(.text)' 은 모든 input file들 안의 '.text' input section을 뜻한다.

location counter가 '0x10000'이었기 때문에, output section '.text'가 defined될 때, linker는 output file내의 '.text' section의 주소를 '0x10000'으로 지정할 거다.

뒤의 줄들은 output file의 '.data''.bss'를 define한다. linker는 '.data' output section을 '0x8000000'에 둔다. 그러고나면 location counter는 '0x8000000' + '.data' output section의 size가 된다. 그 결과 linker는 '.bss' output section을 메모리 내에서 '.data' output section 바로 뒤에 두게 된다.

linker는 필요하다면 location counter를 더해서 각각의 output section에 필요한 alignment를 맞춘다. 이 예제에서는 '.text''.data' section들의 지정된 주소들은 아마 alignment constraint를 만족할 것이고 linker는 '.data''.bss' section 사이에 작은 갭을 만들어야 했을 수 있다.

이게 다다. 간단하고 완전한 하나의 linker script다.

간단한 Linker Script Command

간단한 linker script command들을 설명한다.

  • Entry Point: entry point를 설정
  • File Command들: 파일을 다루는 command들
  • Format Command들: object file format을 다루는 command들
  • 기타 Command들: 다른 linker script command들

Entry point 설정

프로그램 내의 실행되는 첫번째 instruction을 entry point라고 부른다. ENTRY linker script command로 이 entry point를 설정할 수 있다. 그 argument는 symbol name이다:

ENTRY(symbol)

entry point를 설정하는 여러 방법이 있다. linker는 다음 방법들을 순서대로 실행하면서 entry point를 설정하고, 그 중 하나가 성공하면 멈춘다:

  • '-e' entry command-line 옵션;
  • linker script 안의 ENTRY(symbol) command;
  • symbol start가 defined되어 있다면 그 값;
  • '.text' section이 있다면 그 첫 byte의 address;
  • address 0.

파일을 다루는 command들

여러 linker script command들이 파일을 다룬다.

INCLUDE filename

linker script filename 을 이 지점에 include한다. 현재 디렉토리에서 찾고 나서 -L 옵션으로 지정된 디렉토리 내에서 찾는다. INCLUDE는 10 레벨까지 nest call이 가능하다.

INPUT(filefile, ...)

INPUT(file file ...)

INPUT command 는 linker에게 그 link 내에 command line 상에서 정한 것처럼 정해진 file들을 include하도록 지시한다. 예를 들어, 'subr.o'를 link하는 모든 때에 include하고 싶지만 매번 command line에 이를 입력하고 싶지는 않을 때 'INPUT (subr.o)'를 linker script 내에 적을 수 있다. 사실 linker script 안에 모든 input files를 적어두고, linker를 아무 것도 없이 '-T' 옵션으로 실행할 수 있다. linker는 먼저 현재 디렉토리 내에서 그 파일을 열 수 있는지 시도해보고, 없으면, archive library search path를 통해 찾는다. Command Line Options section 내의 '-L' 의 설명을 한번 봐라. 'INPUT (-lfile)'을 쓰면 ld 는 command line argument '-l'로 한 것 처럼 libfile.a로 그 이름을 바꾼다. implicit linker script 내에서 INPUT command를 사용하면, 그 파일들은 그 link상의 linker script file이 included된 그 지점에서 include될 것이다. 이는 archive searching에 영향을 준다.

GROUP(filefile, ...)

GROUP(file file ...)

GROUP command는 정해진 file들 모두 archive되어야 한다는 것, 그리고 새로운 undefined reference가 만들어지기 전까지 반복적으로 찾아진다는 것을 제외하면 INPUT과 비슷하다. Command Line Options section 내의 '-(' 의 설명을 봐라.

OUTPUT(filename)

OUTPUT command는 output file의 이름을 정한다. linker script 안에서 OUTPUT(filename) 을 사용하는 것은 command line (Command Line Options section을 봐라) 상에서 '-o filename' 을 사용하는 것과 정확히 같다. 둘 다 사용되면, command line option이 더 우선된다. OUTPUT command는 보통 기본 값인 'a.out' 말고 다른 file을 기본 값으로 define하려고 할 때 사용할 수 있다.

SEARCH_DIR(path)

SEARCH_DIR command는 ld가 library들을 archive할 때 보는 경로의 리스트에 path를 추가한다. SEARCH_DIR(path)를 사용하는 것은 command line (Command Line Options section을 봐라) 상에서 '-L path'를 사용하는 것과 정확히 같다. 둘 다 사용되면, linker는 두 path 모두 찾는다. command line option에서 지정된 path를 먼저 찾는다.

STARTUP(filename)

STARTUP command는 command line 상에서 먼저 정해졌던 것처럼 그 filename이 link될 첫 input file이 되는 것을 제외하면 INPUT command와 똑같다. 이는 entry point가 언제나 첫 file의 시작인 시스템을 사용할 때 유용할 거다.

Object file format을 다루는 command들

두 linker script command가 object file format을 다룬다.

OUTPUT_FORMAT(bfdname)

OUTPUT_FORMAT(defaultbiglittle)

(생략)

TARGET(bfdname)

(생략)

다른 linker script command들

다른 linker script command들이 몇 있다.

ASSERT(expmessage)

exp 가 0이 아님을 확인한다. 0이라면, linker가 error code와 message를 찍으면서 종료된다.

EXTERN(symbol symbol ...)

output file에 들어갈 symbol을 undefined symbol로 강제한다. 예를 들면 이는 standard library들로부터 추가 모듈들의 linking을 trigger할 수 있다. 각 EXTERN마다 여러 symbol들을 나열할 수 있고, EXTERN은 여러 번 사용할 수 있다. 이 command는 '-u' command-line option과 같은 효과를 갖는다.

FORCE_COMMON_ALLOCATION

이 command는 '-d' command-line option과 같은 효과를 갖는다. relocatable output file이 지정 ('-r') 되었다 하더라도, common symbol들에 ld가 space를 assign하도록 만든다.

NOCROSSREFS(section section ...)

이 command는 ld에게 어떤 output section들 사이의 모든 reference들에 관한 error를 올리라고 말하는데 사용될 수 있다. 어떤 종류의 program에서, 특히 임베디드 시스템 상에서 overlay가 사용될 때, 한 section이 메모리로 load되면, 다른 section은 아닐 것이다. 두 section 사이의 모든 direct reference는 error가 된다. 예를 들어, 한 section의 code가 다른 section에 defined된 함수를 호출한다면 에러일 거다. NOCROSSREF command는 output section 이름의 list를 가져온다. ld가 section들 사이에 어떤 cross references가 발견하면 error를 보고하고, 0이 아닌 exit status로 return한다. NOCROSSREF command는 input section name이 아닌 output section name을 사용한다.

OUTPUT_ARCH(bfdarch)

특정 output machine architecture를 지정한다. argument는 BFD library (BFD section을 봐라) 에 의해 사용되는 이름 중 하나다. objdump'-f' 옵션을 사용해서 object file의 architecture를 볼 수 있다.

Symbol에 값 assign하기

linker script 안에서 symbol에 값을 assign할 수 있다. 이는 symbol을 global symbol로 define한다.

간단한 assignment

C assignment operators의 하나를 사용해서 symbol을 assign할 수 있다.

symbol = expression ;

symbol += expression ;

symbol -= expression ;

symbol *= expression ;

symbol /= expression ;

symbol <<= expression ;

symbol >>= expression ;

symbol &= expression ;

symbol |= expression ;

첫번째 경우는 expression의 값으로 symbol을 define한다. 다른 모든 경우는, symbol이 이미 defined되어 있어야 하고, 값이 그에 따라 조정될 것이다.

special symbol name '.' 은 location counter를 나타낸다. SECTIONS command 내에서만 이를 사용할 거다.

expression 뒤에 ';'을 꼭 써야 한다.

Expression들은 아래에 defined되어 있다. Linker Script내의 expression 절을 봐라.

symbol assignment는 command들의 오른쪽에 command처럼, SECTIONS command 내의 statement처럼, SECTIONS command 내의 output section description의 일부처럼 작성될 수 있다.

symbol의 section은 그 expression의 section으로부터 정해진다. Expression의 Section 절을 봐라.

여기 symbol assignment가 사용될 수 있는 세가지 다른 곳을 보여주는 예제가 있다.

floating_point = 0;
SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
    }
  _bdata = (. + 3) & ~ 4;
  .data : { *(.data) }
}

이 예제에서 symbol 'floating_point'는 0으로 defined될 것이다. symbol '_etext'는 ‘.text’ input section 뒤의 address로 defined될 것이다. symbol '_bdata'는 ‘.text’ output section 뒤에 4 byte boundary로 올려서 aligned된 address로 defined될 것이다.

PROVIDE

어떤 경우에 어떤 symbol이 reference되긴 하지만 그 link에 포함된 어떤 object에도 defined되어 있지 않을 때 linker script가 그 symbol만 define하고 싶을 수 있다. 예를 들면, symbol 'etext'가 defined된 traditional linker들 말이다. 그러나 ANSI C는 error 없이 'etext'를 function name처럼 사용할 수 있어야 함을 요구한다. PROVIDE keyword는 reference되지만, defined되지는 않는 'etext'같은 symbol을 define하는데 사용될 수 있다. syntax는 PROVIDE(symbol = expression) 이다.

여기 'etext'를 define하는 PROVIDE 사용 예제다.

SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
    }
}

이 예제에서 프로그램이 '_etext'를 (앞에 _를 붙여서) define한다면, linker는 multiple definition error가 날 거다. 다르게 말하면, 그 프로그램이 'etext'를 (앞에 _를 붙이지 않고) define한다면, linker는 프로그램에서 그 definition을 조용히 사용할 것이다. program이 'etext'를 reference하긴 하지만, define하지는 않았다면, linker는 그 linker script 안의 definition을 사용할 것이다.

SECTIONS command

SECTIONS command는 linker에게 input section을 어떻게 output sections으로 map할지, 그리고 output section을 메모리 내에 어떻게 둘지를 알린다.

SECTIONS command의 형식은 다음과 같다.

SECTIONS
{
  sections-command
  sections-command
  ...
}

sections-command는 다음 중 하나가 될 수 있다.

  • ENTRY command (entry point 설정하기 절을 보라)
  • symbol assignment (Symbol에 값 assign하기)
  • output section description
  • overlay description

ENTRY command와 symbol assignment는 편의를 위해 이들 command들 내에 location counter를 사용해서 SECTIONS command 내에 있을 수 있다. 이는 또한 output file의 layout 내에서 의미 있는 지점에서 이들 command를 사용할 수 있기 때문에 linker script를 이해하기 쉽게 만들어 준다.

Output section description과 overlay description은 아래에서 설명한다.

linker script에서 SECTIONS command를 사용하지 않으면, linker는 각 input section을 input file에서 처음 나오는 section 순서대로 이름이 같은 output section내에 둘 것이다. 예를 들어, 첫번째 파일 내에 모든 input section들이 있으면 output file 내의 section들의 순서는 첫번째 파일 내의 순서와 같을 거다. 첫 section은 address 0에 있을 것이다.

Output section description

output section의 전체 description은 다음과 같다.

section [address] [(type)] : [AT(lma)]
  {
    output-section-command
    output-section-command
    ...
  } [>region] [:phdr :phdr ...] [=fillexp]

대부분의 output section은 거의 대부분의 output section attribute를 사용하지 않는다.

section 앞 뒤로 whitespace가 있어야 하므로 section 이름은 모호하지 않게 된다. ‘:’과 ‘{‘, ‘}’ 또한 있어야 한다. 줄을 바꾸거나 다른 white space는 맘대로 하면 된다.

output-section-command는 다음 중 하나일 수 있다.

  • symbol assignment (Symbol에 값 assign하기 절을 봐라)
  • input section description (Input section description 절을 봐라)
  • 직접 포함하는 data 값 (Output section data 절을 봐라)
  • special output section keyword (Output section keyword 절을 봐라)

Output section 이름

output section의 이름은 section이다. section은 output format에 따라 제약 사항을 만족해야 한다. 적은 수의 section만을 지원하는 format에서는 a.out같은 이름이 그 format에 의해 지원되는 이름 중의 하나여야 한다(예를 들어, a.out'.text', '.data' 또는 '.bss'만 허용한다). output format이 (Oasys 처럼) 이름이 아닌 숫자로만 모든 section을 지원하면 그 이름은 따옴표로 둘러싼 숫자로된 string으로만 넣어야 한다. section 이름은 문자들로 아무렇게나 구성될 수 있지만 ‘,’같은 일반적인 문자가 있는 이름은 따옴표로 감싸야 한다.

output section 이름 '/DISCARD/'는 좀 특별하다; Output section 폐기 절 참고

Output section address

address는 output section의 VMA (virtual memory address) 에 대한 expression이다. address를 주지 않으면, linker는 region이 있다면 이를 기준으로 설정하고, 아니면 location counter의 현재 값을 기준으로 설정한다.

address를 주면 output section의 address는 그걸로 정확히 설정된다. addressregion 둘 다 주지 않으면, output section의 address는 location counter의 현재 값을 output section의 alignment 요구사항에 맞춰 align된 값으로 설정된다. output section의 alignment 요구사항은 output section 내에 포함된 모든 input section 중 가장 엄격한 alignment가 적용된다.

예를 들어,

.text . : { *(.text) }

.text : { *(.text) }

는 미묘하게 다르다. 첫번째 것은 '.text' output section의 address를 location counter의 현재 값으로 설정할 것이다. 두번째 것은 location counter의 현재 값이 '.text' input section의 가장 엄격한 alignment로 aligned된 값으로 설정된다.

address는 임의의 expression이 될 수 있다; Linker Script내의 Expression 절을 참고. 예를 들어, 0x10 boundary 상에 section을 align하길 원하면, 그 section address의 마지막 4 비트는 0이 되고, 다음처럼 하면 된다.

.text ALIGN(0x10) : { *(.text) }

이는 ALIGN이 현재 location counter를 지정된 값으로 올려서 aligned해서 return하므로 잘 동작한다.

section에 address를 지정하는 것은 location counter의 값을 바꾼다.

Input section description

가장 일반적인 output section command는 input section description이다.

input section description은 가장 기본적인 linker script operation이다. linker에게 메모리 내에 프로그램을 어떻게 lay out할지를 이야기하기 위해 output sections을 사용한다. linker에게 memory layout내로 input file들을 어떻게 map할지를 이야기하기 위해 input section description을 사용한다.

Input section 기본

input section description은 file name으로 구성된다. file name 뒤에는 괄호로 둘러싸인 section name들의 list가 option으로 붙을 수 있다.

file name과 section name은 아래에서 더 설명될 wildcard pattern(Input section wildcard pattern)이 될 수 있다.

가장 일반적인 input section description은 모든 input section을 output section내의 특정 name으로 포함시키는 것이다. 예를 들어, 모든 input '.text' section을 포함시키려면, 아래처럼 쓸 수 있다:

*(.text)

여기서 '*'은 모든 file name에 매치되는 wildcard이다.

둘 이상의 section을 포함하는 두가지 방법이 있다.

*(.text .rdata)
*(.text) *(.rdata)

이 둘의 차이점은 '.text''.rdata' input section이 output section에서 나타날 순서다. 첫번째 예제에서는 이들이 섞인다. 두번째 예제에서는 모든 '.text' input section이 먼저 나타나고 그 다음에 모든 '.rdata' input section들이 뒤따른다.

특정 파일로부터 section들을 포함시킬 때 file name을 지정할 수 있다. 하나 또는 그 이상의 file들이 메모리 내의 특정 위치에 있을 필요가 있는 special data를 갖고 있다면 예를 들어 이렇게 쓸 수 있다.

data.o(.data)

section의 list 없이 file name을 사용한다면, input file내의 이들 모든 section들이 output section 안에 포함될 것이다. 이는 보통 하진 않지만 가끔은 유용할 수 있다. 예를 들면 다음과 같다.

data.o

wild card character가 들어있지 않은 file name을 사용하면 linker는 먼저 linker command line이나 INPUT command 안에 file name이 같이 지정되어 있는지를 먼저 볼 거다. 아니라면 linker는 command line 상에 있을 때처럼 input file로 그 file을 열려고 할거다. 이는 linker가 archive search path에서 그 file을 찾지 않으므로 INPUT command와는 차이가 있다는 것에 주의해라.

Input section wildcard 패턴

input section description에서 file name 또는 section name 아니면 둘 다 wildcard pattern이 될 수 있다.

많은 예제 내에서 봤던 '*'의 file name은 file name에 대한 간단한 wildcard pattern이다.

wildcard pattern은 Unix shell에서 사용되는 것과 비슷하다.

'*'

어떤 수의 character들과도 match

'?'

어떤 single character와 match

'[chars]'

chars의 어떤 single instance와 match; '-' 문자는 소문자를 match하기 위한 '[a-z]'에서처럼 문자의 range를 정하는데 사용된다.

'\'

이 뒤의 다음 character를 quote한다.

file name이 wildcard와 match될 때 wildcard characters는 (Unix 상에서 directory를 나누는데 사용되는) '/' character와는 match되지 않는다. '*'  character를 포함하는 pattern은 예외다; 이는 '/'이 들어 있든 말든 항상 어떤 file name과도 match된다. section 이름에서는 wildcard character가 '/' character와 match된다.

file name wildcard pattern은 command line이나 INPUT command 상에서 명확히 지정된 file들만 match한다. linker는 wildcard를 확장해서 directory를 찾지 않는다.

어떤 file이 하나 또는 그 이상의 wildcard pattern과 match되거나, 또는 어떤 file이 그 file name이 명확히 나타나면서 하나의 wildcard pattern에도 맞으면, linker는 linker script 내에서 먼저 match되는 것을 사용한다.  예를 들어, 다음 순서의 input section description은 'data.o' rule이 사용되지 않기 때문에 아마 error일 거다.

.data : { *(.data) }
.data1 : { data.o(.data) }

보통 linker는 link하는 동안 보인 순서대로 wildcard에 맞는 file과 section을 둔다. 이를 괄호로 싸인 wildcard pattern 전에 (예를 들면, SORT(.text*)처럼) SORT keyword를 써서 바꿀 수 있다. SORT keyword를 쓰면 linker는 output file 안에 file과 section을 두기 전에 name으로 오름차순 정렬한다.

input section들이 가는데가 헷갈린다면 map file을 생성하는 '-M' 링커 옵션을 써라. 이 map 파일은 input section이 output section으로 정확히 어떻게 mapped되는지를 보여준다.

다음 예제는 wildcard pattern이 file을 나누는데 어떻게 사용되는지를 보여준다. 이 linker script는 linker에게 '.text' section을 '.text'안에, 모든 '.bss' section을 '.bss'안에 두도록 지시한다. linker는 대문자로 시작하는 모든 파일로부터의 '.data' section을 '.DATA' 안에 둘 거다. 다른 모든 파일은 linker가 '.data' 안에 '.data'를 둘 거다.

SECTIONS {
  .text : { *(.text) }
  .DATA : { [A-Z]*(.data) }
  .data : { *(.data) }
  .bss : { *(.bss) }
}

common symbol에 대한 input section

common symbol을 위해서는 특별한 표기법이 필요하다. 왜냐하면 많은 object file format에서 common symbol들은 특정 input section을 갖지 않기 때문이다. linker는 common symbol들을 그이름이 'COMMON'인 input section 내에 있는 것처럼 다룬다.

다른 input section들로 하는 것처럼 그냥 'COMMON' section과 file name을 사용할 수 있다. 다른 input file들로부터의 common symbol들을 또 다른 section 내에 두는 것에 비해 한 section 내에 특정 input file로부터의 common symbol들을 두는데 이를 사용할 수 있다.

대부분의 경우에 input file내의 common symbol들은 output file내의 '.bss' section내에 두게 된다. 예를 들면:

.bss { *(.bss) *(COMMON) }

어떤 object file format들은 하나 이상의 common symbol 타입을 갖는다. 예를 들면, MIPS ELF object file format은 standard common symbol들과 small common symbol을 구별한다. 이 경우, linker는 다른 common symbol 타입을 위한 다른 특별한 section name과 '.scommon'을 small common symbol을 위해 사용할 거다. 이는 common symbol의 다른 타입을 메모리의 다른 위치에 map할 수 있도록 한다.

오래된 linker scripts에서는 '[COMMON]'을 볼 수도 있다. 이 표기는 지금 구식으로 취급된다. 이는 '*(COMMON)'과 동등하다.

Input section과 garbage collection

link-time garbage collection이 사용중이면 ('--gc-sections'), 제거되지 말아야할 section들을 표시하는 것이 꽤 유용하다. 이는 KEEP(*(.init)) 이나 KEEP(SORT(*)(.ctors)) 처럼 input section의 wildcard entry를 KEEP()으로 둘러쌈으로써 할 수 있다.

Input section 예제

다음 예제는 완전한 linker script이다. 이는 linker에게 file 'all.o'로부터 모든 section들을 읽어서 그들을 ‘0x10000’위치에서 시작하는 output section 'outputa'의 시작에 두도록 이야기 한다. 그 바로 뒤에 'foo.o' file로부터의 section '.input1' 전부가 같은 output section내에 따른다. 'foo.o'로부터의 section '.input2' 전부는 output section 'outputb'내로 가고, 그 뒤에 'foo1.o'로부터의 section '.input1'이 따른다. 남은 다른 모든 파일로부터의 '.input1''.input2' section들 전부가 output section 'outputc'로 쓰여진다.

SECTIONS {
  outputa 0x10000 :
    {
    all.o
    foo.o (.input1)
    }
  outputb :
    {
    foo.o (.input2)
    foo1.o (.input1)
    }
  outputc :
    {
    *(.input1)
    *(.input2)
    }
}

Output section data

output section command로 output section 내에 BYTE, SHORT, LONG, QUAD 또는 SQUAD를 사용해서 byte 수를 정하는 data를 넣을 수 있다. 각 keyword 뒤에는 괄호 안에 저장할 값을 쓴 expression이 따른다 (Linker Scripts안의 Expression절을 봐라). expression의 값은 location counter의 현재 값에 저장된다.

BYTE, SHORT, LONG과 QUAD command는 1, 2, 4, 그리고 8 bytes를 (각각) 저장한다. Byte들을 저장하고 나서 location counter는 저장된 byte의 수만큼 증가한다.

예를 들어, 다음은 byte 1을 저장하고 symbol ‘addr’의 4 byte 값이 뒤에 온다:

BYTE(1)
LONG(addr)

64 bit host 또는 target을 사용하면 QUADSQUAD는 같다; 둘 다 8 byte 또는 64 bit 값을 저장한다. host와 target 모두 32 bit를 사용하면, expression은 32 bit로 계산된다. 이 경우 QUAD는 64 bit로 zero extended된 32 bit 값을 저장하고, SQUAD는 64 bit로 sign extended된 32 bit 값을 저장한다.

output file의 보통의 경우처럼 output file format이 endianness를 명시하면, 그 값은 그 endianness에 따라 저장된다. object file format이 endianness를 명시하지 않으면, 예를 들어 S-records가 true인 경우, 그 값은 첫 input object file의 endianness에 따라 저장된다.

현재 section의 fill pattern을 설정하려면 FILL command를 사용할 수 있다. 이것 뒤에는 괄호 안의 expression이 온다. 그 section 내의 다른 모든 지정되지 않은 memory regions (예를 들어, input sections에 요구되는 alignment로 인해 남는 gap들)은 그 expression의 two least significant bytes로 필요하면 반복하면서 채워진다. FILL statement는 그 section definition 내에 나타나는 지점 이후의 memory 위치에 적용된다. 하나 이상의 FILL statement를 넣으므로써 output section의 다른 부분에 다른 fill pattern을 쓸 수 있다.

다음 예제는 값 '0x9090'으로 지정되지 않는 메모리 region을 어떻게 채우는지를 보여준다.

FILL(0x9090)

FILL command는 ‘=fillexp‘ output section attribute와 비슷하지만 (Output section 채우기 절을 봐라), 전체 section이 아닌, FILL command 다음의 section 부분에만 영향을 미친다. 둘 다 사용되면 FILL command가 우선된다.

Output section keywords

output section commands처럼 쓸 수 있는 두가지 keyword가 있다.

CREATE_OBJECT_SYMBOLS

이 command는 linker에게 각 input file을 위한 symbol을 만들라고 이야기한다. 각 symbol의 이름은 해당하는 input file의 이름이 된다. 각 symbol의 section은 CREATE_OBJECT_SYMBOLS command가 있는 그 output section이 된다. 이는 a.out object file format에 편리하다. 다른 object file에서는 거의 사용되지 않을 것이다.

CONSTRUCTORS

a.out object file format을 사용해서 linking할 때 linker는 C++ global constructors와 destructors를 지원하기 위해 unusual set construct를 사용한다. ECOFF나 XCOFF 같은 arbitrary section을 지원하지 않는 object file format들을 linking할 때 linker는 알아서 그 이름으로 C++ global constructors와 destructors를 인식한다. 이들 object file format들을 위해서 CONSTRUCTORS command가 linker에게 CONSTRUCTORS command가 나오는 output section 내에 constructor information을 두도록 알려준다. CONSTRUCTORS command는 다른 object file format들에서는 무시된다. symbol __CTOR_LIST__ 는 global constructors의 시작을 표시하고, symbol __DTOR_LIST는 그 끝을 표시한다. list 내의 첫번째 word는 entries의 수이고, 그 뒤에 각 consructor나 destructor의 address가, 그 뒤에 zero word가 하나 붙는다. compiler는 실제로 code를 실행하기 위헤서 조정을 해야 한다. 이들 object file format들을 위해 GNU C++은 보통 subroutine __main으로부터 constructors를 호출한다; __main의 호출은 자동으로 main을 위한 startup code안으로 들어간다. GNU C++은 보통 destructors를 atexit를 사용하거나 아니면 exit로부터 그 funtion을 직접 호출해서 실행한다. arbitrary section name을 지원하는 COFF나 ELF 같은 object file format들에는 GNU C++은 .ctors.dtors sections 안에 global constructors와 destructors의 addresses를 두도록 조정할 것이다. 다음 sequence를 linker script에 두는 것은 GNU C++ runtime code가 보는 table을 build할 것이다.

      __CTOR_LIST__ = .;
      LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
      *(.ctors)
      LONG(0)
      __CTOR_END__ = .;
      __DTOR_LIST__ = .;
      LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
      *(.dtors)
      LONG(0)
      __DTOR_END__ = .;

initialization priority에 global constructors가 실행되는 순서 상의 control을 제공하는 GNU C++ support를 사용하고 있다면, 반드시 정확한 순서로 그들이 실행됨을 보장하기 위해 link time에 constructors를 sort해야 한다. CONSTUCTORS command를 사용하려면, 대신 'SORT(CONSTRUCTORS)'를 사용하라. .ctors.dtors section을 사용하려면, 그냥 '*(.ctors)''*(.dtors)' 대신에 '*(SORT(.ctors))''*(SORT(.dtors))'를 사용하라. 보통 compiler와 linker는 이들 이슈를 알아서 처리할 것이고, 이에 대해 신경쓸 필요가 없을 것이다. 그러나 C++을 사용하고 linker scripts를 작성한다면 이를 고려할 필요가 있을 수 있다.

Output section discarding

linker는 아무 내용도 없는 output section을 만들지 않을 것이다. 이는 어떤 input files내에 있을 수 있는, 혹은 없을 수 있는 input section들을 참조하는데 편리하다. 예를 들어:

.foo { *(.foo) }

는 ‘.foo’ section이 적어도 하나의 input file내에 있을 때만 ‘.foo’ section을 output file 내에 만들거다.

input section description보다는 symbol assignment같은 output section command를 사용한다면, 맞는 input section들이 없어도 output section은 언제나 만들어 질 것이다.

special output section name ‘/DISCARD/’는 input sections을 discard할 때 사용될 수 있다. 이름이 ‘/DISCARD/’인 output section으로 assigned되는 모든 input section들은 output file내에 포함되지 않는다.

Output section attributes

다음처럼 생긴 output section의 full description을 위에서 보았다.

section [address] [(type)] : [AT(lma)]
  {
    output-section-command
    output-section-command
    ...
  } [>region] [:phdr :phdr ...] [=fillexp]

이미 section, address, output-section-command에 대해 설명했다. 이번 절에서는 남은 section attributes를 설명할 거다.

Output section type

각 output section은 type을 가질 수 있다. 이 type은 괄호 안의 keyword이다. 다음 type들이 정의된다:

NOLOAD

그 section은 not loadable로 표시되어야 한다. 그래서 프로그램이 실행될 때 memory로 loaded되지 않을 거다.

DSECT

COPY

INFO

OVERLAY

이들 type 이름들은 backward compatibility를 위해서 지원된다. 잘 쓰이진 않는다. 모두 같은 효과를 갖는다. 그 section은 not allocatable로 표시되어야 한다. 그래서 프로그램이 실행될 때 그 section을 위한 memory가 allocated되지 않는다.

linker는 보통 그에 map되는 input section에 기초해서 output section의 attributes를 set한다. section type을 사용해서 이를 override할 수 있다. 예를 들어, 아래의 script sample에서 ‘ROM’ section은 memory location ‘0’에 지정되고, 프로그램이 실행될 때 loaded될 필요는 없다. ‘ROM’ section의 내용은 linker output file내에 평소처럼 나올거다.

SECTIONS {
  ROM 0 (NOLOAD) : { ... }
  ...
}

Output section LMA

모든 section은 virtual address (VMA)와 load address (LMA)를 갖는다; 기본적인 개념 절을 봐라. output section description에 있을 수 있는 address expression은 VMA를 set한다 (Output section address절을 봐라).

linker는 보통 LMA를 VMA와 같도록 set한다. 이를 AT keyword를 사용해서 바꿀 수 있다. AT keyword 뒤의 expression lma는 그 section의 load address를 지정한다.

이 기능은 ROM image를 쉽게 build하도록 design되었다. 예를 들어, 다음 linker script는 3 output section을 만든다: 0x1000에서 시작하는 '.text', VMA는 0x2000에서 시작하지만 '.text' section의 끝에 load되는 '.mdata', 그리고, address 0x3000에 uninitialized data를 잡는 '.bss'. symbol _data0x2000으로 정의되는데 이는 location counter가 LMA 값이 아닌 VMA 값을 담는다는 것을 보여준다.

SECTIONS
  {
  .text 0x1000 : { *(.text) _etext = . ; }
  .mdata 0x2000 : 
    AT ( ADDR (.text) + SIZEOF (.text) )
    { _data = . ; *(.data); _edata = . ;  }
  .bss 0x3000 :
    { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}
}

이 linker script로 generate되는 프로그램을 위한 실제 run-time initialization code는 ROM image로부터 initialized data를 그 runtime address로 복사하기 위해서 다음과 같은 것을 포함할거다. 이 코드가 linker script가 define하는 symbol들을 어떻게 유용하게 쓰는지에 주목하라.

extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;

/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
  *dst++ = *src++;
}

/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
  *dst = 0;

Output section region

'>region'을 사용해서 한 section을 이전에 정의된 memory region으로 assign할 수 있다. MEMORY command 절을 봐라.

여기 간단한 예제다:

MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }

Output section phdr

':phdr'을 사용해서 한 section을 이전에 define된 program segment로 assign할 수 있다. PHDRS command 절을 봐라. 한 섹션이 하나 또는 그 이상의 segments로 assign되면, 명시적으로 :phdr modifier가 사용되기 전까지 모든 subsequent allocated sections들이 그들 segments로 assign될 것이다. :NONE으로 linker에게 모든 segment에 그 section을 두지 않을 것임을 알려줄 수 있다.

여기 간단한 예제다:

PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }

Output section fill

'=fillexp'를 사용해서 전체 section을 위한 fill pattern을 set할 수 있다. fillexp는 expression이다 (Linker Scripts에서 Expression절을 봐라). 그 output section 내의 memory의 모든 unspecified regions (예를 들어, input sections의 required alignment로 인해 남는 gaps) 은 그 값의 2 least significant bytes로 필요하면 반복적으로 채워질거다.

output section commands내에 FILL command로 fill value를 바꿀 수 있다; Output section data 절을 봐라.

여기 간단한 예제다:

SECTIONS { .text : { *(.text) } =0x9090 }

Overlay description

overlay description은 single memory image의 일부로 load되지만 같은 memory address에서 실행되는 section들을 describe하는 쉬운 방법을 제공한다. run time에 어떤 overlay manager가 overlaid sections를 요구사항에 맞게 아마 간단한 addressing bit를 조작해서 runtime memory address의 안과 밖에 copy할 거다. 이 방식은 예를 들면 특정 memory region이 다른 곳보다 빠를 때 유용할 수 있다.

Overlays는 OVERLAY command를 사용해서 describe한다. OVERLAY comand는 output section description처럼 SECTIONS command 안에서 사용된다. OVERLAY command의 full syntax는 다음과 같다:

OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]
  {
    secname1
      {
        output-section-command
        output-section-command
        ...
      } [:phdr...] [=fill]
    secname2
      {
        output-section-command
        output-section-command
        ...
      } [:phdr...] [=fill]
    ...
  } [>region] [:phdr...] [=fill]

OVERLAY (키워드) 를 빼고 모두 옵션이고, 각 section은 name (위에서 secname1secname2)을 가져야 한다. OVERLAY construct 내의 section definitions은 OVERLAY 내에 section들을 위해 define되는 address와 memory regions들이 없다는 것을 제외하면 일반적인 SECTIONS contruct (SECTIONS command를 봐라)내의 것들과 동일하다.

그 sections들은 모두 같은 starting address로 defined된다. 그 sections들의 load addresses는 전체에 OVERLAY를 위해 사용되는 load address에서 시작하는 memory 내에 consecutive하게 조정된다 (보통 section definitions처럼 load address는 옵션이고 default는 start address이다; start address 또한 옵션이고, default는 현재 location counter 값이다).

NOCROSSREFS keyword가 사용되면 sections 사이에 어떤 reference가 있다면, linker는 error로 report할 거다. 같은 address에서 section 모두가 실행되면, 보통 한 section이 다른 section에 직접 refer하는 것은 부적절하다. 다른 linker script command 절을 봐라.

OVERLAY 내의 각 모든 section에 대해 linker는 자동으로 2개의 symbol을 define한다. symbol __load_start_secname이 그 section의 starting load address로 defined된다. symbol __load_stop_secname이 그 section의 final load address로 defined된다. C identifiers내에 있지 않은 secname 내의 모든 문자는 제거된다. C (또는 assembler) code는 이들 symbol을 필요에 따라 overlaid section 근처로 옮기는데 사용할 수 있다.

overlay의 끝에서 location counter의 값은 overlay의 address + 가장 큰 section의 size가 된다.

여기 예제다. SECTION construct 안에 이게 있어야 함을 기억해라.

  OVERLAY 0x1000 : AT (0x4000)
   {
     .text0 { o1/*.o(.text) }
     .text1 { o2/*.o(.text) }
   }

이는 address 0x1000에서 시작하는 '.text0''.text1' 모두를 define한다. '.text0'는 address 0x4000에 load되고, '.text1''.text0' 바로 뒤에 load될 것이다. 다음 symbol들이 defined된다: __load_start_text0__load_stop_text0__load_start_text1__load_stop_text1.

overlay .text1을 overlay area로 복사하는 코드는 다음과 같을 것이다.

  extern char __load_start_text1, __load_stop_text1;
  memcpy ((char *) 0x1000, &__load_start_text1,
          &__load_stop_text1 - &__load_start_text1);

OVERLAY command는 단지 유용한 문법임을 모든 것은 좀 더 많은 basic command를 사용해서 할 수 있음을 알아둬라. 위의 예제는 아래처럼 똑같이 쓰여질 수 있다.

  .text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
  __load_start_text0 = LOADADDR (.text0);
  __load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0);
  .text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
  __load_start_text1 = LOADADDR (.text1);
  __load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1);
  . = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));

MEMORY command

 

 

 

[Linux:Kernel] AArch64 리눅스의 메모리 배치

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).

Documentation/arm64/memory.txt

     AArch64 리눅스의 메모리 배치
     ============================

Author: Catalin Marinas <catalin.marinas@arm.com>
번역  : 양정석 <dasomoli@gmailREMOVETHIS.com>
Date  : 20 February 2012

이 문서는 AArch64 리눅스 커널이 사용하는 가상 메모리 배치를 설명합니다.
이 아키텍처는 4KB 페이지 크기의 4단계 변환 테이블과 64KB 페이지 크기의
3단계 변환 테이블을 허용합니다.

AArch64 리눅스는 유저와 커널 양 쪽 모두 39비트 (512GB) 가상 주소를 허용하는
4KB 페이지 설정의 3단계 변환 테이블을 사용합니다. 64KB 페이지는 오직
2단계 변환 테이블이 사용되지만 메모리 배치는 같습니다.

유저 주소는 63:39 비트가 0으로 셋팅되는 반면, 커널 주소는 같은 곳의 비트에
1로 셋팅됩니다. TTBRx 선택은 가상 주소의 비트 63에 의해 결정됩니다.
swapper_pg_dir은 오직 커널 (전역) 맵핑만 포합하는 반면,
유저 pgd는 오직 유저 (비전역) 맵핑만 포함합니다. swapper_pgd_dir 주소는
TTBR1으로 쓰여지고, TTBR0로 절대 쓰여지지 않습니다.


4KB 페이지의 AArch64 리눅스 메모리 배치:

시작 크기 용도
———————————————————————–
0000000000000000 0000007fffffffff 512GB 유저

ffffff8000000000 ffffffbbfffeffff ~240GB vmalloc

ffffffbbffff0000 ffffffbbffffffff  64KB [guard page]

ffffffbc00000000 ffffffbdffffffff   8GB vmemmap

ffffffbe00000000 ffffffbffbbfffff  ~8GB [guard, 추후 vmmemap]

ffffffbffa000000 ffffffbffaffffff  16MB PCI I/O 공간

ffffffbffb000000 ffffffbffbbfffff  12MB [guard]

ffffffbffbc00000 ffffffbffbdfffff   2MB 고정 맵핑

ffffffbffbe00000 ffffffbffbffffff   2MB [guard]

ffffffbffc000000 ffffffbfffffffff  64MB 모듈들

ffffffc000000000 ffffffffffffffff 256GB 커널 논리 메모리 맵


64KB 페이지의 AArch64 리눅스 메모리 배치:

시작 크기 용도
———————————————————————–
0000000000000000 000003ffffffffff   4TB 유저

fffffc0000000000 fffffdfbfffeffff  ~2TB vmalloc

fffffdfbffff0000 fffffdfbffffffff  64KB [guard page]

fffffdfc00000000 fffffdfdffffffff   8GB vmemmap

fffffdfe00000000 fffffdfffbbfffff  ~8GB [guard, 추후 vmmemap]

fffffdfffa000000 fffffdfffaffffff  16MB PCI I/O 공간

fffffdfffb000000 fffffdfffbbfffff  12MB [guard]

fffffdfffbc00000 fffffdfffbdfffff   2MB 고정 맵핑

fffffdfffbe00000 fffffdfffbffffff   2MB [guard]

fffffdfffc000000 fffffdffffffffff  64MB 모듈들

fffffe0000000000 ffffffffffffffff   2TB 커널 논리 메모리 맵


4KB 페이지의 변환 테이블 탐색:

+——–+——–+——–+——–+——–+——–+——–+——–+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+——–+——–+——–+——–+——–+——–+——–+——–+
 |                 |         |         |         |         |
 |                 |         |         |         |         v
 |                 |         |         |         |   [11:0]  페이지 내의 오프셋
 |                 |         |         |         +-> [20:12] L3 인덱스
 |                 |         |         +———–> [29:21] L2 인덱스
 |                 |         +———————> [38:30] L1 인덱스
 |                 +——————————-> [47:39] L0 인덱스 (미사용)
 +————————————————-> [63] TTBR0/1


64KB 페이지의 변환 테이블 탐색:

+——–+——–+——–+——–+——–+——–+——–+——–+
|63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
+——–+——–+——–+——–+——–+——–+——–+——–+
 |                 |    |               |              |
 |                 |    |               |              v
 |                 |    |               |            [15:0]  페이지 내의 오프셋
 |                 |    |               +———-> [28:16] L3 인덱스
 |                 |    +————————–> [41:29] L2 인덱스 (38:29 만 사용)
 |                 +——————————-> [47:42] L1 인덱스 (미사용)
 +————————————————-> [63] TTBR0/1

KVM을 사용할 때, 하이퍼바이저는 커널 페이지를 EL2에서 커널 VA로부터 고정된
오프셋(커널 VA의 상위 24비트를 0으로 셋팅한)에 맵핑합니다:

시작 크기 용도
———————————————————————–
0000004000000000 0000007fffffffff 256GB HYP 내에서 맵핑된 커널 객체

[Android] “Running Android with low RAM” 의 Kernel Configuration

Except as noted, this content is licensed under Creative Commons Attribution 2.5. For details and restrictions, see the Content License.
 

원문: http://source.android.com/devices/low-ram.html
번역: 양정석<dasomoli@gmailREMOVETHIS.com>(근데 원래 혼자 보려던 거라 이상한 거 많음;;)

커널 설정


직접 회수(Direct reclaim)를 줄이기 위한 커널/ActivityManager 튜닝

직접 회수는 하나의 프로세스 또는 커널이 메모리의 한 페이지를 (직접 혹은 새로운 페이지 안의 폴트로 인해서) 할당하고자 하며, 커널이 모든 사용 가능한 자유 메모리를 사용하고 있을 때 일어납니다. 이것은 커널에게 하나의 페이지를 해제하는 동안 할당을 막도록 요구합니다. 결국 이것은 더티한 File-backed 페이지를 플러싱하기 위한 디스크 I/O나 lowmemorykiller가 프로세스 하나를 죽이기를 기다리는 것을 종종 요구합니다. 이것은 UI 스레드를 포함하는 어떤 스레드에 추가 I/O를 일으킬 수 있습니다.
직접 회수를 피하기 위해서 커널은 kswapd나 백그라운드 회수를 촉발시키는 워터마크를 가집니다. 이것은 페이지들을 해제하도록 시도해서 다음에 그를 할당하는 리얼 타임 스레드가 빨리 수행할 수 있도록 하는 하나의 스레드입니다.
백그라운드 회수를 촉발시키는 디폴트 임계값은 2GB 디바이스에서 2MB, 512MB 디바이스에서 636KB로, 상당히 낮습니다. 그리고 커널은 그저 수 MB의 메모리를 백그라운드 회수 시에 회수할 뿐입니다. 이것은 수 MB 이상을 빨리 할당하는 어떤 프로세스는 빨리 직접 회수를 치게 될 것이라는 것을 의미합니다.
새로운 커널에 튜닝 가능한 지원이 android-3.4 커널 브랜치에 패치 92189d47f66c67e5fd92eafaa287e153197a454f(“add extra free kbytes tunable”)로 추가됩니다. 이 패치를 디바이스의 커널로 체리픽하는 것은 ActivityManager가 커널에게 3개의 풀스크린 32 bpp 버퍼의 메모리를 자유롭게 유지하도록 할 것임을 이야기하는 것을 허용할 것입니다.
이들 임계값은 프레임워크 config.xml을 통해 설정될 수 있습니다.
<!– 커널 내에 (존재한다면) 튜닝 가능한 /proc/sys/vm/extra_free_kbytes를 셋팅하는 디바이스 설정. 높은 값은 커널이 free를 유지하는 메모리의 양을 증가시키고, 할당 시간을 줄이고, lowmemerykiller가 더 빨리 죽이도록 할 것입니다. 낮은 값은 프로세스들에 의해 더 많은 메모리를 사용하도록 하지만 디스크 I/O나 lowmemorykiller 상에서의 대기를 막는 더 많은 할당이 일어날 것입니다. 화면 크기에 기초한 ActivityManager에 의해 선택된 기본 값은 덮어씌움. 0은 커널의 기본 값을 넘는 추가 메모리를 유지하는 것을 막습니다. -1은 기본값을 유지합니다 –>
<integer name=”config_extraFreeKbytesAbsolute”>-1</integer>
<!– 커널 내에 (존재한다면) 튜닝 가능한 /proc/sys/vm/extra_free_kbytes를 조정하는 디바이스 설정. 0은 ActivityManager가 고르는 기본 값을 사용합니다. 양수 값은 커널이 해제를 유지하는 메모리의 양을 증가시키고, 할당 시간을 줄이고, lowmemorykiller 가 더 빨리 죽이도록 할 것입니다. 음수 값은 프로세스들에 의해 사용되는 메모리를 더 많이 허용하지만, 디스크 I/O나 lowmemorykiller 상의 대기를 막는 더 많은 할당이 일어날 것입니다. 화면 크기에 기초한 ActivityManager에 의해 선택된 기본 값은 직접 더해짐. –>
<integer name=”config_extraFreeKbytesAdjust”>0</integer>
LowMemoryKiller 튜닝
ActivityManager는 각 우선 순위 단계 버킷 안에서 프로세스를 실행하는데 필요한 file-backed 페이지(캐시된(cached) 페이지)의 워킹 셋의 그 예상치를 맞추기 위해서 LowMemoryKiller의 임계값을 설정합니다. 디바이스가 워킹 셋을 위한 높은 요구 사항을 갖는다면, 예를 들면, 벤더 UI가 더 많은 메모리를 요구하거나 더 많은 서비스가 추가되었다면, 임계값은 증가될 수 있습니다.
작아지고 있는 캐시 때문에 디스크 스레싱(thrashing)이 일어나기 전에 백그라운드 프로세스들이 죽여지고 있어서, 너무 많은 메모리가 file backed 페이지를 위해 예약되어 지고 있다면 임계값을 줄일 수 있습니다. 
<!– 커널 내의 lowmemorykiller 내의 튜닝 가능한 minfree를 셋팅하는 디바이스 설정. 높은 값은 lowmemorykiller가 더 일찍 켜지고, 파일 캐시 안에 더 많은 메모리를 유지하고, I/O 스레싱을 막도록 할 것이지만, 메모리 내에 더 적은 프로세스가 머물도록 할 것입니다. 낮은 값은 더 많은 프로세스가 메모리 상에 유지되지만, 너무 낮으면 스레싱을 일으킬 것입니다. 화면 크기와 가장 큰 lowmemorykiller 버킷을 위한 전체 메모리에 기초해서 ActivityManager에 의해 선택된 기본값을 덮어쓰고, 더 작은 버킷에 비례해서 크기가 조정됨. -1은 기본값을 유지 —>
<integer name=”config_lowMemoryKillerMinFreeKbytesAbsolute”>-1</integer>
<!– 커널 내의 lowmemorykiller 내의 튜닝 가능한 minfree를 조정하는 디바이스 설정. 높은 값은 lowmemorykiller가 더 일찍 켜지고, 파일 캐시 안에 더 많은 메모리를 유지하고, I/O 스레싱을 막도록 할 것이지만, 메모리 내에 더 적은 프로세스가 머물도록 할 것입니다. 낮은 값은 더 많은 프로세스가 메모리 상에 유지되지만, 너무 낮으면 스레싱을 일으킬 것입니다. 화면 크기와 가장 큰 lowmemorykiller 버킷을 위한 전체 메모리에 기초해서 ActivityManager에 의해 선택된 기본값에 직접 더해지고, 더 작은 버킷에 비례해서 크기가 조정됨. 0은 기본값을 유지 —>
<integer name=”config_lowMemoryKillerMinFreeKbytesAdjust”>0</integer>

Framework의 설정 참고 소스 코드: frameworks/base/services/java/com/android/server/am/ProcessList.java

[Linux:Kernel] 커널 메모리 누수 검출기

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).

Documentation/kmemleak.txt

번역: 양정석(dasomoli@gmailREMOVETHIS.com)

커널 메모리 누수 검출기
=======================

소개
—-

Kmemleak은 주인없는 객체들을 해제하지는 않고 그저 /sys/kernel/debug/kmemleak을
통해 보고하는 차이가 있는 가비지 컬렉터 추적
(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors)
과 비슷한 방법으로, 가능한 커널 메모리 누수를 검출하는 방법을 제공합니다. 유저-공간의
애플리케이션에서 메모리 누수를 검출하는 데 Valgrind 도구(memcheck –leak-check)가
유사한 방법을 사용합니다.

지원되는 아키텍처를 위해 lib/Kconfig.debug 안의 DEBUG_KMEMLEAK 의존성을 체크하세요.

사용법
——

“Kernel hacking” 안의 CONFIG_DEBUG_KMEMLEAK이 켜져야 합니다. 한 커널 스레드가
메모리를 (기본값으로) 매 10분마다 스캔하고, 찾은 새로운 참조되지 않는 객체들의
수를 출력합니다. 모든 가능한 메모리 누수의 자세할 사항을 보기 위해서는:

  # mount -t debugfs nodev /sys/kernel/debug/
  # cat /sys/kernel/debug/kmemleak

중간에 메모리 스캔을 작동시키려면:

  # echo scan > /sys/kernel/debug/kmemleak

모든 현재의 가능한 메모리 누수의 목록을 지우려면:

  # echo clear > /sys/kernel/debug/kmemleak

그려면 새로운 누수는 /sys/kernel/debug/kmemleak을 다시 읽음으로써 나올 것입니다.

주인없는 객체들은 그들이 할당되었던 순서로 나열되고, 목록의 맨 앞의 한 객체는
주인없는 것으로 보고되는 다른 이어지는 객체들의 원인이 될 것입니다.

메모리 스캐닝 파라미터들은 런타임에 /sys/kernel/debug/kmemleak 파일에 씀으로써
바뀔 수 있습니다. 다음 파라미터들이 지원됩니다:

  off           – kmemleak 끄기 (되돌릴 수 없음)
  stack=on      – 태스크 스택 스캐닝 켜기 (기본값)
  stack=off     – 태스크 스택 스캐닝 끄기
  scan=on       – 자동 메모리 스캐닝 스레드 시작하기 (기본값)
  scan=off      – 자동 메모리 스캐닝 스레드 멈추기
  scan=<secs>   – 자동 메모리 스캐닝 주기를 초로 설정
                  (기본 600, 0은 자동 스캐닝 멈춤)
  scan          – 한 번의 메모리 스캔 작동
  clear         – 현재 메모리 누수 혐의자들의 목록 지우기, 모든 현재 보고된
                  참조되지 않는 객체를 회색으로 표시함으로써 끝
  dump=<addr>   – <주소>에서 찾아진 객체에 대한 정보를 덤프

Kmemleak은 또한 부팅 시간에 “kmemleak=off”를 커널 커맨드 라인에 전달함으로써
꺼질 수도 있습니다.

kmemleak이 초기화되기 전에 메모리는 할당되거나 해제될 것이고, 이들 동작은 초기
로그 버퍼 안에 저장될 것입니다.이 버퍼의 크기는
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE 옵션을 통해 설정됩니다.

기본 알고리즘
————-

kmalloc, vmalloc, kmem_cache_alloc과 그 친구들을 통한 메모리 할당은 추적되고,
그 포인터들은, 크기나 스택 추적같은 추가 정보와 함께 prio 검색 트리 안에
저장됩니다. 그에 맞는 해제 함수 호출들은 기록되고 그 포인터들은 kmemleak 자료
구조로부터 제거됩니다.

메모리의 할당된 블록이 (저장된 레지스터들을 포함하는) 메모리 스캐닝을 통해
그 시작 주소나 블록 안의 어떤 위치로의 어떤 포인터도 찾을 수 없다면, 주인없는
것으로 여겨집니다. 이것은 커널이 할당된 블록의 주소를 해제하는 함수로 넘길 수
있는 방법이 없을 것이고, 그래서 그 블록이 메모리 누수라고 여겨진다는 것을
의미합니다.


스캐닝 알고리즘 단계:

  1. 모든 객체를 하얗게 표시 (남은 하얀 객체들은 나중에 주인없는 것으로 여겨질
     것 입니다.)
  2. 데이터 섹션과 스택들의 메모리 시작을 스캔, prio 검색 트리 안에 저장된 주소들에
     대조되는 값을 검사. 하얀 객체로의 포인터가 있다면, 그 객체는 회색 목록에
     추가합니다.
  3. 회색 집합이 끝날 때까지 매칭되는 주소들을 위해 회색 객체들을 스캔(어떤
     하얀 객체들은 회색이 되고, 회색 목록의 끝에 추가됩니다)
  4. 남아있는 하얀 객체들은 주인없는 것으로 여겨지고, /sys/kernel/debug/kmemleak
     을 통해 보고됩니다.

어떤 할당된 메모리 블록들은 커널의 내부 자료 구조 안에 저장된 포인터들을 가지고,
그들은 주인없는 것으로 검출될 수 없습니다. 이를 피하기 위해서, kmemleak은
또한, 찾아질 필요가 있는 블록 주소 범위 내의 주소를 가리키는 값의 수를 저장할
수 있어서 그 블록은 누수로 여겨지지 않습니다. 한 예로 __vmalloc()이 있습니다.

kmemleak으로 특정 섹션 테스트하기
———————————

초기 부팅 상에서 여러분의 /sys/kernel/debug/kmemleak 출력 페이지는 좀
많을 것입니다. 이것은 또한 여러분이 개발을 할 때 매우 버그가 많은 코드를
가진다면 있을 수 있습니다. 이런 상황에서 일하기 위해서
/sys/kernel/debug/kmemleak 출력으로부터 모든 보고된 참조되지 않는 객체를
지우기 위해서 여러분은 ‘clear’ 명령을 사용할 수 있습니다. ‘clear’ 후의
‘scan’을 통해, 여러분은 새로운 참조되지 않는 객체들을 찾을 수 있습니다;
이것은 코드의 특정 섹션을 테스팅하는 데 도움이 될 것입니다.

깨끗한 kmemleak으로 하고 싶을 때 크리티컬 섹션을 테스트하기 위해서는
이렇게 하세요:

  # echo clear > /sys/kernel/debug/kmemleak
  … 여러분의 커널이나 모듈을 테스트 …
  # echo scan > /sys/kernel/debug/kmemleak

그 후, 여느 때처럼 여러분의 보고서를 얻기 위해서:

  # cat /sys/kernel/debug/kmemleak

Kmemleak API
————

함수들의 프로토 타입은 /include/linux/kmemleak.h 헤더를 보세요.

kmemleak_init            – kmemleak 초기화
kmemleak_alloc           – 하나의 메모리 블록 할당 알림
kmemleak_alloc_percpu    – 하나의 percpu 메모리 블록 할당 알림
kmemleak_free            – 하나의 메모리 블록 해제 알림
kmemleak_free_part       – 하나의 부분 메모리 블록 해제 알림
kmemleak_free_percpu     – 하나의 percpu 메모리 블록 해제 알림
kmemleak_not_leak        – 누수가 아닌 것으로 객체 표시
kmemleak_ignore          – 스캔하지 않거나, 누수로 객체를 보고하지 않음
kmemleak_scan_area       – 메모리 블록내의 스캔 지역 추가
kmemleak_no_scan         – 메모리 블록 스캔하지 않음
kmemleak_erase           – 포인터 변수 안의 이전 값을 지움
kmemleak_alloc_recursive – kmemleak_alloc과 같지만 재귀성 검사
kmemleak_free_recursive  – kmemleak_free와 같지만 재귀성 검사

거짓 검출/미검출의 처리
———————–

거짓 미검출은 진짜 메모리 누수(주인없는 개체)가 메모리 스캐닝 중에 찾은 값들이
객체들을 가리키고 있어서 kmemleak에 의해 보고되지 않는 것입니다.
거짓 미검출의 수를 줄이기 위해서, kmemleak은 kmemleak_ignore,
kmemleak_scan_area, kmemleak_no_scan, 그리고 kmemleak_erase 함수들
(위를 보세요)을 제공합니다. 태스크 스택은 또한 거짓 미검출의 양을 늘려서,
그들의 스캐닝은 기본값으로 켜지지 않습니다.

거짓 검출은 객체들이 메모리 누수(주인없음)이 된 것처럼 잘못 보고되는 것입니다.
누수가 아닌 것으로 알려진 객체들을 위해서, kmemleak은 kmemleak_not_leak
함수를 제공합니다. kmemleak_ignore 또한 그 메모리 블록이 다른 포인터들을
포함하는 것으로 알려지지 않았다면 사용될 수 있고, 더 이상 스캔되지 않을
것 입니다.

보고된 누수들 중 어떤 것들은 CPU 레지스터나 스택 안에 임시적으로 저장되는
포인터들 때문에, 특히 SMP 시스템 상에서, 그저 일시적입니다. Kmemleak은
메모리 누수로 보고되는 객체의 최소 나이를 표시하는 (기본은 1000인)
MSECS_MIN_AGE를 정의합니다.

제한과 결점
———–

주요 결점은 메모리 할당과 해제의 성능을 떨어뜨린 다는 것 입니다. 다른 불이익을
피하기 위해서, 메모리 스캐닝은 /sys/kernel/debug/kmemleak 파일을 읽을 때만
수행됩니다. 어쨌든, 이 도구는 성능이 가장 중요한 요구 사항이 아닌 곳에서의
디버깅을 목적으로 합니다.

알고리즘을 간단히 유지하기 위해서, kmemleak은 한 블록의 주소 범위 안의
어떤 주소를 가리키는 값들을 스캔합니다. 이것은 거짓 미검출의 수를 늘릴 것입니다.
그러나, 그것은 진짜 메모리 누수가 결국 보여지게 되도록 할 것입니다.

거짓 미검출의 다른 출처는 포인터가 아닌 값들안에 저장된 데이터입니다. 이후
버전에서는 kmemleak이 할당된 구조체 안의 포인터 멤버만 스캔할 수 있을 것입니다.
이 기능은 위에서 설명한 많은 거짓 미검출을 해결할 것 입니다.

이 도구는 거짓 검출을 보고할 수 있습니다. 할당된 블록이 해제를 필요로 하지 않는
곳(init_call 함수 안의 어떠한 경우), 그 포인터가 보통의 container_of
매크로가 아닌 다른 방법으로 계산되는 곳, 또는 그 포인터가 kmemleak이 스캔하지
않는 위치 안에 저장된 포인터인 곳 같은 경우들이 있습니다.

페이지 할당과 ioremap은 추적하지 않습니다.

[Linux:Kernel] 오픈 펌웨어(Open Firmware) 없이 리눅스/ppc 커널 부팅하기

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL License).

Documentation/devicetree/booting-without-of.txt

        오픈 펌웨어(Open Firmware) 없이 리눅스/ppc 커널 부팅하기
        ——————————————————–

(c) 2005 Benjamin Herrenschmidt <benh at kernel.crashing.org>,
    IBM Corp.
(c) 2005 Becky Bruce <becky.bruce at freescale.com>,
    Freescale Semiconductor, FSL SOC and 32-bit additions
(c) 2006 MontaVista Software, Inc.
    Flash chip node definition

번역: 양정석<dasomoli@REMOVETHISgmail.com>

차례
====

  I – 도입
    1) arch/arm의 진입 포인트(Entry point)
    2) arch/powerpc의 진입 포인트
    3) arch/x86의 진입 포인트

  II – DT 블록 형식
    1) 헤더
    2) 디바이스 트리 일반론
    3) 디바이스 트리 “구조” 블록
    4) 디바이스 트리 “문자열들” 블록

  III – 디바이스 트리에서 필요한 내용
    1) 셀(cells)과 주소 표현에 관한 기록
    2) “compatible” 프로퍼티에 대한 기록
    3) “name” 프로퍼티에 대한 기록
    4) 노드와 프로퍼티 이름과 문자셋에 대한 기록
    5) 필요 노드와 프로퍼티들
      a) 루트 노드
      b) /cpus 노드
      c) /cpus/* 노드들
      d) /memory 노드(들)
      e) /chosen 노드
      f) /soc<SOC이름> 노드

  IV – “dtc”, 디바이스 트리 컴파일러(device tree compiler)

  V – 부트로더에 대한 권장 사항

  VI – 시스템-온-칩 디바이스와 노드들
    1) SOC의 자식 노드들 정의하기
    2) 현재의 OF 명세없이 디바이스들 나타내기

  VII – 디바이스들의 인터럽트 정보 지정하기
    1) interrupt 프로퍼티
    2) interrupt-parent 프로퍼티
    3) OpenPIC 인터럽트 컨트롤러
    4) ISA 인터럽트 컨트롤러

  VIII – 디바이스 전원 관리 정보 지정하기 (sleep 프로퍼티)

  부록 A – MPC8540의 예제 SOC 노드


개정 정보
=========

   May 18, 2005: Rev 0.1 – Initial draft, no chapter III yet.

   May 19, 2005: Rev 0.2 – Add chapter III and bits & pieces here or
                           clarifies the fact that a lot of things are
                           optional, the kernel only requires a very
                           small device tree, though it is encouraged
                           to provide an as complete one as possible.

   May 24, 2005: Rev 0.3 – Precise that DT block has to be in RAM
                         – Misc fixes
                         – Define version 3 and new format version 16
                           for the DT block (version 16 needs kernel
                           patches, will be fwd separately).
                           String block now has a size, and full path
                           is replaced by unit name for more
                           compactness.
                           linux,phandle is made optional, only nodes
                           that are referenced by other nodes need it.
                           “name” property is now automatically
                           deduced from the unit name

   June 1, 2005: Rev 0.4 – Correct confusion between OF_DT_END and
                           OF_DT_END_NODE in structure definition.
                         – Change version 16 format to always align
                           property data to 4 bytes. Since tokens are
                           already aligned, that means no specific
                           required alignment between property size
                           and property data. The old style variable
                           alignment would make it impossible to do
                           “simple” insertion of properties using
                           memmove (thanks Milton for
                           noticing). Updated kernel patch as well
                         – Correct a few more alignment constraints
                         – Add a chapter about the device-tree
                           compiler and the textural representation of
                           the tree that can be “compiled” by dtc.

   November 21, 2005: Rev 0.5
                         – Additions/generalizations for 32-bit
                         – Changed to reflect the new arch/powerpc
                           structure
                         – Added chapter VI


 ToDo:
        – Add some definitions of interrupt tree (simple/complex)
        – Add some definitions for PCI host bridges
        – Add some common address format examples
        – Add definitions for standard properties and “compatible”
          names for cells that are not already defined by the existing
          OF spec.
        – Compare FSL SOC use of PCI to standard and make sure no new
          node definition required.
        – Add more information about node definitions for SOC devices
          that currently have no standard, like the FSL CPM.


I – 도입
========

리눅스/ppc64 커널, 그리고 더 특정지어서, 오래된 IBM p시리즈/i시리즈
쌍 밖의 새로운 플랫폼 타입의 추가 개발 동안, 커널 진입과 부트 로더
<-> 커널 인터페이스에 관련된 엄격한 몇가지 규칙이 시행되도록 결정되었고,
ppc32 커널 진입 포인트와 그 방법이 되었던 것 같은 퇴보를 피하기 위해서,
새 플랫폼이 커널에 추가되어야 했습니다. 기존의 i시리즈 플랫폼은 이
계획(scheme)에 앞서 있어서 이들 규칙을 깹니다만, 그들을 적절히 따르지
않는 새로운 보드 지원은 하나도 메인 트리에 받아들여지지 않을 것입니다.
게다가 ppc32와 ppc64의 arch/powerpc로의 플랫폼 통합의 완료 이후에는,
새로운 32비트 플랫폼과 arch/powerpc 내로 옮기는 32비트 플랫폼은 이 룰을
잘 따르도록 요구될 것입니다.

아래에서 더 자세히 정의될 주된 요구사항은 오픈 펌웨어 명세(Open Firmware
specification) 이후에 정의된 형식의 디바이스 트리가 있느냐 하는 것입니다.
그러나 임베디드 보드 업체들의 삶이 더 쉽게 만들기 위해서, 커널은
시스템 내의 모든 디바이스를 표현한 디바이스 트리를 요구하지는 않고,
실제로 있는 노드와 항목들만 요구합니다. 이는 섹션 III에서 더 자세히
설명될 겁니다만, 예를 들면, 커널은 여러분에게 시스템 내의 모든 PCI
디바이스를 위한 노드를 만들 것을 요구하지 않습니다. 인터럽트 경로 정보와
다른 것 가운데에서 메모리/IO 범위를 제공하기 위해 PCI 호스트 브리지를
위한 노드 하나를 가지는 것이 요구 사항입니다. 또한 온칩 디바이스와
특별히 맞춰지지 않는 다른 버스들에 대한 노드들을 존재하는 OF 명세 안에
정의하는 것을 권장합니다. 이것은 커널이 모든 종류의 하드 코딩된 테이블
없이, 그들을 감지(probe)해서 드라이버를 디바이스와 맞출 수 있는 방법으로
대단한 유연성을 제공합니다. 또한 보드 업체들에게 커널 코드에 큰 영향없이,
또는 특정 경우로 집어넣는 일 없이 마이너 하드웨어 업그레이드를 하도록
더 유연하게 만듭니다.

1) arch/arm의 진입 포인트(Entry point)
————————————–

   커널 이미지의 시작점에 커널로의 하나의 진입 포인트가 있습니다. 그
   진입 포인트는 두가지 호출 규약을 지원합니다. 그 인터페이스의 요약이
   여기 기술되어 있습니다. 부팅 요구사항의 전체 설명은
   Documentation/arm/Booting 안에 문서화되어 있습니다.

        a) ATAGS 인터페이스. 펌웨어로부터의 최소한의 정보가 커널로
        미리 정의된(predefined) 파라메터의 태그를 붙인 리스트(tagged list)
        로 넘겨집니다.

                r0 : 0

                r1 : 머신 타입 넘버(Machine type number)

                r2 : 시스템 RAM 안의 태그를 붙인 리스트의 물리 주소

        b) 평면(flattened) 디바이스 트리 블록으로 진입. 펌웨어는 r2 안에서
        평면 디바이스 트리 블록(dtb)의 물리 주소를 로딩하고, r1 은 사용되지
        않습니다만, Documentation/arm/Booting 내에 설명된 것처럼 유효한 머신
        넘버를 사용하는 좋은 관례도 고려됩니다.

                r0 : 0

                r1 : 유효한 머신 타입 넘버. 디바이스 트리를 사용할 때,
                하나의 머신 타입 넘버가 SoC의 계열 또는 클래스를 나타내는데
                할당되곤 할 것입니다.

                r2 : (챕터 II에 정의된) RAM 안의 디바이스 트리 블록으로의
                물리적 포인터(physical pointer). 디바이스트리는 시스템 RAM 안
                어느 곳에나 위치할 수 있습니다만, 64비트 경계(boundary) 안에
                정렬되어야 합니다.

   커널은 ATGAS와 디바이스트리 부팅을 r2에 의해 가리켜지는 메모리를 읽고,
   평면 디바이스 트리 블록의 매직 값(0xd00dfeed) 또는 r2 에서 0x4 오프셋
   위치의 ATAG_CORE 값(0x54410001)을 살펴봄으로써 구별할 것입니다.

2) arch/powerpc의 진입 포인트
—————————–

   커널 이미지의 시작에 커널로의 하나의 진입 포인트가 있습니다. 그 진입
   포인트는 두가지 호출 규약을 지원합니다:

        a) 오픈 펌웨어로 부팅. 여러분의 펌웨어가 오픈 펌웨어(IEEE 1275)와
        호환되거나, OF 호환 클라이언트 인터페이스 API(나온 말들이 필요없는
        “interpret” 콜백이 지원되는)를 제공하면, 다음으로 커널로 진압할 수
        있습니다:

              r5 : IEEE 1275 powerpc로의 바인딩에 의해 정의되는 OF 콜백
              포인터. 32비트 클라이언트 인터페이스만 현재 지원됨.

              r3, r4 : 있거나, 또는 0이면, initrd의 주소와 길이

              MMU는 켜져 있거나 꺼져 있을 수 있습니다; 커널은
              arch/powerpc/kernel/prom_init.c 안에 위치한 분기 코드를
              오픈 펌웨어로부터 디바이스-트리와 다른 정보를 추출하고,
              평명화 디바이스-트리를 b에 서술한대로 만들기 위해 실행할
              것입니다). prom_init()은 그 후, 두번째 방법으로 커널을
              재진입할 것입니다. 이 분기 코드는 그 시점에 모든 예외를
              처리하는 펌웨어 컨텍스트에서 실행됩니다.

        b) 평면화 디바이스-트리 블록으로 직접 진입. 이 진입 포인트는 a)에
        의해 OF 분기 코드 후에 호출되고, 오픈 펌웨어 클라이언트 인터페이스를
        지원하지 않는 부트로더에 의해 직접 호출될 수 있습니다. 또한,
        “kexec”에 의해 이전에 실행되던 것에서부터 새로운 커널의 “핫” 부팅을
        구현하는데 사용될 수 있습니다. 이 방법은 방법 a) 가 간단히
        표준 오픈 펌웨어로서, 그래서 그를 정의하고 PowerPC 플랫폼으로의
        그 바인딩을 정의하는 다양한 표준 문서들에 따라 구현되어야 하는,
        제가 이 문서 내에서 더 자세히 설명할 방법입니다. 그래서, 그 진입
        포인트 정의는 다음처럼 됩니다:

                r3 : 램(RAM) 내의 (챕터 II에 정의된)디바이스-트리로의
                물리적 포인터

                r4 : 커널 그 자체로의 물리적 포인터. 이것은 어셈블리 코드에
                의해 여러분이 MMU를 켜거 1:1 맵핑이 아닌 상태로 커널로
                진입하는 경우에 적절히 MMU 를 끄기 위해 사용됩니다.

                r5 : NULL (방법 a와 구별하기 위함)

        SMP 진입에 관한 알림: 여러분의 펌웨어는 여러분의 다른 CPU 들을
        여러분이 신경쓸 필요가 없는 경우에 소프트 리셋이나 어떤 다른
        수단을 통해 수행할 수 있는 롬(ROM) 내의 어떤 슬립 루프나 스핀 루프에
        두거나, 모든 CPU로 커널 진입을 해야만 할 것입니다. 방법 b)로
        하는 방법은 이 문서의 이후 개정판에서 설명될 것입니다.

   보드 지원들(플랫폼들)은 설정 옵션들이 전용이 아닙니다. 임의의
   보드 지원들은 하나의 커널 이미지로 빌드될 수 있습니다. 커널은
   디바이스-트리의 내용에 기초하여 주어진 플랫폼에서 사용될 함수 집합을
   “알” 것입니다. 그래서 여러분은 다음처럼 해야만 합니다:

        a) arch/powerpc/Kconfig 내에 _불린(boolean)_ 옵션으로,
        PPC_PSERIES, PPC_PMAC 과 PPC_MAPLE의 예제를 따라, 여러분의
        플랫폼 지원을 추가하세요. 나중 것이 아마 시작하기 좋은 보드 지원
        예제가 될 것입니다.

        b) 여러분의 주 플룟폼 파일을
        “arch/powerpc/platforms/myplatform/myboard_setup.c” 로 생성하고
        여러분의 CONFIG_ 옵션의 조건 아래서 Makefile 에 추가하세요.
        이 파일은 일반적인 코드가 여러분의 플랫폼에 따른 코드를 얻어 사용할
        다양한 콜백을 포함하는 “ppc_md” 타입의 구조체를 정의할 것입니다.

  플랫폼 기능이 같은 코어 아키텍처를 사용할 때이긴 하지만, 하나의 커널
  이미지가 여러 플랫폼을 지원할 것입니다. 하나의 커널 빌드는 책 E 와
  고전적인 Powerpc 아키텍처의 설정 둘 다를 지원할 수는 없습니다.

3) arch/x86의 진입 포인트
————————-

  code32_start에 커널로의 하나의 32비트 진입 포인트, 압축 해제기
  (리얼 모드 진입 포인트는 보호 모드 안으로 전환되면 같은 32비트 진입
  포인트로 갑니다)가 있습니다. 그 진입 포인트는 Documentation/x86/boot.txt
  안에 문서화된 하나의 호출 규약을 지원합니다.
  (챕터 II 안에 정의된) 디바이스 트리 블록으로의 물리적 포인터는 적어도
  부트 프로토콜 2.09를 요구하는 setup_data 를 통해 넘겨집니다. 그 타입
  (type) 필드는 다음처럼 정의됩니다.

  #define SETUP_DTB                      2

  이 디바이스 트리는 “부트 페이지(boot page)”로의 확장처럼 사용됩니다.
  그처럼 부트 페이지에 의해 이미 대신되는 데이터를 파싱 / 고려하지 않습니다.
  이것은 메모리 크기, 예약된 범위(reserved ranges), 커맨드 라인 인자,
  또는 initrd 주소를 포함합니다. 검색할 수 없는 정보, 아니면 인터럽트 경로나
  I2C 버스 뒤의 디바이스 리스트 같은 정보를 간단히 보유합니다.

II – DT 블록 형식
=================


이 챕터는 커널로 넘기는 평면 디바이스 트리의 실제 형식을 정의합니다.
그 실제 내용과 커널 요구사항은 이후에 설명됩니다. 여러분은 오픈 펌웨어
상세(representation)로부터 생성될 평면 디바이스 트리인
arch/powerpc/kernel/prom_init.c 또는 파일 시스템 상세로부터 생성될
kexec 툴의 일부인 fs2dt 유틸리티를 포함하여 다양한 곳의 형식을 조작하는
코드 예제를 찾을 수 있습니다. uboot 같은 부트로더는 이후에 좀 더 이야기할
조금 더 많은 지원이 기대됩니다.


주의: 그 블록은 주 메모리 안에 있어야 합니다. 주 메모리 외에 다른 매핑없이
리얼 모드와 가상 모드 모두 에서 접근 가능해야 합니다. 간단한 플래시
부트로더를 작성하는 중이라면, 그 블록을 커널로 넘기기 전에 RAM으로 복사해야
합니다.


1) 헤더
——-

   커널은 include/linux/of_fdt.h 안에 boot_param_header 구조체로 대강 설명된
   한 메모리 영역을 가리키는 물리 주소를 넘겨 받습니다:

struct boot_param_header {
        u32     magic;                  /* 매직 워드 OF_DT_HEADER */
        u32     totalsize;              /* DT block의 전체 크기 */
        u32     off_dt_struct;          /* 구조의 오프셋 */
        u32     off_dt_strings;         /* 문자열들의 오프셋 */
        u32     off_mem_rsvmap;         /* 메모리 예약맵의 오프셋 */
        u32     version;                /* 형식 버전 */
        u32     last_comp_version;      /* 마지막 호환되는 버전 */

        /* 아래는 버전 2 필드 */
        u32     boot_cpuid_phys;        /* 우리가 부팅하는 물리 CPU id */
        /* 아래는 버전 3 필드 */
        u32     size_dt_strings;        /* 문자열들 블록의 크기 */

        /* 아래는 버전 17 필드 */
        u32     size_dt_struct;         /* DT 구조 블록의 크기 */
};

   상수를 덧붙입니다:

/* 평면 디바이스 트리에 의해 사용되는 정의들 */
#define OF_DT_HEADER            0xd00dfeed      /* 4: 버전,
                                                   4: 전체 크기 */
#define OF_DT_BEGIN_NODE        0x1             /* 시작 노드: 전체 이름
                                                   */
#define OF_DT_END_NODE          0x2             /* 끝 노드 */
#define OF_DT_PROP              0x3             /* 프로퍼티: name off,
                                                   크기, 내용 */
#define OF_DT_END               0x9

   이 헤더 안의 모든 값은 빅 엔디안(big endian) 형식이고, 다양한 필드들이
   아래에 더 정밀하게 정듸됩니다. 모든 “오프셋(offset)” 값들은 헤더의
   시작으로부터의 바이트 단위 값입니다: 디바이스 트리 블록의 물리적
   베이스 주소입니다.

   – magic

     디바이스 트리 블록 헤더의 시작을 “표시하는” 매직 값입니다.
     0xd00feed 값을 포함하고, OF_DT_HEADER 상수로 정의됩니다.

   – totalsize

     이것은 헤더를 포함한 DT 블록의 전체 크기입니다. 그 “DT” 블록은
     (이 헤더 안의 오프셋들에 의해서 가리켜지는)이 챕터에서 정의하는
     모든 데이터 구조체를 갖고 있어야 합니다. 디바이스 트리 구조, 문자열들,
     메모리 예약 맵을 말합니다.

   – off_dt_struct

     이것은 헤더의 시작으로부터 디바이스 트리 “구조” 부분의 시작까지의
     오프셋입니다. (2) 디바이스 트리 를 보세요)

   – off_dt_strings

     이것은 헤더의 시작으로부터 디바이스 트리 “문자열들” 부분의
     시작까지의 오프셋입니다.

   – off_mem_rsvmap

     이것은 헤더의 시작으로부터 예약 메모리 맵의 시작까지의 오프셋입니다.
     이 맵은 64비트 정수 쌍의 리스트입니다. 각 쌍은 물리 주소와
     크기입니다. 그 리스트는 크기 0의 항목으로 끝납니다. 이 맵은
     커널에게 “예약된” 물리 메모리 영역의 리스트를 제공하고, 그래서
     특히 초반 초기화 시에, 메모리 할당에 사용되지 않습니다. 커널은
     부팅하는 동안 디바이스 트리의 평면 해제화, MMU 해시 테이블, 기타…
     등을 위한 메모리를 할당할 필요가 있습니다. 이 할당은 오픈 펌웨어
     머신 상에서의 RTAS 인스턴스나, 특정 p시리즈 상에서 iommu 에서
     사용되는 TCE 테이블 같은 치명적인 것들을 덮어씌우는 것을 피하도록
     앞의 방법으로 수행되어야만 합니다. 일반적으로, 예약 맵은 _적어도_
     이 DT 블록 자체(header, total_size)는 포함해야 합니다. 여러분이
     커널로 initrd를 넘기고 있다면, 잘 예약해야 합니다. 커널 이미지
     자체를 예약할 필요는 없습니다. 그 맵은 64 비트로 정렬되어 있어야
     합니다.
     

   – version

     이것은 이 구조체의 버전입니다. 버전 1은 여기서 멉춥니다. 버전 2는
     추가적인 필드 boot_cpuid_phys를 추가합니다. 버전 3은 커널이 부팅
     시에 그를 쉽게 재할당하고 사용되지 않는 평면 구조를 확장 후에
     해제할 수 있도록, 문자열들 블록의 크기를 추가합니다. 버전 16은
     트리 자체의 새로운 좀 더 “작은”, 그러나 이전과 호환되지는 않는
     형식을 도입합니다. 버전 17은 재할당하거나, 더 쉽게 옮길 수 있도록
     할 수 있는 추가적인 필드, size_dt_struct 를 추가합니다(이는 감지된
     정보를 기초로 디바이스 트리로 조정하는 부트 로더에 특히 유용합니다).
     여러분은 언제나 여러분의 구현 시점에 정의된 가장 높은 버전의
     구조체를 만드는 것이 좋습니다. 여러분이 명시적으로 하위 호환성에
     초점을 맞추고 있지 않다면, 현재는 버전 17입니다.
     

   – last_comp_version

     마지막 호환 버전. 이것은 여러분이 하위 호환을 제공하는 DT 블록이
     무슨 버전인지를 나타냅니다. 예를 들면, 버전 2는 버전 1과 하위
     호환(즉, 버전 1의 커널 빌드는 버전 2 형식으로 부팅됨)됩니다.
     여러분이 버전 1에서 3의 디바이스 트리를 생성한다면, 이 필드에 1을,
     버전 16이나 새로운 유닛 이름 형식을 사용하는 17의 디바이스 트리를
     생성한다면 16을 넣는 것이 좋습니다.

   – boot_cpuid_phys

     이 필드는 버전 2 헤더에만 존재합니다. 그것은 어떤 CPU ID 가 커널
     진입점을 호출하는지를 나타냅니다. 이것은 다른 것들 중에서도 kexec에
     의해서 사용됩니다. 만약 여러분이 SMP 시스템 상에 있다면, 이 값은
     커널 진입점을 호출하는 CPU에 해당하는 디바이스 트리 안의 CPU 노드의
     “reg” 프로퍼티의 내용과 맞아야 합니다(요구되는 디바이스 트리 내용에
     대한 더 많은 정보는 다른 챕터를 보세요).

   – size_dt_strings

     이 필드는 버전 3과 그 이후 헤더에만 존재합니다. 그것은
     (off_dt_strings로 주어지는 오프셋에서 시작하는) 디바이스 트리의
     “문자열들” 섹션의 크기를 줍니다. 

   – size_dt_struct

     이 필드는 버전 17과 그 이후 헤더에만 존재합니다. 그것은
     (off_dt_struct로 주어지는 오프셋에서 시작하는) 디바이스 트리의
     “구조” 섹션의 크기를 줍니다.

   그래서 DT 블록의 전형적인 배치는 (여러 부분들이 저 순서일 필요는
   없지만) 다음과 같습니다(주소는 위에서 아래로 진행):


             ——————————
     base -> |  struct boot_param_header  |
             ——————————
             |   (정렬을 위한 공간) (*)   |
             ——————————
             |       메모리 예약 맵       |
             ——————————
             |     (정렬을 위한 공간)     |
             ——————————
             |                            |
             |     디바이스 트리 구조     |
             |                            |
             ——————————
             |     (정렬을 위한 공간)     |
             ——————————
             |                            |
             |   디바이스 트리 문자열들   |
             |                            |
      —–> ——————————
      |
      |
      — (base + totalsize)

  (*) 정렬을 위한 공간이 반드시 있지는 않습니다; 그 존재와 크기는 개개의
      데이터 블록의 다양한 정렬 요구 사항에 달려 있습니다.


2) 디바이스 트리 일반론
———————–

디바이스 트리 그 자체는 두가지 다른 블록, 구조 블록과 문자열들 블록으로
나뉘어 집니다. 둘 모두 4바이트 경계로 정렬될 필요가 있습니다.

먼저, 저장 형식을 상세히 하기 전에 디바이스 트리 개면에 대해 빠르게
설명해 봅시다. 이 챕터는 커널을 위한 노드와 프로퍼티들의 요구 타입의
자세한 사항을 설명하지는 _않습니다_. 이것은 챕터 III에서 나중에 합니다.

디바이스 트리 배치는 오픈 펌웨어 IEEE 1275 디바이스-트리 의 정의를
강력하게 상속받습니다. 그것은 기본적으로 각 노드가 두가지 혹은 그 이상의
이름있는 프로퍼티를 가지는 노드들의 하나의 트리입니다. 프로퍼티는
값을 가질 수도, 가지지 않을 수도 있습니다.

그것은 하나의 트리입니다. 그래서 부모가 없는 루트 노드를 제외한 각 노드는
오직 하나의 부모를 갖습니다.

하나의 노드는 2개의 이름을 갖습니다. 실제 노드 이름은 일반적으로 노드
프로퍼티 리스트 안의 값이 0으로 끝나는 문자열인 “이름(name)” 타입의
프로퍼티 안에 포함되고, 이는 (오픈 펌웨어 안에 있는 것처럼) 형식 정의의
버전 1에서 3까지에서는 필수 사항입니다. 버전 16은 아래 정의된 유닛
이름으로부터 이를 생성할 수 있기 때문에, 그것을 선택 사항으로
만들었습니다.

같은 레벨의 같은 이름과 노드를 구분하는데 사용되는 “유닛 이름(unit
name)”도 있는데, 그것은 보통 정의가 그 노드가 부착된 버스 타입으로
특정되는 노드 이름, “@” 기호과 “유닛 주소”로 만듭니다.

유닛 이름은 그 자체로 프로퍼티로 존재하지는 않지만, 디바이스 트리
구조 안에 포함됩니다. 그것은 일반적으로 디바이스 트리 안의 “경로
(path)”를 나타내는데 사용됩니다. 실제 이들 형식에 대한 더 자세한 사항은
아래에서 나타날 것입니다.

커널의 일반적인 코드는 유닛 주소의 어떠한 정규적인 용도를 (어떤 보드
지원 코드가 한다고 하더라도) 만들지 않기 때문에, 여기서의 유닛 주소를
위한 진짜 요구 사항은 그 트리의 주어진 레벨에서의 그 노드 유닛 이름의
유일성을 확인하는 것입니다. 주소 표현 없는 노드들과 (/memory 또는
/cpus 같은) 동일한 이름의 가능하지 않은 형제는 이 명세의 문맥 안에서
유닛 주소를 생략할 것이고, 또는 기본 유닛 주소 “@0″을 사용할 것입니다.
그 유닛 이름은 “/”로 분리된 모든 부모 노드 유닛 이름들을 이어 붙인 
노드 “전체 경로(full path)”를 정의하는데 사용됩니다.


루트 노드는 정의된 이름을 갖지 않고, 여러분이 버전 3 혹은 그 이전의
형식을 사용하고 있다면 name 프로퍼티를 갖도록 요구되지 않습니다. 또한,
유닛 주소도 갖지 않습니다(유닛 주소로 @ 기호가 뒤따르지 않음). 루트
노드 유닛 이름은 그래서 빈 문자열입니다. 루트 노드로의 전체 경로는
“/” 입니다.

실제 디바이스를 실제로 나타내는 (즉, “/cpus” 같은 다른 노드를 위한
가상 “컨테이너”일 뿐인 노드가 아닌) 각 노드는 또한 특정 하드웨어와
그것과 완전한 하위 호환되는 디바이스의 옵션 리스트를 나타내는
“compatible” 프로퍼티를 갖습니다.

마지막으로, 다른 노드 안의 프로퍼티로부터 참조될 수 있는 각 노드는
“phandle” 이나 “linux,phandle” 프로퍼티를 가져야 합니다. 진짜 오픈
펌웨어 구현은 “prom_init()” 점프 코드가 “linux,phandle” 프로퍼티로
바꾸는 각 노드의 유일한 “phandle” 값을 제공합니다. 그러나, 이것은
평면 디바이스 트리가 직접 사용된다면, 선택 사항이 됩니다. 다른 노드를
“phandle”을 통해 참조하는 노드의 예는 이 문서의 이후 버전 안에서
서술될 인터럽트 트리를 배치할 때 같은 것입니다.

“phandle” 프로퍼티는 노드를 유일하게 식별하는 32 비트 값입니다.
시스템 값, 내부 포인터같은 어떠한 값을 사용하거나, 이들을 어떻게
생성하든 맘대로입니다. 유일한 요구 사항은 각 노드가 그를 위한 유일한
값을 가지는 프로퍼티를 제공하는 것 뿐입니다.

여기 간단한 디바이스 트리 예제가 있습니다. 이 예제에서, “o” 는 노드
유닛 이름이 따름을 가리킵니다. 프로퍼티들은 그 내용이 따르는 그들의
이름으로 나타납니다. “내용”은 (0으로 끝나는) ASCII 문자열 값을
나타내는 반면, <내용>은 10진수나 (0x 접두사 뒤의) 16진수로 지정되는
32비트 값을 나타냅니다. 이 시점에서, 저는 여러분에게 실제로 트리가
무엇처럼 보이는지 더 좋은 생각을 제공하기 위해서 의도적으로 “name”과
“linux,phandle” 프로퍼티를 남겨두었습니다.

  / o device-tree
      |- name = “device-tree”
      |- model = “MyBoardName”
      |- compatible = “MyBoardFamilyName”
      |- #address-cells = <2>
      |- #size-cells = <2>
      |- linux,phandle = <0>
      |
      o cpus
      | | – name = “cpus”
      | | – linux,phandle = <1>
      | | – #address-cells = <1>
      | | – #size-cells = <0>
      | |
      | o PowerPC,970@0
      |   |- name = “PowerPC,970”
      |   |- device_type = “cpu”
      |   |- reg = <0>
      |   |- clock-frequency = <0x5f5e1000>
      |   |- 64-bit
      |   |- linux,phandle = <2>
      |
      o memory@0
      | |- name = “memory”
      | |- device_type = “memory”
      | |- reg = <0x00000000 0x00000000 0x00000000 0x20000000>
      | |- linux,phandle = <3>
      |
      o chosen
        |- name = “chosen”
        |- bootargs = “root=/dev/sda2”
        |- linux,phandle = <4>

이 트리는 거의 최소한의 트리입니다. 그것은 리눅스 커널을 부팅하기 위해
필요한 노드와 프로퍼티의 최소한만큼만 멋지게 포함합니다;
즉, 루트의 기본 모델 정보, CPU들, 그리고 물리 메모리 배치입니다.
또한 그것은 /chosen을 통해 넘어오는 부가적인 정보, 예를 들면, 플랫폼
타입(필수 사항)와 커널 커맨드 라인 인자(선택 사항)을 포함합니다.


/cpus/PowerPC,970@0/64-bit 프로퍼티는 값이 없는 프로퍼티의 한 예입니다.
모든 다른 프로퍼티는 하나의 값을 가집니다. #address-cless와 $size-cells
프로퍼티가 지정하는 것은 필요로 하는 노드와 프로퍼티, 그들의 내용을
정밀하게 정의하는 챕터 IV에서 설명될 것입니다.


3) 디바이스 트리 “구조” 블록

디바이스 트리의 구조는 선형 트리 구조입니다. “OF_DT_BEGIN_NODE” 토큰은
새 노드를 시작하고, “OF_DT_END_NODE”는 노드 정의를 끝냅니다. 자식
노드들은 (그 노드 안의 노드들) “OF_DT_END_NODE” 전에 간단히 정의됩니다.
‘토큰(token)’은 32비트 값입니다. 트리는 OF_DT_END 토큰으로 끝납니다.

여기 한 노드의 기본 구조입니다:

     * (0x00000001인) OF_DT_BEGIN_NODE 토큰
     * 버전 1에서 3은 이것이 0으로 끝나고, “/”로 시작하는 노드 전체
       경로입니다. 버전 16과 그 이후에 이것은 노드 유닛 이름(또는,
       루트 노드의 빈 문자열)일 뿐입니다.
     * [다음 4바이트 경계로 공간을 정렬]
     * 각 프로퍼티는:
        * (0x00000003인) OF_DT_PROP 토큰
        * 바이트로 된 프로퍼티 값 크기인 32비트 값(또는 값이 없으면 0)
        * 프로퍼티 이름의 문자열 블록 안의 오프셋인 32비트 값
        * 있다면, 프로퍼티 값 데이터
        * [다음 4바이트 경계로 공간을 정렬]
     * [있다면, 자식 노드들]
     * (0x00000002인) OF_DT_END_NODE 토큰

그래서 노드 내용은 시작 토큰, 전체 경로, 프로퍼티의 리스트, 자식 노드의
리스트, 그리고 끝 토큰으로 요약될 수 있습니다. 모든 각 자식 노드는 위에
정의된 것 같은 그 자체가 전체 노드 구조입니다.

알림: 위의 정의는 특정 노드를 위한 모든 프로퍼티 정의들이 그 노드를 위한
어떤 보조 노드 정의보다 우선해야 함을 필요로 합니다. 프로퍼티들과 보조
노드 들이 막 섞여 있다면, 그 구조가 애매하지 않아야 하지만, 커널 파서
(parser)는 그 프로퍼티들이 먼저 올 것을 (적어도 2.6.22까지는) 요구합니다.
어떤 (평면 드리를 조작하는) 툴도 이 제약을 지켜야 합니다.

4) 디바이스 트리 “문자열들” 블록

공간을 아끼기 위해서, 일반적으로 중복되는 프로퍼티 이름들을 “문자열들”
블록 안에 별도로 저장합니다. 이 블록은 간단히 모든 프로퍼티 이름을 위한
0으로 끝나는 문자열 전부를 연이어 붙여놓은 것입니다. 구조 플록 안의
디바이스 트리 프로퍼티 정의들은 문자열들 블록의 시작으로부터의 오프셋
값을 포함할 것입니다.


III – 디바이스 트리에서 필요한 내용
===================================

경고: 이 문서 내에 정의된 모든 “linux,*” 프로퍼티들은 평면 디바이스
트리에만 적용됩니다. 만약 여러분의 플랫폼이 오픈 펌웨어의 진짜 구현이나,
오픈 펌웨어 클라이언트 인터페이스와 호환되는 구현을 사용한다면, 그들
프로퍼티들은 커널의 prom_init() 파일 내의 점프 코드에 의해서 생성될
것입니다. 예를 들면, 여러분이 여러분의 보드 모델을 감지하기 위한 코드를
넣고, 플랫폼 넘버를 셋팅해야만 하는 곳 말입니다. 그러나, 평면 디바이스
트리 진입점을 사용할 때는 prom_init() 을 지나지 않고, 그래서 여러분은
그들 프로퍼티를 스스로 제공해야만 합니다.


1) 셀(cells)과 주소 표현에 관한 기록
————————————

일반적인 룰은 다양한 오픈 펌웨어 문서들 내에 문서화되어 있습니다.
여러분이 버스를 디바이스 트리 서술하는 것을 고르고, OF 버스 바인딩이
있다면, 그 명세를 따르는 것이 좋습니다. 그러나 커널은 디바이스 트리에
의해서 설명되는 각각의 모든 독립된 디바이스나 버스를 요구하지 않습니다.

일반적으로 디바이스를 위한 주소의 형식은 부모 버스 타입에 의해
#address-cells 와 #size-cells 프로퍼티에 기초하여 정의됩니다.
그 부모의 부모 #address-celss와 #size-cells 정의들은 상속되지 않으므로
자실을 가진 모든 각각의 노드는 그들을 지정해야 합니다. 커널은 루트 노드가
직접 프로세스 버스 상에 맵핑된 디바이스들을 위한 주소들의 형식을 정의하는
이들 프로퍼티를 가질 것을 요구합니다.

이들 2 프로퍼티는 주소와 크기를 표현하기 위한 ‘cells’를 정의합니다.
예를 들면, 만약 둘 다 위에서 주어졌던 예제 트리 같이 2를 갖는다면,
주소와 크기는 모두 2 셀로 구성되고, 각각은 64비트 수(셀들은 이어 붙고,
빅 엔디안 형식일 것으로 여겨집니다)입니다. 또다른 예제는 애플 펌웨어가
그들을 하나의 주소를 위한 두 셀과 크기를 위한 하나의 셀로 정의하는
방법입니다. 대부분의 32비트 구현들은 #address-cless와 #size-cless를
32비트 값으로 표현하는 1로 정의해야 합니다. 어떤 32비트 프로세서들은
32비트 이상의 물리 주소를 혀용합니다; 이들 프로세서들은 #address-celss를
2로 정의해야 합니다.

“reg” 프로퍼티들은 언제나 버스 #address-cless와 #size-cells에 의해 
정해지는 주소와 크기의 셀의 개수인 “address size” 타입의 튜플입니다.
버스는 여러가지 주소 공간과 조어진 주소 할당(prefetchable이나 기타 등등
같은)과 연관된 다른 플래그들을 지원합니다. 이들 플래그들은 일반적으로
물리 주소의 최상위 비트에 붙습니다. 예를 들면, PCI 물리 주소는 3 셀로
만들어지는데, 가장 아래 둘이 실제 주소 자체를 포함하는 반면, 최상위
셀이 주소 공간 표현, 플래그들, 그리고 pci 버스 & 디바이스 수를 가집니다.

동적 할당 지원을 위한 버스를 위해, “reg” 안의 주소를 제공하지 않기
(0을 갖음)는 하지만, 주소가 동적으로 할당되는 것을 표시하는 플래그를
제공하고, 완전히 할당된 주소들을 갖는 개별 “assigned-addresses”
프로퍼티를 제공하는 것이 관행입니다. 자세한 사항은 PCI OF 바인딩을
보세요.

보통, 주소 공간 비트가 없고, 동적 할당도 없는 간단한 버스가 여러분의
하드웨어를 반영한다면, 존재하는 커널 주소 파싱 함수들이 틀 밖에서
실행되므로 더 좋습니다. 만약 여러분이 주소 공간 비트 같은 것들을 포함하는
더 복잡한 주소 형식으로 버스 타입을 정의한다면, 버스 번역기를 여러분의
버스 타입을 위한 최신 커널의 prom_parse.c 파일에 덧붙여야만 할 것입니다.


“reg” 프로퍼티는 조어진 버스 내의 오직 주소와 크기만 (#size-cells가 0이
아니라면) 정의합니다. 위로 주소를 바꾸기 위해서(부모 버스 주소로,
가능하면 CPU 물리 주소로), 모든 버스는 “ranges” 프로퍼티를 포함해야만
합니다. “ranges” 프로퍼티가 주어진 레벨에 없다면, 주소 변환은 불가능한
것으로, 즉, 부모 버스 상에 레지스터들이 안보이는 것으로 가정합니다.
버스를 위한 “ranges” 프로퍼티의 형식은 다음의 리스트입니다:

        버스 주소, 부모 버스 주소, 크기

“버스 주소”는 이 버스 노드가 정의하는 버스의 형식 안에 있습니다. 즉,
PCI 브릿지에서는 PCI 주소가 될 겁니다. 그래서 (버스 주소, 크기) 는
자식 디바이스의 주소의 범위를 정의합니다. “부모 버스 주소”는 이 버스의
부모 버스의 형식 안에 있습니다. 예를 들면, PCI 호스트 컨트롤러에서는
CPU 주소가 될 것입니다. PCI<->ISA 브릿지에서는 PCI 주소가 될 겁니다.
이것은 그 범위의 시작이 맵핑된 부모 버스 내의 베이스 주소를 정의합니다.

64비트 보드 지원을 위해서, 저는 2/2 형식이나 크기가 보통 하나의 32비트
워드 안에 들어가기 때문에 약간 더 작은 애플의 2/1 형식을 추천합니다.
새로운 32비트 보드 지원은 그 프로세서가 2/1 형식이 권장되는 32비트
이상의 물리 주소를 지원하기 전까지는 1/1 형식을 사용해야 합니다. 

아니면, 그 레지스터들이 식별 맵핑 변환을 사용하는 부모 버스 상에서 보임을
나타내는 것으로, “ranges” 프로퍼티를 빈 상태로 둡니다. 다른 말로 하면,
그 부모 버스 주소 공간은 자식 버스 주소 공간과 같다는 것입니다.

2) “compatible” 프로퍼티에 대한 기록
————————————

이들 프로퍼티들은 선택 사항입니다만, 디바이스와 루트 노드 안에서
권장됩니다. “compatible” 프로퍼티의 형식은 이어진 0으로 끝나는 문자열의
하나의 리스트입니다. 그들은 디바이스가 그들의 유사한 디바이스 계열과의
호환성을, 어떤 경우에는 하나의 드라이버가 그들의 실제 이름과 상관없이
여러 디바이스들과 맞춰지는 것을 나타낼 수 있습니다. 

3) “name” 프로퍼티에 대한 기록
——————————

OldWorld 매킨토시 같은 오픈 펌웨어의 초기 사용자들은 “name” 프로퍼티에
실제 디바이스 이름을 사용하는 경향이 있었던 반면, 오늘날에는 디바이스
클래스(종종 device_type과 같은)에 더 가까운 이름을 사용하는 것이
좋은 실제 예제로 고려됩니다. 예를 들면, 오늘날에는, 이더넷 컨트롤러는
“ethernet”으로, 그 칩 타입/모델을 정확히 정의하는 추가적인 “model”
프로퍼티, 하나의 드라이버가 하나 이상의 이들 칩을 드라이버할 수 있는
경우 그 계열을 정의하는 “compatible” 프로퍼티로 이름 지어집니다.
그러나, 커널은 보통 “name” 프로퍼티 상에 어떤 제한도 두지 않습니다;
그것은 간단히 표준을 따르고, 그 진화에 가능한한 가까이하고자 하는
좋은 관례입니다.


새로운 형식 버전 16은 “name” 프로퍼티를 선택 사항으로 만들었음을 또한,
알아두세요. 어느 노드에 그게 없다면, 그 노드의 유닛 이름이 그 이름을
재구성하는데 사용됩니다. 즉, “@” 기호 전의 그 유닛 이름의 일부가
사용됩니다(또는 “@” 기호가 없다면, 전체 유닛 이름).

4) 노드와 프로퍼티 이름과 문자셋에 대한 기록
——————————————–

오픈 펌웨어가 8859-1의 더 유연한 용법을 제공하는 반면, 이 명세는
더 제한적인 규칙을 강제합니다. 노드와 프로퍼티들은 ASCII 문자들,
‘a’에서 ‘z’, ‘0’에서 ‘9’, ‘,’, ‘.’, ‘_’, ‘+’, ‘#’, ‘?’, 그리고 ‘-‘로만
조합되어야 합니다. 노드 이름은 추가적으로 대문자들 ‘A’에서 ‘Z’
(프로퍼티 이름은 소문자여야 합니다. 애플과 같은 업체들은 이 규칙을
존중하지 않는다는 사실은 여기서 무관합니다)를 허용합니다. 추가적으로,
노드와 프로퍼티 이름들은 언제나 ‘a’에서 ‘z'(또는 노드 이름에 ‘A’에서
‘Z’) 안의 문자로 시작해야 합니다.

노드와 프로퍼티 이름 모두, 문자의 최대 개수는 31입니다. 노드 이름의
경우, 이것은 유닛 이름의 맨 왼쪽의 이름(순수한 “name” 프로퍼티)이고,
그 제한 너머로 확장될 수 있는 유닛 주소를 포함하지 않습니다.

5) 필요 노드와 프로퍼티들
————————-
  이들이 현재 필요한 모두입니다. 그러나, 오픈 펌웨어로 PCI 바인딩 안에
  PCI 호스트 브릿지를 문서화하고, 여러분의 인터럽트 트리를 OF 인터럽트
  트리 명세 안에 문서화하여 노출하는 것을 강력히 권장합니다.
  

  a) 루트 노드

  루트 노드는 존재하기 위해서 몇가지 프로퍼티가 필요합니다:

    – model : 이것은 여러분의 보드 이름/모델입니다.
    – #address-cells : “root” 디바이스의 주소 표현
    – #size-cells: “root” 디바이스의 크기 표현
    – compatible : 여기서 그들의 방법으로 보통 찾을 수 있는 보드 “계열”,
      예를 들면, 커널 내의 같은 플랫폼 코드로 보통 구동될 수 있는
      유사한 배치의 2 보드 모델이 있다면, 여러분은 정확한 보드 모델을
      SOC 모델을 나타내는 항목에 따른 compatible 프로퍼티 내에 지정할
      것 입니다.

  루트 노드는 또한 아무거나 일련 번호같은 여러분의 보드에 따른 추가적인
  프로퍼티를 추가하는 곳 입니다. 표준이 정의하는 어떤 것과 충돌할만한
  어떤 “맞춤형” 프로퍼티가 있다면, 벤터 이름과 콤마를 앞에 붙이는 것이
  권장됩니다.

  b) /cpus 노드

  이 노드는 모든 개별 CPU 노드들의 부모입니다. 어떤 지정 요구 사항도
  없긴하지만 일반적으로 적어도 다음을 가지는 것이 좋은 관례입니다:

               #address-cells = <00000001>
               #size-cells    = <00000000>

  이것은 한 CPU의 “주소”가 하나의 셀이고, 의미없는 크기를 가지고
  있음을 정의합니다. 이것이 필요하진 않지만, 커널은 한 CPU 노드의 “reg”
  프로퍼티를 읽을 때의 형식을 추정하고 아래를 볼 것입니다.

  c) /cpus/* 노드들

  그래서 /cpus 아래에서, 여러분은 머신 상의 모든 개별 CPU의 노드를
  생성한다고 추측할 수 있습니다. CPU 이름에 어떤 지정 제한은 없지만,
  <아키텍처>,<코어> 로 부르는 게 일반적입니다. 예를 들면, 애플은
  PowerPC,G5를 사용하는 반면, IBM 은 PowerPC,970FX를 사용합니다.
  그러나 일반적인 이름 관례는 모든 개별 cpu 노드에 간단히 ‘cpu’를
  사용하고, 특정 cpu 코어를 식별하기 위해서 compatible 프로퍼티를
  사용하는 것이 더 좋다고 합니다.

  필수 프로퍼티:

    – device_type : “cpu”가 되어야만 함.
    – reg : 이것은 물리적 CPU 넘버입니다. 하나의 32비트 셀이고, 또한,
      전체 경로 안의 유닛 이름을 재구성하기 위한 유닛 넘버로써 그냥
      사용됩니다. 예를 들면, 2 CPU에서 전체 경로는 다음과 같습니다:
        /cpus/PowerPC,970FX@0
        /cpus/PowerPC,970FX@1
      (유닛 주소는 0으로 시작할 필요는 없습니다)
    – d-cache-block-size : 하나의 셀, 바이트로 된 L1 데이터 캐시 블록
      크기 (*)
    – i-cache-block-size : 하나의 셀, 바이트로 된 L1 명령 캐시 블록 크기
    – d-cache-size : 하나의 셀, 바이트로 된 L1 데이터 캐시 크기
    – i-cache-size : 하나의 셀, 바이트로 된 L1 명령 캐시 크기

(*) 캐시 “블록” 크기는 캐시 관리 명령 동작 상의 크기입니다. 역사적으로,
이 문서는 여기서 올바르지 않은 캐시 “라인” 크기를 사용합니다. 커널은
캐시 블록 크기를 더 선호할 것이고, 하위 호환성을 위해 캐시 라인 크기로
후퇴할 것입니다.

  권장 프로퍼티:

    – timebase-frequency : Hz로 된 시간 축의 주파수를 나타내는 셀. 이것은
      일반적인 코드에 의해 직접 사용되지는 않지만, 이 값을 기초로 커널
      시간 축/감쇠기 보정을 셋팅하기 위해 p시리즈 코드를 복사/붙여넣기하는
      것도 괜찮습니다.
    – clock-frequency : Hz로 된 CPU 코어 클럭 주파수를 나타내는 셀.
      새로운 프로퍼티는 64비트 값으로 정의될 것이지만, 여러분의 주파수가
      < 4GHz 이면, 하나의 셀로 충분합니다. 여기서는 위에서처럼 좋게,
      일반적인 코드는 이 프로퍼티를 사용하지 않습니다만, p시리즈나
      Maple 것을 재사용하는 것도 괜찮습니다. 미래의 커널 버전은 이를 위한
      일반적인 코드를 제공할 것입니다.
    – d-cache-line-size : 한 셀, 블록 크기와 다르다면, 바이트로 된
      L1 데이터 캐시 라인 크기
    – i-cache-line-size : 한 셀, 블록 크기와 다르다면, 바이트로 된
      L1 명령 캐시 라인 크기

  CPU 들을 소프트-리셋하는데 사용되는 메카니즘에 관한 어떤 정보같은
  여러분의 보드에 관련되어 찾은 어떤 프로퍼티를 추가하는 것도 괜찮습니다.
  예를 들면, 애플은 그들을 소프트 리셋함으로써 두번째 CPU들을 시작하면서
  CPU 소프트 리셋 라인을 위한 GPIO 넘버를 “soft-reset” 프로퍼티로
  넣었습니다.


  d) /memory 노드(들)

  여러분 보드의 물리 메모리 배치를 정의하기 위해서, 여러분은 하나 또는
  그 이상의 memory 노드(들)을 생성해야 합니다. 여러분은 원하는대로,
  하나의 노드에 모든 메모리 범위를 그 reg 프로퍼티에 넣거나, 또는 여러
  노드를 생성할 수 있습니다. 전체 경로에 사용되는 유닛 주소(@ 부분)는
  주어진 노드에 의해 정의된 메모리의 첫번째 범위의 주소입니다. 여러분이
  하나의 메모리 노드를 사용한다면, 일반적으로 @0이 될 것입니다.
  
  필수 프로퍼티:

    – device_type : “memory”가 되어야 함.
    – reg : 이 프로퍼티는 여러분 보드의 모든 물리 메모리 범위를 갖습니다.
      루트 노드의 #address-cells와 #size-cells로 각각 정의된 셀들을
      모두 이어 붙인 주소들/크기들의 하나의 리스트입니다. 예를 들면,
      이들 프로퍼티 둘 다 앞에서 주어진 예제처럼 2를 갖고, 6Gb 램의
      970 베이스 모델이 보통 다음처럼 여기서 “reg” 프로퍼티를 갖을 수
      있습니다:

      00000000 00000000 00000000 80000000
      00000001 00000000 00000001 00000000

      이것은 0에서 시작하는 0x80000000 바이트 범위와 0x10000000에서
      시작하는 0x10000000 바이트 범위입니다. 여러분은 2Gb 과 4Gb 사이의
      IO 홀을 대신하는 메모리가 없음을 볼 수 있습니다. 어떤 업체는
      더 작은 세그먼트로 이들 범위를 나누는 것을 더 선호합니다만,
      커널은 신경쓰지 않습니다.

  e) /chosen 노드

  이 노드는 약간 “특별”합니다. 보통, 오픈 펌웨어가 인자들이나, 또는
  기본 입/출력 디바이스와 같은 어떤 환경 변수 정보를 두는 곳입니다.

  이 명세는 약간의 필수 요소를 갖습니다만, 또한 보통 OF 클라이언트
  인터페이스로 부팅할 때 prom_init() 분기 코드에 의해 재구성되는 몇가지
  리눅스에만 필요한 프로퍼티를 정의합니다. 그러나 여러분은 평면화
  형식을 사용했을 때 여러분 스스로 제공해야만 합니다.

  권장 프로퍼티:

    – bootargs : 이것은 커널로 전달되는 0으로 끝나는 문자열입니다.
    – linux,stdout-path : 이것은 만약 있다면, 여러분의 표준 콘솔
      디바이스의 전체 경로입니다. 보통, 여러분 보드에 시리얼 디바이스가
      있다면, 커널이 그를 기본 콘솔로 고르도록 여기서 펌웨어 내에
      기본 콘솔로 그 하나를 전체 경로를 두고 싶을 것입니다.

  u-boot 은 그를 사용하는 플랫폼을 위해 chosen 노드를 생성하고 내부를
  채움을 알아두세요.

  (알림: 지금은 구식이 된 관례는 주 인터럽트 컨트롤러를 가리키는
  phandle 값을 가지는 interrupt-controller 로 불리는 프로퍼티를
  /chosen 아래에 포함하는 것입니다)

  f) /soc<SOC이름> 노드

  이 노드는 시스템-온-칩(SOC)를 표현하는데 사용되고, 프로세서가 SoC라면
  반드시 있어야 합니다. 최고 레벨 soc 노드는 SoC 상의 모든 디바이스에
  대한 전역 정보를 포함합니다. 노드 이름은 SoC의 메모리-맵핑된
  레지스터S의 기초 주소인 SoC의 유닛 주소를 포함해야 합니다. SoC의
  이름은 “soc”로 시작해야하고, 나머지 이름은 soc의 부품 번호를 나타내야
  합니다. 예를 들면, MPC8540의 soc 노드는 “soc8540″이 될 겁니다.
  

  필수 프로퍼티:

    – ranges : 메모리 맵핑된 SoC 레지스터의 SoC 주소의 변환을
      설명하도록 지정하여 정의되어야 함.
    – bus-frequency: SoC 노드의 버스 주파수를 포함. 보통 이 필드의
      값은 부트 로더에 의해 채워집니다.
    – compatible : SoC의 정확한 모델


  권장 프로퍼티:

    – reg : 이 프로퍼티는 SOC 노드 그 자체적으로 사용되는 메모리 맵핑된
      레지스터들의 주소와 크기를 정의합니다. 자식 디바이스 레지스터들은
      포함하지 않습니다 – 이들은 각 자식 노드 내에 정의될 것입니다.
      그 “reg” 프로퍼티 내에 지정된 주소는 SOC 노드의 유닛 주소와
      맞아야 합니다.
    – #address-cells : “soc” 디바이스의 주소 표현. 이 필드의 형식은
      디바이스 레지스터들이 메모리 맵핑됐느냐에 따라 다양할 것입니다.
      메모리 맵핑된 레지스터들에 대해서 이 필드는 레지스터들의 주소를
      나타내는데 필요한 셀의 수를 나타낼 것입니다. MMIO를 사용하지 않는
      SOC에서는 필요한 정보를 나타내는 충분한 셀을 포함하는 특별한 주소
      형식으로 정의되어져야 합니다. #address-cells를 정의하는데 대한
      자세한 사항은 위의 1) 을 보세요.
    – #size-cells : “soc” 디바이스의 크기 표현
    – #interrupt-cells : 인터럽트를 표현하는데 사용되는 셀의 너비를
      정의합니다. 보통 이 값은 인터럽트 번호를 표현하는 32비트 수 하나와
      인터럽트 감지와 레벨을 표현하는 32비트 수 하나를 포함하는 <2>
      입니다. 이 필드는 오직 SOC가 인터럽트 컨트롤러를 포함할 때만
      포함됩니다.

  SOC 노드는 플랫폼이 사용하는 각 개별 SOC 디바이스를 위한 자식 노드들을
  포함할 것 입니다. 노드들은 SOC 상에 존재는 하지만 특정 플랫폼에서
  사용되는 디바이스들은 생성되지 않아야 합니다. SOC 의 일부인 디바이스를
  어떻게 지정하는지에 대한 더 많은 정보는 챕터 VI를 보세요.

  MPC8540의 SOC 노드 예제:

        soc8540@e0000000 {
                #address-cells = <1>;
                #size-cells = <1>;
                #interrupt-cells = <2>;
                device_type = “soc”;
                ranges = <0x00000000 0xe0000000 0x00100000>
                reg = <0xe0000000 0x00003000>;
                bus-frequency = <0>;
        }



IV – “dtc”, 디바이스 트리 컴파일러(device tree compiler)
========================================================


dtc 소스 코드는 다음에서 찾을 수 있습니다.
<http://git.jdl.com/gitweb/?p=dtc.git>

경고: 이 버전은 아직 초기 개발 단계에 있습니다; 결과 디바이스 트리
“바이너리 객체”는 커널에 아직 유효하지 않습니다. 현재 생성되는 블록은
무엇보다도 유용한 예약 맵(빈 것을 생성하도록 고쳐질 것이고 부트로더가
채울 것입니다)이 없습니다. 에러 처리도 작업이 필요하고, 버그들이
숨어 있고, 또…


dtc는 기본적으로 주어진 형식의 디바이스-트리를 취해서 다른 형식으로
디바이스-트리를 출력합니다. 현재 제안되는 형식은 다음과 같습니다:

  입력 형식:
  ———-

     – “dtb”: “바이너리 객체” 형식, 즉, 바이너리 객체 내의 모든 헤더로
       된 평면 디바이스-트리 블록
     – “dts”: “소스” 형식. 이것은 디바이스-트리의 “소스”를 포함하는
        텍스트 파일입니다. 이 형식은 이 챕터 이후에 정의됩니다.
     – “fs” 형식. 이것은 노드들이 디렉토리들이고, 프로퍼티들이 파일들인
        /proc/device-tree 의 출력과 동등한 표현입니다.
        

 출력 형식:
 ———-

     – “dtb”: “바이너리 객체” 형식
     – “dts”: “소스” 형식
     – “asm”: 어셈블리 언어 파일. 이것은 디바이스-트리 “바이너리 객체”
       를 생성하는 가스에 의해 얻어질 수 있는 파일입니다. 이 파일은
       간단히 여러분의 Makefile에 붙을 수 있습니다. 추가적으로, 그
       어셈블리 파일은 사용될 수 있는 심볼들을 노출합니다.


dtc 툴의 문법은

    dtc [-I <입력-형식>] [-O <출력-형식>]
        [-o 출력-파일이름] [-V 출력_버전] 입력_파일이름


“출력_버전”은 생성될 “바이너리 객체” 형식이 무슨 버전인지 정의합니다.
지원되는 버전은 1,2,3, 그리고 16입니다. 기본은 현저 버전 3 이지만,
지나면 버전 16 으로 바뀔 겁니다.

덧붙여, dtc는 linux의 유일성, phandle 프로퍼티들, 문자열들의 유효함,
기타… 와 같은 트리 상의 다양한 자체 검사를 수행합니다.

.dts “소스” 파일은 C 와 C++ 스타일 주석을 지원하는 “C” 같은 형식이다.

/ {
}

위는 “디바이스-트리” 정의이다. 최고 레벨에 현재 지원되는 단하나의
문장입니다.

/ {
  property1 = “string_value”;   /* 0으로 끝나는 문자열을 포함하는
                                 * 프로퍼티를 정의
                                 */

  property2 = <0x1234abcd>;     /* 수로 된 32비트 값(16진수)을
                                 * 포함하는 프로퍼티를 정의
                                 */
                                 

  property3 = <0x12345678 0x12345678 0xdeadbeef>;
                                /* 수로 된 32비트 16진수 값들(셀들)
                                 * 3 개를 포함하는 프로퍼티를 정의
                                 */
  property4 = [0x0a 0x0b 0x0c 0x0d 0xde 0xea 0xad 0xbe 0xef];
                                /* 내용이 바이트들의 임의의 배열인
                                 * 프로퍼티를 정의
                                 */

  childnode@address {   /* 유닛 이름이 “address의 childnode”인
                                 * “childnode” 가 이름인 자식노드를
                                 * 정의
                                 */

    childprop = “hello\n”;      /* 자식 노드의 “childprop” 프로퍼티를
                                 * 정의(이 경우는 문자열)
                                 */
  };
};

노드들은 다른 노드들, 기타… 를 포함할 수 있어서 트리의 계층적 구조를
정의합니다.

문자열은 C에서의 일반적인 특수 문자들을 지원합니다: “\n”, “\t”, “\r”,
“\(8진수 값)”, “\x(16진수 값)”.

또한, cpp (gcc 전처리기)를 통해 여러분의 소스파일을 파이프로 흘려보낼
수 있어서 #include 의 것, 상수를 위한 #define, 기타… 를 사용할 수
있습니다.

마지막으로, phandle의 자동 생성, labels(asm 파일로 노출되어 여러분이
프로퍼티 내용을 가리킬 수 있고, 디바이스 트리로 연결한 어떠한 것으로부터
쉽게 변경할 수 있음), 어떤 셀들 안의 수로 된 값 대신 노드(컴파일 타임에
phandle에 의해 대체됨)를 “가리키는” label 또는 path, asm 파일로 예약 맵
주소를 노출하기, 컴파일 타임에 예약 맵 내용을 지정하는 능력, 기타…
같은 다양한 옵션들이 계획되어 있지만, 아직 구현되어 있지는 않습니다.

컴파일러에 구조 정의들의 개념을 추가하는 것이 더 낫겠지만, 우리는
.h 를 포함하는 파일을 (PCI 프로퍼티들 또는 인터럽트 맵 같은)몇가지
프로퍼티를 유용하게 감지하는 일반적인 정의들로 제공할 것입니다.


V – 부트로더에 대한 권장 사항
=============================


여기에 다양한 몇가지 제안되었던 생각들/권장 사항들이 있습니다. 이 모든
것들이 정의되고 구현되었습니다.

  – 부트로더는 디바이스-트리 그 자체를 사용할 수 있기를 원하고,
    조작하기(물리 메모리 크기나 커널 인자들 같은, 어떤 프로퍼티를
    추가하고/편집하고)를 원합니다. 이 시점에서 두가지 선택이 있을 수
    있습니다. 부트로더가 직접 평면화 형식 상에서 작업하거나, 또는,
    부트로더가 그 자신의 포인터들로 된(커널 것과 비슷한) 내부 트리
    표현을 갖거나 말입니다. 앞에 것이 편집/수정에 약간 더 어렵고,
    뒤에 것이 아마도 트리 구조를 처리하는 약간 더 복잡한 코드를
    필요로 합니다. 상대적으로 프로퍼티나 노드들의 “추가”가 쉽고,
    그저 메모리 이동으로 삭제하는것이 쉽게 설계되었습니다. 이런
    목적으로 내부 오프셋이나 포인터를 포함하지 않습니다.

  – 평면화 트리 형식으로부터 직접 노드들을 열거하고, 프로퍼티를
    탐색하기 위한 예제 코드는 커널 파일 drivers/of/fdt.c 안에서 찾을
    수 있습니다. of_scan_flat_dt() 함수, early_init_devtree() 안의
    그 사용 예, 그리고, 해당하는 다양한 early_init_dt_scan_*() 콜백들
    을 살펴 보세요. 그 코드는 GPL 부트로더에서 재사용 될 수 있고,
    그 코드의 작성자로서, 저는 GPL이 아닌 부트로더로 이 코드의 일부,
    또는 전부를 통합하길 원하는 업체들과 자유 라이센스를 토론할 수
    있다는 것이 행복합니다(참고가 필요합니다; 여기서 누가 ‘나’인가?
    (who is ‘I’ here?) —gcl 2011년 1월 31일)



VI – 시스템-온-칩 디바이스와 노드들
===================================

많은 회사들이 지금 프로세서 코어(CPU)와 많은 주변 장치가 한 조각의
실리콘 상에 존재하는 시스템-온-칩 프로세서 개발을 시작하고 있습니다.
이들 SOC들에 대해, SOC 노드는 SOC를 구성하는 디바이스들에 대한 자식
노드들을 정의하는데 사용되어야 합니다. 플랫폼들이 커널을 부팅하기 위해
이 모델을 사용하는 것이 필요하지 않은데 반해, 모든 SOC 구현들은
SOC 상의 디바이스들을 기술하는데 가능한 한 완전한 평면-디바이스-트리로
정의하고자 장려됩니다. 이것은 많은 양의 커널 코드의 일반화를 가능하게
할 것입니다.


1) SOC의 자식 노드들 정의하기
—————————–

SOC의 일부인 각 개별 디바이스들은 SOC 노드 내에 그들만의 노드 항목을
가질 것입니다. SOC 내에 포함된 각 개별 디바이스들을 위해, 유닛 주소
프로퍼티는 이 디바이스의 부모의 주소 공간 안의 메모리 맵핑된
레지스터들을 위한 주소 오프셋을 나타냅니다. 그 부모의 주소 공간은
최고 레벨 soc 노드 내의 “ranges” 프로퍼티로 정의됩니다. SOC 노드 아래에
직접 존재하는 각 개별 노드의 “reg” 프로퍼티는 자식 주소 공간으로부터
부모 SOC 주소 공간으로의 주소 맵핑, 디바이스의 메모리-맵핑된 레지스터
파일의 크기를 포함해야만 합니다.

SOC 내부에 존재하는 많은 디바이스들을 위해, 디바이스 트리 노드의 형식을
미리 정의하는 명세들이 있습니다. 모든 SOC 자식 노드들은 이 문서에서
알리는 곳 외에는 이 명세를 따라야 합니다.

MPC8540의 일부 SOC 노드 정의 예제를 부록 A에서 보세요.


2) 현재의 OF 명세없이 디바이스들 나타내기
—————————————–

현재, 주로 보드들이 현재 오픈 펌웨어를 사용하여 부팅되지 않는 SoC들을
포함하고 있기 때문에, 오픈 펌웨어 명세의 일부로 정의된 표준 표현이 없는
SOC 상의 많은 디바이스들이 있습니다. 새로운 디바이스의 바인딩 문서는
Documentation/devicetree/bindings 디렉토리에 추가되어야 합니다.
그 디렉토리는 더 많고 많은 SoC들에 디바이스 트리 지원을 확장할 것입니다.


VII – 디바이스들의 인터럽트 정보 지정하기
=========================================

디바이스 트리는 하드웨어 시스템의 버스들과 디바이스들을 하드웨어의
물리적 버스 토폴로지와 유사한 형식으로 표현합니다.

추가로, 하드웨어 내의 인터럽트의 계층과 경로를 표현하는 논리적인
‘인터럽트 트리’가 존재합니다.

인터럽트 트리 모델은 “오픈 펌웨어 권장 용례: 인터럽트 맵핑 버전 0.9
(Open Firmware Recommended Practice: Interrupt Mapping Version 0.9)”
안에 완전히 서술되어 있습니다. 이 문서는
<http://www.openfirmware.org/ofwg/practice/> 에서 이용 가능합니다.

1) interrupt 프로퍼티
———————

하나의 인터럽트 컨트롤러로 인터럽트를 생성하는 디바이스들은 
OF 인터럽트 맵핑 문서 내에 서술된 관례적인 OF 표현을 사용해야 합니다.

인터럽트를 생성하는 각 개별 디바이스는 ‘interrupt’ 프로퍼티를 가져야만
합니다. 인터럽트 프로퍼티 값은 디바이스의 그 인터럽트 또는 인터럽트들을
서술하는 임의의 개수의 ‘interrupt specifier’ 값들입니다.

인터럽트 지시자의 인코딩은 디바이스가 인터럽트 트리 내에 위치한
인터럽트 도메인에 따라 결정됩니다. 인터럽트 도메인의 루트는 그의
#interrupt-cells 프로퍼티 내에 인터럽트 지시자를 인코딩하는데 필요한
32비트 셀들의 수를 정합니다. 도메인들의 더 자시한 설명은 OF 인터럽트
맵핑 문서를 보세요.

예를 들면, OpenPIC 인터럽트 컨트롤러의 바인딩은 #interrupt-cells 값으로
인터럽트 번호와 레벨/감지 정보를 위해 2를 지정합니다. OpenPIC 인터럽트
도메인 내의 모든 인터럽트 자식들은 그들의 인터럽트 프로퍼티 내에
인터럽트 당 2 셀을 사용합니다.

PCI 버스 바인딩은 어느 인터럽트 핀(INTA,INTB,INTC,INTD)이 사용되는지
인코딩하기 위해서 #interrupt-cell 값으로 1을 지정합니다.

2) interrupt-parent 프로퍼티
—————————-

interrupt-parent 프로퍼티는 인터럽트 트리 내의 디바이스 노드와 그의
인터럽트 부모 간의 명시적인 링크를 정의하기 위해서 지정됩니다. 이
interrupt-parent 값은 부모 노드의 phandle 입니다.

interrupt-parent 프로퍼티가 한 노드를 정의하지 않으면, 그의 인터럽트
부모는 그 노드의 _디바이스 트리_ 계층 내의 조상으로 추정합니다.

3) OpenPIC 인터럽트 컨트롤러
—————————-

OpenPIC 인터럽트 컨트롤러는 인터럽트 정보를 인코딩하는데 2셀을
필요로 합니다. 첫번째 셀은 인터럽트 번호를 정의합니다. 두번째 셀은
감지와 레벨 정보를 정의합니다.

감지와 레벨 정보는 다음처럼 인코딩되어야 합니다:

        0 = 로우(Low)에서 하이(High)로의 엣지(Edge) 감지 타입이 활성
        1 = 엑티브 로우(active low) 레벨(level) 감지 타입이 활성
        2 = 엑티브 하이(active high) 레벨 감지 타입이 활성
        3 = 하이에서 로우로의 엣지 감지 타입이 활성

4) ISA 인터럽트 컨트롤러
————————

ISA PIC 인터럽트 컨트롤러는 인터럽트 정보를 인코딩하는데 2셀을
필요로 합니다. 첫번째 셀은 인터럽트 번호를 정의합니다. 두번째 셀은
감지와 레벨 정보를 정의합니다.

ISA PIC 인터럽트 컨트롤러는 다음에 나열된 ISA PIC 인코딩으로 붙어야
합니다.

        0 = 엑티브 로우(active low) 레벨(level) 감지 타입이 활성
        1 = 엑티브 하이(active high) 레벨 감지 타입이 활성
        2 = 하이(High)에서 로우(Low)로의 엣지(Edge) 감지 타입이 활성
        3 = 로우에서 하이로의 엣지 감지 타입이 활성

VIII – 디바이스 전원 관리 정보 지정하기 (sleep 프로퍼티)
========================================================

SoC 상의 디바이스들은 그 디바이스의 자체 레지스터 블록으로부터 떨어져나가는
저-전력 상태로 디바이스를 두는 메카니즘을 종종 갖고 있습니다. 어떤 때는,
이 정보가 cell-index 프로퍼티가 꽤 설명할 수 있는 것보다 좀 복잡합니다.
그래서 각 이런 형태의 개별 디바이스는 이들 연결을 서술하는 “sleep”
프로퍼티를 포함할 것 입니다.

sleep 프로퍼티는 하나 또는 그 이상의 각각이 슬립 컨트롤러로의 phandle,
그 뒤에 0 또는 그 이상의 셀의 컨트롤러에-따른(contrller-specific)
슬립 지시자로 구성된, 슬립 자원들로 구성됩니다.

가능한 저전력 모드가 무슨 종류인지의 의미들은 슬립 컨트롤러에 의해
정의됩니다. 지원될 저전력 모드의 타입 예제는:

 – 다이내믹(Dynamic): 디바이스가 아무때나 꺼지고 켜짐.
 – 시스템 서스펜드(System Suspend): 디바이스가 꺼지거나 시스템 서스펜드
   동안 깬 상태로 남아있도록 요청할 것입니다.
 – 퍼머넌트(Permanent): 디바이스가 영원히 꺼집니다(다음 하드 리셋 전까지)

어떤 디바이스는 각각 다른 것들과 클럭 도메인을 공유할 것 입니다. 그런 것들은
디바이스가 사용 중인 것이 없을 때에만 서스펜드 상태가 됩니다. 정당할 때는,
이들 노드들이 버스가 sleep 프로퍼티를 갖는 곳에서 가상 버스 상에 있어야만
합니다. 클럭 도메인이 이런 형태로 꽤 그룹 지어진 디바이스들 간에 공유되면,
(그 필요성이 나타나기 전까지 표준화된 슬립-맵을 정의하는 것을 기다려야 하는
것을 제외하면, 인터럽트 넥서스와 유사하게)가상 슬립 컨트롤러를 생성해야
합니다.

부록 A – MPC8540의 예제 SOC 노드
================================

        soc@e0000000 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = “fsl,mpc8540-ccsr”, “simple-bus”;
                device_type = “soc”;
                ranges = <0x00000000 0xe0000000 0x00100000>
                bus-frequency = <0>;
                interrupt-parent = <&pic>;

                ethernet@24000 {
                        #address-cells = <1>;
                        #size-cells = <1>;
                        device_type = “network”;
                        model = “TSEC”;
                        compatible = “gianfar”, “simple-bus”;
                        reg = <0x24000 0x1000>;
                        local-mac-address = [ 0x00 0xE0 0x0C 0x00 0x73 0x00 ];
                        interrupts = <0x29 2 0x30 2 0x34 2>;
                        phy-handle = <&phy0>;
                        sleep = <&pmc 0x00000080>;
                        ranges;

                        mdio@24520 {
                                reg = <0x24520 0x20>;
                                compatible = “fsl,gianfar-mdio”;

                                phy0: ethernet-phy@0 {
                                        interrupts = <5 1>;
                                        reg = <0>;
                                        device_type = “ethernet-phy”;
                                };

                                phy1: ethernet-phy@1 {
                                        interrupts = <5 1>;
                                        reg = <1>;
                                        device_type = “ethernet-phy”;
                                };

                                phy3: ethernet-phy@3 {
                                        interrupts = <7 1>;
                                        reg = <3>;
                                        device_type = “ethernet-phy”;
                                };
                        };
                };

                ethernet@25000 {
                        device_type = “network”;
                        model = “TSEC”;
                        compatible = “gianfar”;
                        reg = <0x25000 0x1000>;
                        local-mac-address = [ 0x00 0xE0 0x0C 0x00 0x73 0x01 ];
                        interrupts = <0x13 2 0x14 2 0x18 2>;
                        phy-handle = <&phy1>;
                        sleep = <&pmc 0x00000040>;
                };

                ethernet@26000 {
                        device_type = “network”;
                        model = “FEC”;
                        compatible = “gianfar”;
                        reg = <0x26000 0x1000>;
                        local-mac-address = [ 0x00 0xE0 0x0C 0x00 0x73 0x02 ];
                        interrupts = <0x41 2>;
                        phy-handle = <&phy3>;
                        sleep = <&pmc 0x00000020>;
                };

                serial@4500 {
                        #address-cells = <1>;
                        #size-cells = <1>;
                        compatible = “fsl,mpc8540-duart”, “simple-bus”;
                        sleep = <&pmc 0x00000002>;
                        ranges;

                        serial@4500 {
                                device_type = “serial”;
                                compatible = “ns16550”;
                                reg = <0x4500 0x100>;
                                clock-frequency = <0>;
                                interrupts = <0x42 2>;
                        };

                        serial@4600 {
                                device_type = “serial”;
                                compatible = “ns16550”;
                                reg = <0x4600 0x100>;
                                clock-frequency = <0>;
                                interrupts = <0x42 2>;
                        };
                };

                pic: pic@40000 {
                        interrupt-controller;
                        #address-cells = <0>;
                        #interrupt-cells = <2>;
                        reg = <0x40000 0x40000>;
                        compatible = “chrp,open-pic”;
                        device_type = “open-pic”;
                };

                i2c@3000 {
                        interrupts = <0x43 2>;
                        reg = <0x3000 0x100>;
                        compatible  = “fsl-i2c”;
                        dfsrr;
                        sleep = <&pmc 0x00000004>;
                };

                pmc: power@e0070 {
                        compatible = “fsl,mpc8540-pmc”, “fsl,mpc8548-pmc”;
                        reg = <0xe0070 0x20>;
                };
        };

[Linux:Kernel] 원형 버퍼(Circular Buffer)

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).

      =========
      원형 버퍼
      =========
작성: David Howells <dhowells@redhat.com>
      Paul E. McKenney <paulmck@linux.vnet.ibm.com>
번역: 양정석 <dasomoli@gmailREMOVETHIS.com>
리눅스는 원형 버퍼를 구현하는데 사용할 수 있는 많은 기능을 제공합니다.
다음의 두가지 셋이 있습니다:
 (1) 2의 거듭제곱 크기의 버퍼에 대한 정보를 결정하기 위한 편리한 함수들
 (2) 버퍼 안의 객체의 생산자와 소비자가 하나의 락(lock)을 공유하지 않기를
     원할 때를 위한 메모리 장벽
이들 기능을 사용하기 위해서, 아래에서 논의되는 것처럼, 적어도 하나의 생산자와
하나의 소비자가 있어야 합니다. 여러 생산자를 처리하는 것도 그들을 연속적으로
일렬로 늘어놓는 것으로, 여러 소비자를 처리하는 것도 그들을 연속적으로 일렬로
늘어놓는 것으로써 처리 가능합니다.
내용:
 (*) 원형 버퍼가 뭔가요?
 
 (*) 2의 거듭제곱 버퍼 측정.
 (*) 원형 버퍼와 함께 메모리 장벽 사용하기
     – 생산자.
     – 소비자.
===================
원형 버퍼가 뭔가요?
===================
가장 먼저, 원형 버퍼가 뭘까요? 원형 버퍼는 그 안에 두가지 인덱스가 있는 유한한
크기의 고정된 버퍼입니다:
 (1) ‘헤드(head)’ 인덱스 – 생산자가 항목을 버퍼로 집어넣는 곳
 (2) ‘테일(tail)’ 인덱스 – 소비자가 다음 항목을 버퍼에서 찾는 곳
일반적으로 테일 포인터가 헤드 포인터와 같을 때, 그 버퍼는 빈 것입니다; 그리고 그
버퍼는 헤드 포인터가 테일 포인터보다 하나 작을 때, 가득 찬 것입니다. 
헤드 인덱스는 아이템들이 추가될 때, 테일 인덱스는 아이템들이 제거될 때 증가합니다.
테일 인덱스는 헤드 인덱스를 절대로 넘어설 수 없고, 두 인덱스 모두 그들이 버퍼의
끝에 다다랐을 때, 0으로 다시 돌아와야 합니다. 그래서 무한한 양의 데이터가
그 버퍼를 통해 흐를 수 있습니다.
일반적으로 항목들은 모두 같은 단위 크기이지만, 아래 테크닉을 사용하는 것이
엄격하게 요구되지는 않습니다. 인덱스들은 여러 항목이나 가변 크기의 항목들이
두 인덱스 모두 다른 것을 추월하지 않도록 제공되는 그 버퍼 안으로 포함된다면,
1보다 더 많이 증가할 수 있습니다. 그러나 그 구현자는 한 단위 크기 이상의
한 부분은 버퍼의 끝을 돌 수 있고, 두 세그먼트로 쪼개질 수 있기 때문에 조심해야
합니다.
=======================
2의 거듭제곱 버퍼 측정
=======================
제멋대로인 크기의 원형 버퍼의 사용하거나 남아있는 양의 계산은 보통 나머지
(나누기) 명령의 사용을 필요로 하는 느린 동작이 됩니다. 그러나 버퍼가 2의
거듭제곱 크기라면, 훨씬 빠른 비트-앤드(bitwise-AND) 명령을 대신 사용할 수
있습니다.
리눅스는 2의 거듭 제곱 원형 버퍼를 처리하기 위한 매크로 셋을 제공합니다. 이들은
다음을 통해 사용할 수 있습니다:
#include <linux/circ_buf.h>
그 매크로들은:
  (*) 버퍼의 남은 양 측정:
CIRC_SPACE(head_index, tail_index, buffer_size);
     이것은 항목들이 넣어질 수 있는 그 버퍼[1] 안에 남은 공간의 양을 반환합니다.
 (*) 버퍼 안에 최대로 연이어 붙어있는 공간 측정:
CIRC_SPACE_TO_END(head_index, tail_index, buffer_size);
     이것은 항목들이 다시 그 버퍼의 처음으로 돌아가는 것 없이 즉시 삽입되어질
     수 있는 그 버퍼[1] 안에 남은 연이은 공간의 양을 반환합니다.
 (*) 버퍼의 사용량 측정:
CIRC_CNT(head_index, tail_index, buffer_size);
     이것은 현재 버퍼[2]를 사용하는 항목 수를 반환합니다
 (*) 처음으로 돌아가는 것이 없을 때(non-wrapping)의 버퍼 사용량 측정:
CIRC_CNT_TO_END(head_index, tail_index, buffer_size);
     이것은 다시 그 버퍼의 처음으로 돌아가는 것 없이 뽑아 낼 수 있는 연이은
     항목들[2]의 수를 반환합니다.
이들 매크로 각각은 명목상으로 0 에서 buffer_size-1 사이의 값을 반환할 것입니다.
그러나:
 [1] CIRC_SPACE*() 들은 생산자 안에서 사용되도록 의도되었습니다. 생산자에게
     그들은 생산자가 헤드 인덱스를 제어하기 때문에 하한값을 반환할 것입니다.
     그러나 소비자는 여전히 다른 CPU 상에서 그 버퍼를 감소시키고, 테일 인덱스를
     옮기고 있을 수 있습니다.
     생산자가 그 공간을 바쁘게 감소시킬 수 있기 때문에, 소비자에게 그것은
     상한값을 보여줄 것입니다.
 [2] CIRC_CNT*() 들은 소비자 안에서 사용되도록 의도되었습니다. 소비자에게
     그들은 소비자가 테일 인덱스를 제어하기 때문에 하한값을 반환할 것입니다.
     그러나 생산자는 여전히 다른 CPU 상에서 그 버퍼를 채우고, 헤드 인덱스를
     옮기고 있을 수 있습니다.
     소비자가 그 버퍼를 바쁘게 비울 수 있기 때문에, 생산자에게 그것은 상한값을
     보여줄 것입니다.
 [3] 서드 파티에게는, 생산자와 소비자가 보여질 수 있게 되어감에 의해, 그
     인덱스들에 쓰는 순서는 그들이 독립적이고, 다른 CPU 상에서 생성될 있기 때문에
     보장될 수 없습니다. 그래서 이런 상황에서의 결과는 그저 추정이 될 것이고, 아예
     틀릴 수도 있습니다.
=======================================
원형 버퍼와 함께 메모리 장벽 사용하기
=======================================
원형 버퍼와 함께 메모리 장벽을 사용함으로써, 여러분은 다음을 위한 욕구를
피할 수 있습니다.
 (1) 그 버퍼의 양 끝으로의 접근을 다스리기 위한 단일 락(lock) 사용, 그래서
     그 버퍼가 동시에 채우고 비울 수 있는; 그리고
 (2) 어토믹(atomic) 카운터 연산 사용
이를 위한 두 편이 있습니다: 그 버퍼를 채우는 생산자, 그를 비우는 소비자.
어느 한번에 하나만 버퍼를 채워야 하고, 어느 한번에 하나만 버퍼를 비워야 합니다만,
두 편은 동시에 수행할 수 있습니다.
생산자
——
생산자는 이처럼 보일 것입니다:
spin_lock(&producer_lock);
unsigned long head = buffer->head;
unsigned long tail = ACCESS_ONCE(buffer->tail);
if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
/* 버퍼로 아이템 하나를 넣어라 */
struct item *item = buffer[head];
produce_item(item);
smp_wmb(); /* 헤드를 증가시키기 전에 항목을 넣어라 */
buffer->head = (head + 1) & (buffer->size – 1);
/* wake_up() 은 어느 하나가 깨기 전에 헤드가 제출됐음을 확인할
* 것이다 */
wake_up(consumer);
}
spin_unlock(&producer_lock);
이는 CPU가 새로운 항목의 내용을 헤드 인덱스가 소비자에게 사용가능하게 만들기
전에 반드시 쓰여져야 한다는 것을 지시할 것이고, 그 후 그 CPU가 소비자가 깨기
전에 바뀐 헤드 인덱스가 쓰여져야만 함을 지시합니다.
wake_up() 이 꼭 사용하는 그 메카니즘일 필요는 없지만, 만약 상태 변경이 일어난다면,
사용되는 아무 것이나 헤드 인덱스의 갱신과 소비자의 상태 변경 사이에 반드시 한번의
(쓰기) 메모리 장벽을 보장해야 함을 명심하세요.
소비자
——
소비자는 이처럼 보일 것입니다:
spin_lock(&consumer_lock);
unsigned long head = ACCESS_ONCE(buffer->head);
unsigned long tail = buffer->tail;
if (CIRC_CNT(head, tail, buffer->size) >= 1) {
/* 그 인덱스에 있는 내용을 읽기 전에 인덱스를 읽어라 */
smp_read_barrier_depends();
/* 버퍼로부터 하나의 항목을 꺼내라 */
struct item *item = buffer[tail];
consume_item(item);
smp_mb(); /* 테일을 증가시키기 전에 서술자 읽기를 끝내라 */
buffer->tail = (tail + 1) & (buffer->size – 1);
}
spin_unlock(&consumer_lock);
이는 CPU가 새로운 항목을 읽기 전에 그 인덱스가 올라갔음을 확인할 것을 지시할
것이고, 그 후 CPU가 그 항목을 지울 새로운 테일 포인터를 쓰기 전에 그 항목
읽기를 끝냈음을 확인하도록 합니다.
반대 인덱스를 읽기 위한 두 알고리듬 안에 ACCESS_ONCE() 의 사용에 주의하세요.
이것은 컴파일러가 그들의 캐시된 값-어떤 컴파일러는 smp_read_barrier_depends()를
가로질러 수행하는-을 버리고 재로딩하는 것을 막습니다. 여러분이 반대 인덱스가
한 번만 사용될 거라고 알 수 있다면, 이것이 엄격히 필요하지는 않습니다.
=============
더 읽을 거리
=============
리눅스의 메모리 장벽 기능에 대한 설명을 위해 Documentation/memory-barriers.txt도
보세요.

[Linux] vmlinux -> Image

명령어만 덜렁 써놓기 뭐해서 설명을 덧붙인다.
가끔 undefined instrunction 예외가 날 때, 코드 메모리의 이상 여부를 확인해야 할 때가 있다.
커널은 알다시피 zImage를 Decompressed 하여 메모리상에 올린 후 실행하는데, 그 Decompressed Image가 Image 이다. Image는 커널의 빌드 과정에서 아래와 같이 objcopy를 이용해서 만든다. Makefile 을 참고해보면 알 수 있을 것이다. ARM의 경우는 arch/arm/boot/ 와 그 아래의 Makefile을 살펴보면 된다.
Android JB MR1의 경우 아래 경로의 prebuilt 된 툴체인을 사용한다.

prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin/arm-eabi-objcopy -O binary -R .comment -S vmlinux arch/arm/boot/Image

나온 Image를 Trace32로 실제 커널 메모리를 dump 해서 Binary Diff 해보면 메모리의 H/W적인 이상여부나 코드 메모리를 어디서 건드려서 깨지진 않았는지 확인해 볼 수 있다.
커널 프로그램이 물리메모리 0x40008000과 같은 주소에 로딩되므로, 특정 모듈이나 특정 루틴의 경우 objcopy로 걸러낸 후, 해당 주소의 프로그램을 실제 메모리에서 offset을 뺀 값으로 Image파일에서 찾아야 한다.

Windows에서는 HxD와 같은 툴로 Binary Diff 할 수 있다. Beyond compare를 써도 되고(근데 유료라는 점)…

[Linux] ARM 리눅스 부팅(Booting ARM Linux)

ARM 리눅스 부팅

원문 : http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html
번역 : 양정석(dasomoli@gmailREMOVETHIS.com)

Vincent Sanders

Review and advice, large chunks of the ARM Linux kernel, all around good guy: Russell King

Review, advice and numerous clarifications.: Nicolas Pitre

Review and advice: Erik Mouw, Zwane Mwaikambo, Jeff Sutherland, Ralph Siemsen, Daniel Silverstone, Martin Michlmayr, Michael Stevens, Lesley Mitchell, Matthew Richardson

Review and referenced information (see bibliography): Wookey

  • This document is released under a GPL licence.

  • All trademarks are acknowledged.

While every precaution has been taken in the preparation of this article, the publisher assumes no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.

2004-06-04

Revision History
Revision 1.00 10th May 2004 VRS

Initial Release.

Revision 1.10 4th June 2004 VRS
Update example code to be more complete.
Improve wording in places, changes suggested by Nicolas Pitre.
Update Section 2, “Other bootloaders”.
Update acknowledgements.

차례

1. 이 문서에 관해서 
2. 다른 부트로더
3. 개요
4. 시스템의 메모리 설정하기
5. 커널 이미지 로딩

6. 램디스크 로딩

7. 콘솔 초기화
8. 커널 파라미터
9. ARM 리눅스 머신 타입
 얻기
10. 커널 시작
A. 태그 레퍼런스
B. 완전한 예제
참고문헌

요약

이 문서는 ARM 리눅스 커널을 시작하기 위한 부트 로더의 필요사항과 절차를 구현 가이드, 예제와 함께 명확히 간결한 용어로 정의합니다.

1. 이 문서에 관해서

이 문서는 2.4.18 이후의 커널이 사용하는 “새로운” 부팅 절차를 설명합니다. 이전의 “struct” 메소드는 사용하지 마세요.

이 문서는 광범위한 종류의 출처(참고문헌 참고)와 저자들로부터의 정보를 포함하고 있습니다. 메인테이너들 또는 ARM 리눅스 메일링 리스트 상에 질문하기 전에 이 출처들을 찾아보는 것이 좋습니다. 이 분야의 대부분의 것들은 이전에 반복적으로 문의 및 답변되었고, 만약 여러분이 기본적인 조사도 하지 않았다면 여러분은 무시될 가능성이 큽니다.

추가적으로 이 문서에서 제공하는 가이드를 따르고, 커널을 시작하는 어셈블러의 모든 미묘한 차이를 이해하는 개발자가 될 필요는 없다는 것을 알아두세요. 또한 대부분의 부팅 문제는 여기 있는 코드와 상관없는 경우가 많았고, 많은 사람들이 말하는 코드는 매우 까다롭고 그 문제에 대해 간파할 수 없는 경우가 많았습니다.

2. 다른 부트로더

새 부트 로더의 작성을 시작하기 전에 개발자들은 이미 존재하는 부트 로더가 적절하지는 않은지 한번 생각해보는 것이 좋습니다. 간단한 GPL 부트 로더에서부터 상용으로 제공하는 것까지 다양한 영역의 부트 로더 예제가 있습니다. 여기서 다음 작은 표를 제공합니다만 참고문헌에 있는 문서는 더 많은 솔루션을 제공합니다.

표 1. 부트 로더

이름 URL 설명
Blob Blob bootloader SA11x0 (StrongARM)을 위한 GPL 부트 로더
Bootldr Bootldr GPL과 GPL 아닌 버전을 모두 제공하는 핸드헬드 디바이스에서 주로 사용하는 부트 로더
Redboot Redboot eCos 라이센스의 레드햇 부트로더
U-Boot U-Boot 몇 몇 CPU를 지원하는 GPL universal bootloader
ABLE ABLE bootloader 편리한 기능의 상용 부트로더

3. 개요

ARM 리눅스는 시스템을 초기화하는 특정 머신에서만 동작하는 작은 코드 없이는 시작될 수 없습니다. 몇몇 부트로더는 더 많은 추가 기능을 제공하긴 하지만, ARM 리눅스는 이 작은 일을 하는 부트 로더가 필요합니다. 최소한의 필요사항은 다음과 같습니다:

시스템의 메모리 설정하기.
정확한 메모리 주소에 커널 이미지 로딩.
필요하면 정확한 메모리 주소에 램디스크 로딩.
커널로 전달할 부트 파라미터 초기화.
ARM 리눅스 머신 타입 얻기
적당한 레지스터 값으로 커널로 진입.

부트로더는 일반적으로 이런 기본적인 작업들 뿐만 아니라 시리얼 또는 비디오 콘솔을 초기화해야 합니다. 사실 시리얼 포트는 거의 대부분의 설정을 위해 필수사항으로 생각됩니다.

위의 각 단계를 다음 절들에서 설명합니다.

4. 시스템의 메모리 설정하기

부트 로더는 커널이 시스템의 임시 저장 장소로 사용할 모든 램을 찾아 초기화해야 합니다. 이는 특정 머신에서만 동작하는 방법(모든 램의 크기와 위치를 찾는 내부 알고리즘을 사용하거나, 머신에 달린 램에 관한 정보를 사용하거나, 또는 부트 로더 설계자 관점의 다른 방법 등)으로 수행됩니다.

어떠한 경우에도 모든 셋업은 부트 로더에 의해서 수행되어야 합니다. 커널은 시스템 안의 램의 셋업 또는 설정에 대해서 부트 로더가 제공하는 것 외에는 아무 것도 몰라야 합니다. 커널 안의 machine_fixup()의 사용은 이런 것을 하기 위한 것이 절대 아닙니다. 이 부분에서의 커널과 부트 로더 간의 책임 구별은 명확합니다.

물리 메모리 배치는 ATAG_MEM 파라미터를 사용해서 커널로 전달됩니다. 메모리가 가장 적게 분리되어 있는 것이 좋긴 하지만, 완전히 연속적일 필요는 없습니다. 여러 메모리 지역을 위한 여러개의 ATAG_MEM 블록도 상관없습니다. 연속된 물리 메모리 지역이 있다면 커널은 그 블록들을 합칠 겁니다.

부트 로더는 또한 linux/Documentation/kernel-parameters.txt 에 완벽히 문서화되어 있는 ‘mem=’ 파라미터를 사용해서 커널의 커맨드라인으로 메모리를 조작할 수도 있습니다.

커널 커맨드 라인 ‘mem=’ 는 정의된 메모리 영역의 물리 메모리 위치와 크기를 위해서 mem=<size>[KM][,@<phys_offset>] 문법을 가집니다. mem= 파라미터는 각기 다른 곳의 여러개의 연속되지 않은 메모리 블록을 나타내기 위해 여러 번 쓸 수 있습니다.

5. 커널 이미지 로딩

커널 이미지는 커널 빌드 과정에서 생성된 압축되지 않은 “Image” 파일 또는 압축된 zImage 파일입니다.

압축되지 않은 Image 파일은 식별 가능한 매직 넘버를 포함하고 있지 않아서 일반적으로 사용되지 않습니다. 압축된 zImage 형식이 거의 보편적으로 사용됩니다.

zImage 파일은 매직 넘버 뿐만 아니라 여러 장점을 갖고 있습니다. 일반적으로 이미지의 압축 해제는 외부 저장 장치를 읽는 것보다 빠릅니다. 압축 해제 실패의 결과로 이미지의 무결성도 보장할 수 있습니다. 커널은 일반적인 외부 압축 방법보다 더 나은 결과를 알 수 있는 그 내부 구조와 상태에 대한 정보를 가집니다.

zImage는 그 앞 부분 근처에 유용한 정보와 매직 넘버를 가집니다.

표 2. zImage head 코드 안의 유용한 정보

zImage안의 오프셋 설명
0x24 0x016F2818 ARM 리눅스 zImage를 나타내는데 사용하는 매직 넘버
0x28 시작 주소 zImage가 시작하는 주소
0x2C 끝 주소 zImage가 끝나는 주소

시작과 끝 오프셋은 압축된 이미지의 크기(크기 = 끝 – 시작)를 결정하는데 사용됩니다. 몇몇 부트 로더들은 어떤 데이터가 커널 이미지에 추가된다면 이 정보를 사용합니다. 이 데이터는 일반적으로 초기 램디스크(initrd)를 위해 사용됩니다. 시작 주소는 일반적으로 zImage 코드가 위치에 무관하므로 0 입니다.

zImage 코드의 이용 가능한 주소 공간 안의 어떤 곳에도 로딩될 수 있는 위치에 무관한 코드(Position Independent Code – PIC) 입니다. 압축해제 된 후의 커널 크기는 최대 4 메가 바이트 입니다. 이는 bootpImage 타겟이 사용되었을 때의 initrd를 포함한 제약 사항입니다.

주의

zImage가 어느 곳이든 위치할 수 있다 하더라도 주의해야 합니다. 압축된 커널을 시작하는 것은 이미지를 압축해제할 추가적인 메모리를 필요로 합니다. 이 공간은 확실한 제약사항입니다.

zImage 압축해제 코드는 압축된 데이터를 덮어쓰지 않도록 확인할 겁니다. 커널이 어떤 충돌 같은 것을 발견한다면, 압축된 zImage 데이터 바로 뒤에 이미지를 압축 해제하고, 압축 해제 후에 커널을 재위치시킬 겁니다. zImage가 로딩된 메모리 영역은 그 뒤에 4 메가 바이트보다 큰 공간을 가져야 한다는 것, 즉 ZRELADDR로서 같은 4 메가 바이트 뱅크에 위치한 커널이 기대하는대로 동작하지 않는 것을 명확히 알 수 있습니다.

메모리 안에 어느 곳이든 zImage가 위치할 수 있지만 관례상 물리 메모리의 베이스에서 0x8000 (32K) 오프셋에 로딩됩니다. 이 것은 파라미터 블록이 일반적으로 위치하는 0x100 오프셋, Zero page exception 벡터와 페이지 테이블을 위한 공간을 남겨둡니다. 이 관례는 매우 일반적입니다.

6. 램디스크 로딩

램디스크는 많은 시스템상에서 일반적인 요구사항입니다. 그 것은 다른 드라이버나 설정에 접근할 필요없이 사용가능한 루트 파일 시스템을 제공합니다. 완전한 세부사항은  linux/Documentation/initrd.txt 에서 볼 수 있습니다.

ARM 리눅스가 램디스크를 얻는 방법은 두가지입니다. 첫번째는 빌드 할 때 램디스크를 가져와서 zImage 에 덧붙인 target bootpImage를 따로 빌드하는 겁니다. 이 방법은 부트 로더와의 상호 약속된 것이 없어도 된다는 장점이 있는 반면에 커널 빌드 과정에서 램디스크가 위치할 물리 주소를 (INITRD_PHYS 정의를 사용해서) 알려 주어야 한다는 단점이 4있습니다. 압축 해제된 커널과 initrd의 크기는 4 메가 바이트로 제한됩니다. 이 제한 사항 때문이 이 타겟은 실제로는 별로 사용되지 않습니다.

두번째 방법은 더 널리 사용되는 방법으로 부트로더가 어떤 저장 장치에서 램디스크를 받아 메모리의 지정된 위치에 위치시키는 것입니다. 이 위치는 커널에게 ATAG_INITRD2 와 ATAG_RAMDISK를 사용해서 전달됩니다.

관례적으로 initrd는 물리 주소 베이스의 8메가 바이트에 위치합니다. 위치된 곳이 어디 건간에 부팅 후에 램디스크를 실제 램디스크로 압축해제 하기 위한 충분한 메모리, 즉 zImage + 압축 해제된 zImage + initrd + 압축 해제된 램디스크를 위한 메모리가 있어야 합니다. 압축된 램디스크 메모리는 압축이 해제되고 나면 할당 해제될 겁니다. 램디스크의 위치 제한은 다음과 같습니다: 

한 개의 메모리 영역안에 있어야 합니다(다른 ATAG_MEM 파라미터에 의해 정의된 다른 영역에 걸쳐 있으면 안됩니다).
한 페이지 경계(일반적으로 4k)로 맞추어져 있어야 합니다.
zImage head 코드가 커널을 압축해제 하기 위해서 사용하는 메모리와의 충돌이 없어야 합니다, 아니면 체크 없이 덮어쓰게 될 것입니다.

7. 콘솔 초기화

콘솔은 시스템이 초기화될 때 커널이 무엇을 수행하는 지를 볼 수 있는 방법으로 매우 권장됩니다. 일반적인 경우인 비디오 프레임 버퍼 드라이버나 시리얼 드라이버 같은 적절한 드라이버로 어떤 입출력 장치도 사용될 수 있습니다. ARM 리눅스가 실행되는 시스템은 거의 항상 시리얼 콘솔 포트를 가지고 있습니다.

부트 로더는 부트로더 상에서 한 개의 시리얼 포트를 초기화하고 쓸 수 있도록 만들어야 합니다. 이 것은 그 포트를 사용하기 위한 하드웨어적인 파워 관리 같은 것들을 포함합니다. 이 것은 커널 시리얼 드라이버가 (일반적으로 디버깅 목적이나 타겟과의 통신 용도로 사용되는) 커널 콘솔로 사용되는 시리얼 포트를 자동으로 찾을 수 있도록 합니다.

아니면 부트로더는 linux/Documentation/kernel-parameters.txt에 설명된 Serial format 옵션과 포트를 지정하는 tagged list를 통해 이에 관련된 ‘console=’ 옵션을 커널로 전달할 수 있습니다.

8. 커널 파라미터

부트로더는 커널로 수행한 셋업, 시스템의 메모리의 크기와 영역, 그리고 필요시에는 다른 여러 값들을 설명하는 파라미터를 넘겨야 합니다.

Tagged list는 다음 제약 사항을 따라야 합니다.

리스트는 램에 저장 되어져야만 하고 커널 압축 해제기나 initrd 처리가 덮어쓰지 않는 메모리 영역에 위치해야만 합니다. 권장되는 곳은 RAM의 첫번째 16KiB 안이고, 일반적으로 물리 RAM의 시작에서 (Zero page exception 벡터를 피하기 위해서) 0x100 더하여 진 곳에 위치합니다.
Tagged list의 물리 주소는 커널로의 진입 시점에 R2 레지스터에 저장되어져 있어야 합니다. 예전에는 이 것이 필수적이지는 않았고, 커널이 물리 램의 시작에서 0x100 더한 곳을 고정값으로 사용했었습니다. 앞으로는 이렇게 하지 않을 겁니다.
리스트는 커널이 초기 페이지 테이블을 생성하는 0x4000 경계까지 확장되면 안됩니다.
리스트는 (권장되는 위치를 사용하지 않는다면) word 크기(32비트, 4바이트)로 맞추어져야 합니다.
리스트는 ATAG_CORE로 시작해서 ATAG_NONE으로 끝나야 합니다.
리스트는 ATAG_MEM을 적어도 하나는 포함해야 합니다.

리스트의 각 태그는 양수 32비트로 된 size와 tag, 두개의 값을 포함하는 헤더와 그 태그의 값들로 구성됩니다.

struct atag_header {
        u32 size; /* legth of tag in words including this header */
        u32 tag;  /* tag value */
};

데이터를 갖지 않는 ATAG_NONE와 필요하면 데이터를 쓰는 ATAG_CORE를 제외한 각 태그 헤더에는 그 태그와 관련된 데이터가 뒤따릅니다. 데이터의 크기는 헤더 안의 size 필드에 의해 결정되는데 이 값은 헤더 크기를 포함하기 때문에 최소 사이즈는 2입니다. ATAG_NONE은 size 필드를 0으로 셋팅해야 하는 유일한 태그입니다.

한 태그에는 덧붙여지는 정보 크기를 제공하는 필수 구조체 뒤에 추가 데이터를 포함할 겁니다. 이 것은 추후에 커널로 제공하는 데이터를 확장하기 위해서 사용될 수 있습니다. 예를 들면 부트 로더는 ATAG_SERIAL 안에 추가적인 제품 일련 번호 정보를 제공하여 이를 위해 수정된 커널이 이를 읽어갈 수 있도록 할 수 있습니다.

파라미터 리스트의 태그의 순서는 중요하지 않습니다. 중복된 태그에 대한 해석은 태그에 따라 다를 수는 있지만 필요한 만큼 여러번 포함 될 겁니다.

각각의 태그의 데이터는 부록 A. 태그 레퍼런스 절에서 설명합니다.

표 3. 유용한 태그의 목록

태그 이름 크기 설명
ATAG_NONE 0x00000000 2 리스트의 끝으로 사용하는 빈 태그
ATAG_CORE 0x54410001 5 (비었다면 2) 리스트의 시작으로 사용하는 첫번째 태그
ATAG_MEM 0x54410002 4 메모리의 물리 영역을 설명
ATAG_VIDEOTEXT 0x54410003 5 VGA text 디스플레이를 설명
ATAG_RAMDISK 0x54410004 5 커널에서 램디스크가 어떻게 사용되는지를 설명
ATAG_INITRD2 0x54420005 4 메모리에 램디스크 이미지가 어디에 있는지를 설명
ATAG_SERIAL 0x54410006 4 64비트의 제품 일련 번호
ATAG_REVISION 0x54410007 3 32비트의 보드 리비전
ATAG_VIDEOLFB 0x54410008 8 vesafb 타입의 프레임 버퍼의 초기값
ATAG_CMDLINE 0x54410009 2 + ((cmdline의 길이 + 3) / 4) 커널로 전달되는 커맨드 라인

구현 목적으로 태그는 다음과 같이 정의될 수 있습니다.

struct atag {
        struct atag_header hdr;
        union {
                struct atag_core         core;
                struct atag_mem          mem;
                struct atag_videotext    videotext;
                struct atag_ramdisk      ramdisk;
                struct atag_initrd2      initrd2;
                struct atag_serialnr     serialnr;
                struct atag_revision     revision;
                struct atag_videolfb     videolfb;
                struct atag_cmdline      cmdline;
        } u;
};

리스트를 만들 필요가 있는 구현을 정의해 본다면 코드는 아마 다음과 유사할 겁니다.

#define tag_next(t)     ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */

static void
setup_core_tag(void * address,long pagesize)
{
    params = (struct tag *)address;         /* Initialise parameters to start at given address */

    params->hdr.tag = ATAG_CORE;            /* start with the core tag */
    params->hdr.size = tag_size(atag_core); /* size the tag */

    params->u.core.flags = 1;               /* ensure read-only */
    params->u.core.pagesize = pagesize;     /* systems pagesize (4k) */
    params->u.core.rootdev = 0;             /* zero root device (typicaly overidden from commandline )*/

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_mem_tag(u32_t start, u32_t len)
{
    params->hdr.tag = ATAG_MEM;             /* Memory tag */
    params->hdr.size = tag_size(atag_mem);  /* size tag */

    params->u.mem.start = start;            /* Start of memory area (physical address) */
    params->u.mem.size = len;               /* Length of area */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_end_tag(void)
{
    params->hdr.tag = ATAG_NONE;            /* Empty tag ends list */
    params->hdr.size = 0;                   /* zero length */
}


static void
setup_tags(void)
{
    setup_core_tag(0x100, 4096);            /* standard core tag 4k pagesize */
    setup_mem_tag(0x10000000, 0x400000);    /* 64Mb at 0x10000000 */
    setup_mem_tag(0x18000000, 0x400000);    /* 64Mb at 0x18000000 */
    setup_end_tag(void);                    /* end of tags */
}

여기 있는 코드가 완전하긴 하지만 이 코드는 파마리터 셋을 위한 절대적으로 최소한의 필요사항만을 나타내고 있고, 앞서 설명된 이 절에서의 컨셉을 보여주기 위한 용도로 작성되었습니다. 실제 부트로더는 아마 추가적인 값들을 전달하고 고정값을 사용하는 것보다는 실제 시스템 메모리에서 알아낸 값을 사용할 것입니다. 더 완전한 예제는 부록 B. 완전한 예제에서 볼 수 있습니다.

9. ARM 리눅스 머신 타입 얻기

부트 로더가 제공할 필요가 있는 추가 정보는 각 ARM 시스템에서 MACH_TYPE으로 불리는 간단하고 유일한 수, 머신 타입 뿐입니다.

머신 타입 넘버는 ARM 리눅스 웹사이트의 Machine Registry 페이지에서 획득할 수 있습니다. 머신 타입은 가능한한 프로젝트 초기에 획득해야 합니다. 커널이 그 스스로(머신 정의값들 등)를 포팅하는 것은 수많은 결과를 만들 수 있고 이후에 정의값들을 바꾸는 것은 수많은 원치 않는 문제를 만들 수 있습니다. 이 값들은 커널 소스 (linux/arch/arm/tools/mach-types) 안에 정의된 리스트에 의해 표현됩니다.

부트 로더는 어떤 방법으로든 실행될 머신의 타입을 획득해야 합니다. 그 방법이 하드 코딩된 값이던 연결된 하드웨어에서 탐색하는 어떤 알고리즘이던 상관없습니다. 그 구현 방법은 문서의 범위를 넘어섭니다. 

10. 커널 시작하기

부트 로더가 다른 모든 단계를 수행했다면 CPU 셋팅에 정확한 값들로 커널의 실행을 시작해야 합니다.

진입 제약 사항은:

CPU 는 IRQ 와 FIQ 모두 꺼진 상태로 SVC (supervisor) 모드에 있어야 합니다.
MMU는 반드시 꺼져 있어야, 즉 물리 램에서 주소를 변환하지 않는 상태로 코드가 실행되어야 합니다.
데이터 캐시는 반드시 꺼져 있어야 합니다.
인스트럭션 캐시는 켜져 있건 꺼져있건 상관없습니다.
CPU 레지스터 0 은 0 이어야 합니다.
CPU 레지스터 1 은 ARM 리눅스 머신 타입이어야 합니다.
CPU 레지스터 2 는 파라미터 리스트의 물리 주소여야 합니다.

부트 로더는 커널 이미지의 첫번째 명령으로 직접 점프하여 커널 이미지를 호출할 것입니다.

A. 태그 레퍼런스

ATAG_CORE

ATAG_CORE — 리스트의 시작으로 사용되는 시작 태그

0x54410001

크기

5 (빈 데이터면 2)

구조체 멤버

struct atag_core {
        u32 flags;              /* bit 0 = read-only */
        u32 pagesize;           /* systems page size (usually 4k) */
        u32 rootdev;            /* root device number */
};

설명

어떤 부트 로더도 전달해야만 하는 기본 정보를 포함하는 리스트의 시작은 다른 구조가 뒤에 붙지 않는 것을 나타내는 길이 2의 이 태그가 되어야 합니다.


ATAG_NONE

ATAG_NONE — 리스트의 끝으로 사용되는 끝 태그

0x00000000

크기

2

구조체 멤버

없음

설명

이 태그는 리스트의 끝을 나타내는데 사용됩니다. 헤더 안의 size 필드가 (2가 아닌) 0 이 되는 유일한 태그입니다.


ATAG_MEM

ATAG_MEM — 물리 메모리 영역을 설명하는데 사용되는 태그

0x54410002

크기

4

구조체 멤버

struct atag_mem {
        u32     size;   /* size of the area */
        u32     start;  /* physical start address */
};

설명

커널이 사용하는 물리 메모리 영역을 설명합니다.


ATAG_VIDEOTEXT

ATAG_VIDEOTEXT — VGA test 타입 디스플레이를 설명하는 데 사용되는 태그

0x54410003

크기

5

구조체 멤버

struct atag_videotext {
        u8              x;           /* width of display */
        u8              y;           /* height of display */
        u16             video_page;
        u8              video_mode;
        u8              video_cols;
        u16             video_ega_bx;
        u8              video_lines;
        u8              video_isvga;
        u16             video_points;
};

설명


ATAG_RAMDISK

ATAG_RAMDISK — 커널에 의해 램디스크가 어떻게 사용될 지를 설명하는 태그

0x54410004

크기

5

구조체 멤버

struct atag_ramdisk {
        u32 flags;      /* bit 0 = load, bit 1 = prompt */
        u32 size;       /* decompressed ramdisk size in _kilo_ bytes */
        u32 start;      /* starting block of floppy-based RAM disk image */
};

설명

(초기) 램디스크가 커널에 의해 어떻게 설정될지를 설명합니다. 특히 부트로더가 ATAG_INITRD2를 사용해서 전달하는 압축 해제된 초기 램디스크 이미지를 얻을 수 있을 만큼의 크기를 보장받을 수 있습니다.


ATAG_INITRD2

ATAG_INITRD2 — 압축된 램디스크의 물리 위치를 설명합니다.

0x54420005

크기

4

구조체 멤버

struct atag_initrd2 {
        u32 start;      /* physical start address */
        u32 size;       /* size of compressed ramdisk image in bytes */
};

설명

일반적으로 ATAG_RAMDISK와 같이 사용되는 압축된 램디스크 이미지의 위치. 커맨드 라인 파라미터의 ‘root=/dev/ram’으로써 초기 루트 파일 시스템으로 사용될 수 있습니다. 이 태그는 원래 가상 주소를 사용하는 ATAG_INITRD를 대신함으로써 문제가 될 수 있습니다. 모든 새로운 부트 로더는 이 태그를 사용하는 것이 좋습니다.


ATAG_SERIAL

ATAG_SERIAL — 64비트 값의 보드 일련 번호 태그

0x54410006

크기

4

구조체 멤버

struct atag_serialnr {
        u32 low;
        u32 high;
};

설명


ATAG_REVISION

ATAG_REVISION — 보드 리비전 태그

0x54410007

크기

3

구조체 멤버

struct atag_revision {
        u32 rev;
};

설명


ATAG_VIDEOLFB

ATAG_VIDEOLFB — 프레임버퍼 타입 디스플레이의 파라미터를 설명하는 태그

0x54410008

크기

8

구조체 멤버

struct atag_videolfb {
        u16             lfb_width;
        u16             lfb_height;
        u16             lfb_depth;
        u16             lfb_linelength;
        u32             lfb_base;
        u32             lfb_size;
        u8              red_size;
        u8              red_pos;
        u8              green_size;
        u8              green_pos;
        u8              blue_size;
        u8              blue_pos;
        u8              rsvd_size;
        u8              rsvd_pos;
};

설명


ATAG_CMDLINE

ATAG_CMDLINE — 커널로 커맨드 라인을 전달하는데 사용되는 태그

0x54410009

크기

2 + ((cmdline의 길이 + 3) / 4)

구조체 멤버

struct atag_cmdline {
        char    cmdline[1];     /* this is the minimum size */
};

설명

커널로 커맨드 라인 파라미터를 전달하는데 사용됩니다. 커맨드 라인은 NULL로 끝나야 합니다. cmdline의 길이 변수는 끝내는 문자를 포함한 길이여야 합니다.

B. 완전한 예제

여기 있는 것은 이 문서에서 설명하는 모든 정보를 보여주는 간단한 부트 로더에서 작동하는 예제입니다. 실제 부트로더는 더 많은 코드가 필요합니다. 이 예제는 그저 보여주기 위함입니다.

이 예제 코드는 BSD 라이센스로 배포됩니다. 자유롭게 복사하고, 필요하면 사용할 수 있습니다.

/* example.c
 * example ARM Linux bootloader code
 * this example is distributed under the BSD licence
 */

/* list of possible tags */
#define ATAG_NONE       0x00000000
#define ATAG_CORE       0x54410001
#define ATAG_MEM        0x54410002
#define ATAG_VIDEOTEXT  0x54410003
#define ATAG_RAMDISK    0x54410004
#define ATAG_INITRD2    0x54420005
#define ATAG_SERIAL     0x54410006
#define ATAG_REVISION   0x54410007
#define ATAG_VIDEOLFB   0x54410008
#define ATAG_CMDLINE    0x54410009

/* structures for each atag */
struct atag_header {
        u32 size; /* length of tag in words including this header */
        u32 tag;  /* tag type */
};

struct atag_core {
        u32 flags;
        u32 pagesize;
        u32 rootdev;
};

struct atag_mem {
        u32     size;
        u32     start;
};

struct atag_videotext {
        u8              x;
        u8              y;
        u16             video_page;
        u8              video_mode;
        u8              video_cols;
        u16             video_ega_bx;
        u8              video_lines;
        u8              video_isvga;
        u16             video_points;
};

struct atag_ramdisk {
        u32 flags;
        u32 size;
        u32 start;
};

struct atag_initrd2 {
        u32 start;
        u32 size;
};

struct atag_serialnr {
        u32 low;
        u32 high;
};

struct atag_revision {
        u32 rev;
};

struct atag_videolfb {
        u16             lfb_width;
        u16             lfb_height;
        u16             lfb_depth;
        u16             lfb_linelength;
        u32             lfb_base;
        u32             lfb_size;
        u8              red_size;
        u8              red_pos;
        u8              green_size;
        u8              green_pos;
        u8              blue_size;
        u8              blue_pos;
        u8              rsvd_size;
        u8              rsvd_pos;
};

struct atag_cmdline {
        char    cmdline[1];
};

struct atag {
        struct atag_header hdr;
        union {
                struct atag_core         core;
                struct atag_mem          mem;
                struct atag_videotext    videotext;
                struct atag_ramdisk      ramdisk;
                struct atag_initrd2      initrd2;
                struct atag_serialnr     serialnr;
                struct atag_revision     revision;
                struct atag_videolfb     videolfb;
                struct atag_cmdline      cmdline;
        } u;
};


#define tag_next(t)     ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
static struct atag *params; /* used to point at the current tag */

static void
setup_core_tag(void * address,long pagesize)
{
    params = (struct tag *)address;         /* Initialise parameters to start at given address */

    params->hdr.tag = ATAG_CORE;            /* start with the core tag */
    params->hdr.size = tag_size(atag_core); /* size the tag */

    params->u.core.flags = 1;               /* ensure read-only */
    params->u.core.pagesize = pagesize;     /* systems pagesize (4k) */
    params->u.core.rootdev = 0;             /* zero root device (typicaly overidden from commandline )*/

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_ramdisk_tag(u32_t size)
{
    params->hdr.tag = ATAG_RAMDISK;         /* Ramdisk tag */
    params->hdr.size = tag_size(atag_ramdisk);  /* size tag */

    params->u.ramdisk.flags = 0;            /* Load the ramdisk */
    params->u.ramdisk.size = size;          /* Decompressed ramdisk size */
    params->u.ramdisk.start = 0;            /* Unused */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_initrd2_tag(u32_t start, u32_t size)
{
    params->hdr.tag = ATAG_INITRD2;         /* Initrd2 tag */
    params->hdr.size = tag_size(atag_initrd2);  /* size tag */

    params->u.initrd2.start = start;        /* physical start */
    params->u.initrd2.size = size;          /* compressed ramdisk size */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_mem_tag(u32_t start, u32_t len)
{
    params->hdr.tag = ATAG_MEM;             /* Memory tag */
    params->hdr.size = tag_size(atag_mem);  /* size tag */

    params->u.mem.start = start;            /* Start of memory area (physical address) */
    params->u.mem.size = len;               /* Length of area */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_cmdline_tag(const char * line)
{
    int linelen = strlen(line);

    if(!linelen)
        return;                             /* do not insert a tag for an empty commandline */

    params->hdr.tag = ATAG_CMDLINE;         /* Commandline tag */
    params->hdr.size = (sizeof(struct atag_header) + linelen + 1 + 4) >> 2;

    strcpy(params->u.cmdline.cmdline,line); /* place commandline into tag */

    params = tag_next(params);              /* move pointer to next tag */
}

static void
setup_end_tag(void)
{
    params->hdr.tag = ATAG_NONE;            /* Empty tag ends list */
    params->hdr.size = 0;                   /* zero length */
}


#define DRAM_BASE 0x10000000
#define ZIMAGE_LOAD_ADDRESS DRAM_BASE + 0x8000
#define INITRD_LOAD_ADDRESS DRAM_BASE + 0x800000

static void
setup_tags(parameters)
{
    setup_core_tag(parameters, 4096);       /* standard core tag 4k pagesize */
    setup_mem_tag(DRAM_BASE, 0x4000000);    /* 64Mb at 0x10000000 */
    setup_mem_tag(DRAM_BASE + 0x8000000, 0x4000000); /* 64Mb at 0x18000000 */
    setup_ramdisk_tag(4096);                /* create 4Mb ramdisk */ 
    setup_initrd2_tag(INITRD_LOAD_ADDRESS, 0x100000); /* 1Mb of compressed data placed 8Mb into memory */
    setup_cmdline_tag("root=/dev/ram0");    /* commandline setting root device */
    setup_end_tag(void);                    /* end of tags */
}

int
start_linux(char *name,char *rdname)
{
    void (*theKernel)(int zero, int arch, u32 params);
    u32 exec_at = (u32)-1;
    u32 parm_at = (u32)-1;
    u32 machine_type;

    exec_at = ZIMAGE_LOAD_ADDRESS;
    parm_at = DRAM_BASE + 0x100

    load_image(name, exec_at);              /* copy image into RAM */

    load_image(rdname, INITRD_LOAD_ADDRESS);/* copy initial ramdisk image into RAM */

    setup_tags(parm_at);                    /* sets up parameters */

    machine_type = get_mach_type();         /* get machine type */

    irq_shutdown();                         /* stop irq */

    cpu_op(CPUOP_MMUCHANGE, NULL);          /* turn MMU off */

    theKernel = (void (*)(int, int, u32))exec_at; /* set the kernel address */

    theKernel(0, machine_type, parm_at);    /* jump to kernel with register set */

    return 0;
}

참고문헌

Setting R2 correctly for booting the kernel (explanation of booting requirements). Russell M King.

Makefile defines and symbols. Russell M King.

Bootloader guide. Wookey.

Kernel boot order. Russell M King.

Advice for head.S Debugging. Russell M King.

Linux kernel 2.4 startup. Bill Gatliff.

Blob bootloader. Erik Mouw.

Linux Kernel 의 Memory barrier 구현

Linux Kernel의 프로세스 상태 변경 매크로(set_task_state, set_current_state)를 살펴보다가 ARM 아키텍처에서 다음과 같이 구현된 것을 보았다.

include/linux/sched.h

#define set_task_state(tsk, state_value)        \
    set_mb((tsk)->state, (state_value))
#define set_current_state(state_value)        \
    set_mb(current->state, (state_value))

set_mb 매크로는 시스템마다 다르게 구현되어 있는데 ARM 쪽을 따라가보면 다음과 같이 쓰여져 있다.

arch/arm/include/asm/system.h

#define dmb() __asm__ __volatile__ (“” : : : “memory”)

#define smp_mb()    dmb()

#define set_mb(var, value)    do { var = value; smp_mb(); } while (0)

do-while-0 구문에 대해서는 이 글을 참고하도록 하고, memory barrier에 대해서는 이 글을 참고하라. dmb() 의 inline assembly의 구조와 설명은 이 글을 참고하자.

참고된 글을 정리하자면, 명령이 R, W, R, W, R, W 순으로 사용된다면, 이를 하드웨어 혹은 소프트웨어 적으로 R, R, R, W, W, W 순으로 배열하는 등의 최적화를 할 수 있는데, 이 때 명령의 순서를 보장해 주는 역할로써 Memory barrier 라는 것을 구현해서 사용한다. 이는 하드웨어적으로 혹은 소프트웨어적으로 구현되는데 하드웨어적인 방법은 CPU 자체의 명령으로 구현되는 등의 방법이 사용될 수 있고, 소프트웨어적으로 구현될 때 위와 같이 구현될 수 있다.
위 구문은 gcc inline assembly의 확장으로 clobber list에 “memory”를 적어넣어 해당 명령(“” – 아무 명령도 수행하지 않음)을 수행한 후에 변경되는 것이 메모리 타입 저장장치(모든 레지스터, 모든 플래그, 모든 메모리)임을 나타낸다. gcc는 이럴경우 __asm__ __volatile__(“”: : :”memory”) 경계를 넘어가는 최적화 또는 instruction scheduling을 수행하지 않기 때문에 __asm__ __volatile__(“”: : :”memory”)를 사용하면 이전 코드의 수행 완료를 보장할 수 있고 이후 코드가 __asm__ __volatile__(“”: : :”memory”) 이전에 수행되는것을 방지 할수 있다. 별개로 volatile의 경우 읽기 연산에서 메모리에서 한번 읽어온 데이터를 레지스터에 저장해서 사용하는 것이 아닌 사용할 때마다 메모리 참조를 통해 가져오도록 한다.

FTL 중 FAST 기법 정리

멤버십 초기에 FTL에 관련된 과제를 하면서 보았던 FTL 기법 중 FAST에 관련된 것들을 잊어버리기 전에 정리해두고자 한다.

FAST는 FAST의 저자들이 BAST라고 이름붙인 기법의 원리와 문제점을 설명하고 이를 해결하기 위해
완전연관섹터변환(Fully Associative Sector Translation) 방식을 사용하는 것을 말한다. 즉,
BAST에서는 임의쓰기가 동시에 발생할 때 여러 논리블록에 대한 로그블록이 경쟁상태에서 모든 섹터가 사용되지 않고 합병되는
문제점이 있는데 이를 해결하기 위해 각 로그블록의 활용률을 최대한 높임으로써 각 로그블록이 블록 전체의 섹터가 모두 사용된
이후에 소거되도록 한다.

이를 위해서 최초 쓰기 연산시에는 해당 자리에 섹터 데이터를 기록하고, 덮어쓰기 연산시에 아래의 조건들을 검사하여 적절한 연산을 수행한다.

조건 1. (start_lsn mod 32 = 0) & (lbn = (start_lsn div 32) !∈ rw_lbn_set)
조건 2. (lbn = sw_lbn) & (start_lsn mod 32 = sw_sec_num)
조건 3. (lbn = sw_lbn) & (start_lsn mod 32 > sw_sec_num)
조건 4. (lbn = sw_lbn) & (start_lsn mod 32 < sw_sec_num)
조건 5. 조건 1, 2, 3, 4를 모두 만족하지 않는 경우

위의 1~4까지의 조건들은 모두 순차쓰기용 로그블록에 쓰여지는 경우의 조건들이다. rw_set_of_lbn은 임의쓰기용 로그블록
그룹에 기록된 섹터들의 논리블록들의 주소 정보이고, sw_lbn는 순치쓰기용 로그블록에 현재 쓰여지고 있는 논리블록 주소,
그리고 sw_sec_num은 논리 블록에 대해 순차적으로 쓰여진 섹터의 수이다.

조건 1의 경우 순차쓰기용 로그블록의 첫번째 섹터에 기록된다. 이 때, 순차쓰기용 로그블록에 이미 다른 섹터들이 저장되어 있다면 그 블록과 원본 데이터 블록간의 합병 연산이 발생하여 순차쓰기용 로그블록을 소거한 후 기록된다.

조건 2의 경우 순차쓰기용 로그블록에 기록된 데이터의 다음 위치에 추가(append)할 수 있는 쓰기연산이다. 순차쓰기용 로그블록의 모든 섹터가 순차적으로 다 기록된 경우 교환 연산을 한다.

조건 3의 경우 순차쓰기용 로그블록에 연이어쓰여지지는 않지만 기록할 수 있는 쓰기 연산이다. 이 때, 그 중간 섹터가 채워질 확률이 낮기 때문에 합병 연산을 수행한다. 이 때 비어있는 섹터들만 쓰기연산을 수행하여 교환연산을 수행하면 데이터 블록만 한번 소거하면 된다.

조건 4의 경우 순차쓰기용 로그블록에 이미 기록되어 있는 섹터들 중 일부분에 대해서 덮어쓰기가 발생하는 쓰기연산이다. 이 때는 데이터 블록과 로그 블록간의 합병 연산이 수행된다.

조건 5의 경우 임의쓰기용 로그블록에 기록하며, 기록할 위치는 로그블록에 마지막으로 기록한 위치 바로 다음이 된다. 만약, 로그
블록 그룹에 더 이상의 빈 섹터가 없으면 첫 번째 로그블록에 대해 합병 연산을 수행하여 공간을 확보한다. 합병 연산 수행시에는
원형 큐 방식으로 시작 로그 블록을 합병 연산의 대상으로 삼는데, 그 로그 블록에 쓰여진 섹터들의 논리 블록의 개수만큼
합병연산이 발생한다. 이 때 로그블록에서 최신 섹터를 찾아 이 최신 섹터의 내용을 예비블록에 기록하고 데이터 블록으로부터 나머지
최신 섹터를 복사한다. 로그블록에서 최신 섹터를 찾을 때는 큐의 마지막부터 역탐색(backward search)를 수행하고,
합병 연산의 대상이 아닌 역탐색으로 찾은 최신 섹터를 포함하는 로그블록에서 섹터를 복사하고 해당 섹터 번호를 사상테이블에서
-1로 설정하여 무효화(invalid)시켜 그 로그블록이 나중에 합병연산의 대상이 되었을 때 무시하도록 한다.