OS/vxWorks VxWorks (romStart( ) 함수의 동작 분석)
  • 728x90
    반응형

     

     

     

     

     romStart( ) 함수의 동작 분석


      

     

     

     

    romStart( ) 함수의 동작 분석    

    romInit.s 로부터 제어권을 넘겨받아서 실행되는 romStart( ) 함수의 동작을 분석해 보도록 한다.

    어셈블러 ROM 초기화 코드에서 하는 가장 중요한 일은, 시스템의 RAM 영역을 사용 가능한 형태로 만들어

    놓는 것이다. 그렇게 해야 CPU 의 스택 포인터 값을 그 RAM 영역으로 설정 할 수가 있고, 스택 포인터가

    설정이 되어야 비로서 C함수를 수행할 준비가 되는 것이다.

      

    romStart( ) – 최초로 수행되는 C함수

    romInit.s에 있는 어셈블러 함수에 의해서 최소한의 보드 초기화가 완료되고, RAM 영역이 사용 가능한

    상태가 되서 romStart( ) 함수로 제어권이 넘어오게 된다.    

      

    209    void romStart

    210    (

    211    FAST int startType /* start type */

    212    )

      

    romInit.s 파일을 분석할 때, 제일 처음에 COLD 부트인지 WARM 부트인지에 따라서 R3레지스터에

    BOOT_COLD라는 상수 값을 저장하는 부분에 관해서 설명을 했었다. 그때 저장된 R3 레지스터의 값이

    바로 위의 코드에서 startType라는 변수가 되는 것이다.

      

    물론, 실제로 사용되는 CPU가 CISC (Complex Instruction Set Computer)냐 RISC (Riduced Instruction Set

    Computer)냐에 따라서, 혹은 RISC 중에서도 PowerPC냐 MIPS 냐 ARM 이냐에 따라서 함수 호출시 전달되는

    인자를 어떤 식으로 넘겨주느냐가 결정이 된다.

    PowerPC와 ARM을 포함한 대부분의 RISC 계열의 CPU들에서는 레지스터를 통해서 인자의 값을 넘겨주는

    방식이 일반적이고 인텔 펜티엄과 같은 CISC계열의 CPU들에서는 스택을 통해서 인자의 값을 넘겨주는

    방식이 보다 보편화된 방법이다.

      

    214    {

    215    #if ((CPU_FAMILY==SPARC) || (CPU_FAMILY==MIPS) || (CPU_FAMILY==I80X86) || \

    216    (CPU_FAMILY==PPC) || (CPU_FAMILY==ARM))

    217    volatile /* to force absolute adressing */

    218    #endif /* (CPU_FAMILY==SPARC) */

    219    FUNCPTR absEntry; /* to avoid PC Relative Jump Subroutine */

      

    이 부분은 romStart( ) 함수의 실행이 끝나고 다음에 실행될 함수인 usrInit( )함수의 주소를 결정하기 위해서

    필요한 변수를 선언하는 부분이다. 우선 알 수 있는 것은, 대부분의 CPU의 경우에 volatile 선언을 해 준다는

    점인데, 이는 컴파일 과정에서 이미 분기할 주소인 usrInit( )의 위치가 결정이 되고, 컴파일러가 이를

    이용해서 분기를 한다거나 하는 식으로, 최적화에 의한 잘못된 변수 사용을 막기 위한 것으로 보인다.

      

    219라인의 FUNCPTR 역시, 주석에 나와있는 바와 같이, 프로그램 카운터에 대한 상대 점프를 하지 않고

    절대 번지로 분기할 수 있도록 하기 위해서, 함수를 호출하는 대신에 함수 포인터를 사용하는 것이다.

      

    220    #if CPU_FAMILY==ARM) && (!defined(ROM_RESIDENT)) && !define(BOOTCODE_IN_RAM)

    221    VOIDFUNCPTR ramfillLongs = fillLongs; /*force call to RAM */

    222    #define fillLongs(a,b,c) ramfillLongs(a,b,c)

    223    #endif /* (CPU_FAMILY==ARM) */

    224    #if(CPU_FAMILY==MC680X0)&&!defined(ROM_RESIDENT)&&!defined(BOOTCODE_IN_RAM)

    225    volatile VOIDFUNCPTR romcopyLongs = @Longs; /* force call to ROM */

    226    #define copyLongs romcopyLongs

    227    #endif /* (CPU_FAMILY==MC680X0) */

      

    여기서의 #if 문은 ARM CPU일 경우, ROM 레지던트 이미지가 아니고, 부트 코드 (즉 현재 실행 중인 ROM

    초기화 코드) 가 ICE 나 기타 다른 수단에 의해서 이미 RAM에 적재되어 있는 경우가 아니라면 fillLongs( )

    라는 함수를 직접 호출하지 않고 함수 포인터를 이용해서 호출하도록 되어 있다. 이렇게 할 경우,

    위에서 설명한 바와 같이, 현재의 프로그램 카운터를 이용한 상대번지 계산에 의한 분기가 아니고,

    함수 포인터에 저장되는 절대번지 값, 즉 fillLongs( )의 링크번지인 RAM번지로 분기를 하게 되는 것이다.

    그리고 그 아래의 #if문은 68k 계열의 CPU에 대해서, 같은 상황에 대해서 copyLongs( ) 이라는 함수를

    역시 함수 포인터를 이용해서 호출 하도록 하고 있다.

      

    228    /*

    229    * Copy from ROM to RAM, minus the compressed image

    230    * if compressed boot ROM which relies on binArray

    231    * appering last in DATA segment,

    232    */

    233

    234    #ifdef ROM_RESIDENT

    235    /* if ROM resident code, initialize memory and jump

    237    * to usrInit.

    238    */

    239    

    240

    241    #if (CPU_FAMILY == SPARC)

    242    copyLongs ((UINT *)(etext + 8), (UINT *) RESIDENT_DATA,

    243    #else

    244    copyLongs((UINT *)etext, (UINT *) RESIDENT_DATA,

    245    #endif

    246    ((UINT) wrs_kernel_data_end – (UINT) RESIDENT_DATA) / sizeof(long));

    247

      

    이 부분은 ROM 레지던트, 즉 프로그램 코드를 RAM 영역으로 복사하지 않고 ROM에서 실행하는

    이미지의 경우에, 코드 영역을 제외한 데이터 영역만을 RAM으로 복사하는 과정을 보여준다.

    여기서 etext는 링커에 의해서 정의되는 심볼로서, VxWorks 이미지의 코드 영역이 끝나는 부분의

    번지를 갖게 된다. 이 심볼로부터 8바이트가 지나서 실제 데이터 영역이 시작되는 SPARC 계열의 칩을

    제외하고는, 이 번지가 곧 데이터 영역의 시작 번지가 되는 것이다.

    데이터 영역이 복사 되는 RAM 번지는 RESIDENT_DATA로 되어있는데, 이는 일반적인 경우

    RAM_LOW_ADRS와 같은 값이 되지만 이미지의 종류에 따라서 RAM_HIGH_ADRS가 되기도 한다.

      

    248    #else /* ROM_RESIDENT */

    249    

    250    #ifdef UNCOMPRESS

    251

    252    #if(CPU_FAMILY==MIPS)

    253    /*

    254    * copy text to uncached locations to avoid problems with

    255    * copy back caches

    256    */

    257    ((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS, (UNIT)K0_TO_K1(romInit),

    258    ROM_COPY_SIZE / sizeof(long));

    259    #else /* CPU_FAMILY == MIPS */

    260    ((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS, (UINT)romInit,

    261    ROM_COPY_SIZE / sizof(long));

    262    #endif /* CPU_fAMILY == MIPS */

    263

      

    현재 우리가 분석하고 있는 이미지는 압축되지 않은 VxWorks_rom 이미지인데, 이 부분의 코드가 바로

    압축하지 않은 운영체제 이미지를 RAM으로 복사하는 부분이다. MIPS 계열의 침인 경우, 캐쉬 문제를

    해결하기 위해서 K0_TO_K1 이라는 매크로를 이용해서 복사 대상 번지인 romInit을 캐쉬하지 않는

    영역으로 바꿔주는 작업을 하게 되고, 다른 CPU들의 경우 아직 캐쉬를 작동시키지 않았으므로 그냥

    romInit 번지로 ROM_COPY_SIZE 만큼 복사를 하게 된다.

      

    혹시 대상 번지가 romInit인 것을 보고, 어떻게 ROM번지로 데이터를 복사하느냐고 생각할 수도 있는데,

    이는 초기 스택이 설정되는 번지가 romInit인 이유를 설명할 때 간단히 언급을 한 바가 있다. 즉,

    대부분의 코드가 나중에 실제로 RAM에 복사되어, RAM에서 사용되는 vxWorks_rom 이미지의 경우,

    링커에게 엔트리 포인트인 romInit의 번지를 실제 플레쉬 메모리에 구워지는 ROM번지가 아닌,

    RAM번지를 주게 된다. 이렇게 함으로서, 비록 ROM에서 실행되는 초기화 코드의 경우 링크 번지와 실행

    번지가 서로 일치하지 않는 현상이 생기지만 그 외의 모든 심볼들은 실행번지와 링크번지가 일치하게

    되는 것이다. 물록 이런 이유로, ROM초기화 코드는 PIC(Positon Independent Code), 즉 실행번지에

    상관없이 될 수 있는 코드로 작성이 되어야 하는 것이다.

      

    264    #else /* UNCOMPRESS */

    265

    266    #if (CPU_FAMILY==MIPS)

    267    /*

    268    * copy text to uncached location to avoid problems with

    269    * copy back caches

    270    * copy the entire data segment because there is no way ensure that

    271    * binArry is the last thing in the data segment because of GP relative

    272    * addressing

    273    */

    274    ((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS, (UINT)K0_TO_K1(romInit),

    275    ((UNIT)binArrayStart – (UINT)romInit) / sizeof(long));

    276    #else /* CPU_FAMILY == MIPS */

    277    ((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS, (UNIT)romInit,

    278    ((UNIT)binArrayStart-(UINT)romInit) / sizeof(long));

    279

    280    ((FUNCPTR(ROM_OFFSET(copyLongs))

    281    ((UINT *)((UINT)ROM_TEXT_ADRS + ((UINT)BINARRAYEND_ROUNDOFF -

    282    (UINT)romInit)), (UINT *)BINARRAYEND_ROUNDOFF,

    283    ((UINT)wrs_kernel_data_end – (UINT)binArrayEnd) / sizof(long));

    284

    285    #if(CPU == XSCALE)

    286    /* validate coherence, can not assume uncahed area… */

    287    ((FUNCPTR)ROM_OFFSET(checkLongs))

    288    (ROM_TEXT_ADRS, (UINT)romInit,

    289    ((UINT)binArrayStart – (UINT)romInit) / sizeof(long));

    290    

    291    ((FUNCPTR)ROM_OFFSET(checkLongs))

    292    ((UINT *)((UINT)ROM_TEXT_ADRS + ((UINT)BINARRAYEND_ROUNDOFF -

    293    (UINT)romInit)), (UINT *)BINARRAYEND_ROUNDOFF,

    294    ((UINT)wrs_kernel_data_end – (UINT)binArrayEnd) / sizeof(long));

    295    #endif

    296    #endif /* CPU_FAMILY == MIPS */

    297

    298    #endif /* UNCOMPRESS */

    299    #endif /* ROM_RESIDENT */

    300

    301

      

    ROM에서 실행되는 코드 (ROM Resident image)와 압축되지 않은 이미지의 경우를 제외한 이미 지는,

    압축되어 ROM에 저장되고, 압축이 풀려서 RAM으로 복사되어 RAM에서 실행되는 이미지 즉 압축된

    ROM 이미지이다. 따라서 이 부분에서는 ROM에 있는 초기화 코드를 RAM으로 복사를 하게 되는데,

    이때 관심을 갖고 볼 분이 몇가지 있다. 실제로 ROM이 있는 번지인 ROM_TEXT_ADRS 으로부터,

    복사되는 이미지의 RAM 엔트리 포인트인 romInit으로 복사를 진행하는 것은 다른 이미지와 비슷하지만,

    우선 일반적으로 이경우의 romInit은 RAM_HIGH_ADRS인 경우가 많다.

    또한, 복사되는 크기는 romInit부터 시작해서 binArrayStart 까지 먼저 복사하고, 또 binArrayEnd 부터 전체

    이미지의 끝까지를 RAM에 복사한다. 즉, ROM에 저장된 이미지 중에서, binArrayStart에서 binArrayEnd

    까지를 제외한 나머지 를 RAM에 복사하게 되는 것이다.

    그리고, XSCALE CPU의 경우, 복사된 이미지가 이상이 없는지 다시한번 확인을 하는 과정을 거치게 된다.

      

    여기서 binArrayStart와 binArrayEnd는 압축된 ROM이미지의 컴파일 과정에서, binToAsm.exe라는 호스트

    프로그램에 의해서 압축된 바이너리 이미지를 어셈블러 파일로 바꿀 때 사용되는 심볼로, 링크가 된

    상태에서는 데이터 심볼로서 압축된 이미지의 시작 번지와 끝번지를 식별하는데 사용된다.

    압축된 이미지의 컴파일 과정에서 대해서는 다음에 보다 자세히 다루도록 하겠다.

      

    302    #if(CPU_FAMILY != MIPS) && (!defined (BOOTCODE_IN_RAM))

    303    

    304    /* clear all memory if cold booting */

    305

    306    if(startType & BOOT_CLEAR)

    307    {

    308    #ifdef ROM_RESIDENT

    309    /* Clear memory not loaded with text & data.

    310    *

    311    * We are careful about initializing all memory(except

    312    * STACK_SAVE bytes) due to parity error generation(on

    313    * some hardware) at a later stage. This is usually

    314    * caused by read accesses wihout initialization.

    315    */

    316    fillLongs((UINT *)wrs_kernel_data_end),

    317    ((UINT)romInit – STACK_SAVE – (UINT)SYS_MEM_BOTTOM) /

    318    / sizeof(long), 0);

    319    fillLongs(((UINT *)wrs_kernel_data_end),

    320    ((UINT)SYS_MEM_TOP – ((UINT) wrs_kernel_data_end)) / size(long), 0)l

    321    

    322    #else /* ROM_RESIDENT */

    323    fillLongs((UINT *)(SYS_MEM_BOTTOM),

    324    ((UINT)romInit –STACK_SAVE – (UINT) SYS_MEM_BOTTOM) /

    325    sizeof(long), 0);

    326

    327    #if defined(UNCOMPRESS)

    328    fillLong((UINT *)((UINT)romInit + ROM_COPY_SIZE),

    329    ((UINT)SYS_MEM_TOP – ((UINT)romInit + ROM_COPY_SIZE))

    330    / sizeof(long), 0);

    331    #else

    332    fillLong ((UINT *((UINT)wrs_kernel_data_end,

    333    ((UINT)SYS_MEM_TOP – (UINT)wrs_kernel_data_end) / sizeof(long), 0);

    334    #endif /* UNCOMPRESS */

    335    #endif /* ROM_RESIDENT */

    336

    337    /*

    338    * Ensure the boot line null. This is necessary for those

    339    * targets whose boot line is excluded from cleaning.

    340    */

    341

    342    *(BOOT_LINE_ADRS) = EOS;

    343    }

    344    

    345    #endif /* (CPU_FAMILY != MIPS) && (!defined(BOOTCODE_IN_RAM)) */

    346

      

    여기서는 콜드 부팅일 경우, 즉 전원이 최초로 인가되거나 혹은 하드웨어 리셋 신호에 의한 리부팅의

    경우에 RAM을 0으로 초기화 하는 과정을 보여준다. 일부 하드웨어의 경우, RAM 값이 0으로 초기화

    되지 않은 상태에서 사용이 되면 패리티 에러가 발생하는 등의 이유로 콜드 부팅에서 RAM을 0으로

    초기화 하는데, 제품의 부팅 시간을 단축시키는 것이 중요할 경우, 이 부분을 생략하는 경우도 많다.

      

    우선 ROM_RESIDENT의 경우, 이미지의 코드 영역은 모두 ROM에 남아있고, RAM에는 데이터 영역만

    복사가 된 상태이다. 따라서, 복사된 데이터 영역을 제외한 그 앞뒤의 영역, 즉 RAM의 바닥 에서부터

    데이터 영역까지, 그리고 데이터 영역의 끝부터 RAM의 최 상단까지 0으로 초기화를 하게 되는 것이다.

      

    그 외의 ROM 이미지의 경우, RAM의 바닥부터 romInit까지를 0으로 초기화 하는 부분은 공통적으로 같고,

    압축되지 않은 이미지의 경우엔 복사된 영역(ROM_COPY_SIZE)만큼을 제외한 그 윗부분의 RAM을 0으로

    초기화 하게 된다.

    압축된 이미지의 경우엔, 이미지의 중간에 binArrary가 들어가 있으므로, 복사된 크기와 관계없이,

    복사된 이미지의 데이터 영역이 끝나는 부분부터 나머지 상위 RAM영역을 0으로 초기화 하게 된다.

    그리고 마지막에는 부트 파라메터를 저장하고 있는 영역이 혹시 0으로 클리어 되지 않는 경우를 위해서

    이를 NULL 값으로 만들어주게 된다.

      

    347    /* jump to VxWorks entry point (after uncompressing) */

    348

    349    #if define(UNCOMPRESS) || define(ROM_RESIDENT)

    350    #if (CPU_FAMILY == 1960)

    351    absEntry = (FUNCPTR)usrInit; /* on to bootConfig */

    352    #else

    353    absEntry = (FUNCPRTR)usrInit; /* on to bootConfig */

    354    #endif /* CPU_FAMILY == 1960 */

    355

      

    이제 압축되지 않은 이미지의 경우엔 다음 초기화 단계인 usrInit( )으로 분기를 하게 되는데, 1960 계열의

    CPU들은, 그 전에 하드웨어적으로 처리할 사항을 sysInitAlt( ) 루틴에서 처리를 하게 되고, 나머지 CPU

    들은 ROM이나 RAM에 위치한 usrInit( )함수의 실행을 개시하게 된다.

      

    356    #else

    357    {

    358    #if(CPU_FAMILY == MIPS)

    359    volatile FUNCPTR absUncompress = (FUNCPTR)UNCMP_RTN;

    360    if((absUncompress)((UCHAR *)ROM_OFFSET(binArrayStart),

    361    (UCHAR *)K0_TO_K1(RAM_DST_ADRS),

    362    (int )((UINT)binArrayEnd – (UINT)binArrayStart)) != OK)

    363    #elif (CPU_FAMILY == I80X86)||(CPU_FAMILY == ARM)

    364    volatile FUNCPTR absUncompress = (FUNCOTR)UNCMP_RTN;

    365    if((absUncompress)((UCHAR *)ROM_OFFSET(binArrayStart),

    366    (UCHAR *)RAM_DST_ADRS, binArrayEnd – binArrayStart) != OK)

    367    #else

    368    if(UNCMP_RTN ((UCHAR *)ROM_OFFSET(binArrayStart),

    369    (UCHAR *)RAM_DST_ADRS, binArrayEnd – binArrayStart) != OK)

    370    #endif /* (CPU_FAMILY == MIPS) */

    371    return; /* if we return ROM's will halt */

    372

    373    absEntry = (FUNCPTR)RAM_DST_ADRS; /* compressedEntry( ) */

    374    }

    375    #endif /* define UNCOMPRESS || defined ROM_RESIDENT */

    376

      

    압축된 이미지의 경우 압축을 해제해야 하는데, 여기서도 MIPS의 경우 캐쉬 관련 매크로를 사용

    한다는 점과, I80X86 및 ARM 계열의 CPU들은 상대번지 분기를 막기 위해서 함수 포인터를 사용

    한다는 점을 제외하면 동작 원리는 동일하다. 우선, 압축을 해제하는 UNCMP_RTN는 inflate( )라는

    함수를 사용하게 된다. 이는 공개된 소스 코드인 zlib의 압축 해제 부분을 VxWorks로 포팅한 함수이다.

    실행코드의 경우 약 50%정도의 압축률을 보인다고 알려져 있다.

      

    압축이 해제되는 목적 번지는 대부분 RAM_LOW_ADRS가 된다. 즉, 압축된 이미지의 경우, ROM 초기화

    코드와 압축 해제 코드는 RAM_HIGH_ADRS로 들어가고, 실제 압축된 VxWorks 실행 이미지는

    RAM_LOW_ADRS로 압축이 풀려서 실행되는 형태로 되어있는 것이다.

      

    377    #if((CPU_FAMILY == ARM) && ARM_THUMB)

    378    absEntry = (FUNCPTR)((UINT32)absEntry | 1); /* force Thumb state */

    379    #endif /* CPU_FAMILY == ARM */

    380

    381    (absEntry)(startType);

    382    }

      

    마지막으로, 각각의 이미지 별로 설정된 목표 번지가 저장된 absEntry 함수 포인터를 이용해서 운영체제의

    나머지 부분이 초기화 되는 usrInit( )으로 분기를 하게 된다. 이상으로 romStart( )함수의 구성을 살펴보았다.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    728x90
    반응형

    'OS > vxWorks' 카테고리의 다른 글

    vxSim 과 Window 네트워크 통신  (0) 2017.07.05
    VxWorks (usrInit 함수의 동작 분석)  (0) 2017.07.05
    VxWorks (BSP 파일구성/romInit.s 분석)  (0) 2017.07.05
    VxWorks C99 버전 변경  (0) 2017.06.30
    [VxWorks] 메시지 큐 예제  (0) 2017.06.30
상단으로