[Linux] scnprintf()

리눅스 커널은 scnprintf 라는 함수를 제공하는데, 이 함수는 snprintf의 일종이라고 볼 수 있다.
snprintf 는 C99 표준에 의해서 버퍼에 실제로 쓰여진 길이를 리턴하지 않고, 쓰여지고자 했던 길이를 리턴하도록 되어 있다. 예를 들어 다음과 같은 소스 코드가 있다면,

#include <stdio.h>

int main(void)
{
        char buffer[10] = { 0, };
        int output = 0;

        output = snprintf(buffer, sizeof(buffer), “%ld”, 1234567890123l);
        printf(“buffer: %s, output: %d\n”, buffer, output);

        return 0;
}

대부분 생각하는 출력은 아마도 “buffer: 123456789, output:9” 와 같을텐데, 이 소스 코드에 해당하는 프로그램은 다음과 같이 output으로 원래 찍으려고 했었던 수의 출력 길이인 13을 출력한다.

dasomoli@dasomoli-ubuntu:~/src/snprintf$ ./a.out
buffer: 123456789, output: 13

그래서 커널은 snprintf 말고도 scnprintf 함수를 제공한다.

요즘은 Device driver의 제어를 위해 ioctl 함수보다는 sysfs 를 이용하는 추세인데, 이를 위한 sysfs의 show 함수에서는 scnprintf를 사용하도록 하고 있다(Documentation/filesystems/sysfs.txt).

참고 : http://lwn.net/Articles/69419/

[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] Shell script 에서 문자열 다루기

참고에 매우 자세히 잘 정리되어 있다. 감사.

문자열 길이 구하기
${#string}

문자열 추출
${string:position}
${string:position:length}

문자열 조각 삭제
${string#substring}
 string의 앞에서부터 가장 짧게 일치하는 substring 제거
${string##substring}
 string의 앞에서부터 가장 길게 일치하는 substring 제거

${string%substring}
 string의 뒤에서부터 가장 짧게 일치하는 substring 제거
${string%%substring}
 string의 뒤에서부터 가장 길게 일치하는 substring 제거

참고 : 
http://wonylog.tistory.com/192

[Linux] ioctl을 대체하는 unlocked_ioctl

기존 device driver를 제어하기 위한 방법 중 하나였던 ioctl이 2.6.36 커널부터 unlocked_ioctl 로 대체되었다.
ioctl과 unlocked_ioctl의 차이점은 ioctl이 BKL(Big Kernel Lock)로 보호되었다면, unlocked_ioctl은 BKL이 없다. 따라서 Preemptible 하게 되었으므로, ioctl의 동시성에 대해서 driver 스스로 보호하여야 한다.
driver의 제어를 위해서 ioctl보다는 sysfs를 이용하는 방향으로 가고 있는 듯 하다(http://lists.kernelnewbies.org/pipermail/kernelnewbies/2011-May/001852.html).

compat_ioctl은 64비트 시스템에서 32비트 프로세스가 ioctl을 부를 때를 위해서 만들어졌다.

참고 : http://lists.kernelnewbies.org/pipermail/kernelnewbies/2011-May/001851.html

Linux kernel, ramdisk 사용 및 Android 빌드 시스템에서의 설정

Linux 커널의 경우 램디스크를 여러 종류를 사용하는데 이 중 initramfs/initrd 를 사용하는 경우
CONFIG_BLK_DEV_INITRD 를 설정하고, CONFIG_INITRAMFS_SOURCE 에 root 파일템이 만들어져 있는 디렉토리 경로를 써준다.
Android 빌드 시스템에서는 커널 및 램디스크를 합쳐서 boot.img 를 만들어 사용하는데,
이 때, 사용될 Kernel Command line 을 지정할 수 있다. 이 때 TARGET_NO_KERNEL은 false 여야 한다.
이는 build/core/Makefile 에서 확인할 수 있다. 

BOARD_KERNEL_CMDLINE := $(strip $(BOARD_KERNEL_CMDLINE))
ifdef BOARD_KERNEL_CMDLINE
  INTERNAL_BOOTIMAGE_ARGS += –cmdline “$(BOARD_KERNEL_CMDLINE)”
endif

이 외에도 Kernel Base address, Page Size 등도 지정할 수 있다.

BOARD_KERNEL_BASE := $(strip $(BOARD_KERNEL_BASE))
ifdef BOARD_KERNEL_BASE
  INTERNAL_BOOTIMAGE_ARGS += –base $(BOARD_KERNEL_BASE)
endif
BOARD_KERNEL_PAGESIZE := $(strip $(BOARD_KERNEL_PAGESIZE))
ifdef BOARD_KERNEL_PAGESIZE
  INTERNAL_BOOTIMAGE_ARGS += –pagesize $(BOARD_KERNEL_PAGESIZE)
endif

TARGET_BOOTIMAGE_USE_EXT2 를 true로 하면 ext2 파일시스템의 boot image를 만드는 것 같다.

ifeq ($(TARGET_BOOTIMAGE_USE_EXT2),true)
tmp_dir_for_image := $(call intermediates-dir-for,EXECUTABLES,boot_img)/bootimg
INTERNAL_BOOTIMAGE_ARGS += –tmpdir $(tmp_dir_for_image)
INTERNAL_BOOTIMAGE_ARGS += –genext2fs $(MKEXT2IMG)
$(INSTALLED_BOOTIMAGE_TARGET): $(MKEXT2IMG) $(INTERNAL_BOOTIMAGE_FILES)
    $(call pretty,”Target boot image: $@”)
    $(hide) $(MKEXT2BOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) –output $@
else # TARGET_BOOTIMAGE_USE_EXT2 != true
$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES)
    $(call pretty,”Target boot image: $@”)
    $(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) –output $@
    $(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE),raw)
endif # TARGET_BOOTIMAGE_USE_EXT2

예제)

TARGET_NO_KERNEL := false
BOARD_KERNEL_BASE := 0x30000000
BOARD_KERNEL_PAGESIZE := 4096
BOARD_KERNEL_CMDLINE := console=ttyFIQ0 no_console_suspend

개발 환경 개선 Git + Gerrit + checkpatch + cleanpatch

원래 나한테 좀 맞지 않던 카메라 개발에서 벗어나서 요즘 좀 재밌는 걸 하고 있다.
개발 업무를 살짝 벗어나서 SCM 업무를 하고 있는데, 내가 옳다고 생각하는 방향으로 부서 전체를 바꾸고 있어서 좀 재미나다. 회사 전체를 바꿀 수 있으면 더 재미날 것 같지만, 그건 이후의 일이고..

아무튼 요약하면 구글에서 NexusS 개발을 하면서 썼던 git + gerrit 시스템을 적용하고, 여기에 코딩룰 자동 오류 보고 + 코딩 룰 자동 오류 수정 + 수정본 자동 업로드를 구축 중이다. 여러 가지 것들을 조합해서 만들어 내는 개발환경 개선 작업은 신난다.

1. 기존의 불편한 Centeralized VCS 툴을 벗어나 DVCS를 사용해 여러 브랜치의 Integration 작업의 어려움을 줄이고, 같은 패치를 여러 곳에 적용하기 쉽게 만들고,
2. 코드 리뷰 시스템을 통해 좋은 코드를 함께 말할 수 있는 장을 만들고, 코드 개선을 양지로 끌어내고, 코딩 룰에 대해 생각하게 하고, 코드의 구조에 대해 생각하게 하고, 후배 개발자에게 선배 개발자의 지식을 전달할 수 있는 체계를 만들고,
3. 코딩 룰의 자동 체크를 통해 리뷰어의 노력을 덜 들이게 하고, 개발자의 모든 코드의 코딩룰을 검사하도록 하고, 수정하도록 요구하며,
4. 구조 개선 같은 복잡한 것까지는 아니더라도 체크한 패치가 오류가 있으면 간단한 코딩 룰 등은 자동으로 수정해서 바로 업로드 하여 개발자들의 코딩 룰 등 간단한 수정에 드는 노력을 줄인다.

여기에 기존 시스템(빌드, 배포 등)과의 호환성 유지를 위한 작업까지.. 아직은 사내 모든 부서가 내가 구축하는 것을 따르고 있지는 않기 때문에..

개발 생산성 향상이라는게 말만큼 거창한게 아니다. 개선에 개선을 거듭해 쓸데없는 일을 줄이고 줄여, 더이상 뺄 게 없는 프로세스를 만들어 내는 거다.

위에 것이 어떻게 가능하냐. git + gerrit + jenkins + Linux kernel의 checkpatch.pl, Linux kernel의 cleanpatch + Linux shell의 여러 utility + 손수 제작 스크립트(bash, perl 등)를 조합하면 된다.

딴데도 이렇게 하고 있는 곳이 있을까? 내맘대로 세계 최초라 주장하련다. ㅋㅋㅋㅋ

[JAVA] JVM의 Proxy 셋팅

net.properties 를 셋팅하여 Java 프로그램이 실행되는 JVM이 프록시를 통해 네트워크를 연결할 수 있도록 할 수 있다. java로 실행된다면 다음과 같이 옵션을 넣어주면 된다.

java -Dhttp.proxyHost=168.219.61.250 -Dhttp.proxyPort=8080 …
우분투 리눅스의 경우에는 /etc/java-6-sun/net.properties 파일에 시스템 전역 설정값을 설정할 수 있다.

[Ubuntu] dos2unix 사용하기

1. tofrodos 패키지를 설치

sudo apt-get install tofrodos

2. dos2unix 로 fromdos 를 소프트 링크

sudo ln -s /usr/bin/fromdos /usr/bin/dos2unix

혹은 슈퍼유저 권한이 없을 경우 fromdos 파일을 ~/bin에 복사한 후 dos2unix를 소프트링크

ln -s ~/bin/fromdos ~/bin/dos2unix

참고 : http://whoa.egloos.com/2633778