'임베디드'에 해당되는 글 112건

  1. 2006.11.30 리눅스 시스템 시간과, 외부 RTC와의 시간 동기 및 설정 읽기
  2. 2006.10.30 [kernel]플래쉬 메모리 컨트롤
  3. 2006.10.27 [kernel]램디스크 or MTD ?
  4. 2006.10.26 [커널] mtd 관련 sst
  5. 2006.10.24 [kernel] jffs2를 이용한 부팅시 에러 메세지
  6. 2006.10.23 [u-boot]램디스크 이미지 읽어오는 부분 재수정
  7. 2006.10.20 mtd 관련 소프트웨어 구하고 포팅하기
  8. 2006.10.20 target시스템에 zlibㅏ이브러리 포팅하기(arm)
  9. 2006.10.19 ramdisk와 ext2의 차이점
  10. 2006.10.17 [u-boot] 랜덤 mac 주소와 dhcp - 061017

리눅스 시스템 시간과, 외부 RTC와의 시간 동기 및 설정 읽기

제목: [응용] 시간 처리
글쓴이: 유영창
글쓴날: 2005년 03월 10일 오전 01:45
URL : http://kelp.or.kr/korweblog/?story=05/03/10/6722374

 

--------------------------------------------------------------------------------

정말 오랜만에 글 올리네요 ^^

제가 그동안 넘 바빠서요 쩝... 용서해 주세용...

APK009 시간 처리
==============================================

1. 개요

   이 문서는 ESP-NS에서 동작하는 응용 프로그램에서
   시간과 관련된 처리를 해야 하는 경우에 대한 소개입니다.
  
  
   작성자 : 유영창 frog@falinux.com
   작성일 : 2004년 9월 17일
   수정일 : 2005년 3월 10일
  
   관련된 ADK( Application Developer Kit ) 디렉토리
  
   adk/sample/time
  

2. 달력 시간

   임베디드 시스템의 경우 시간 관리는 거의 필수적인 사항이라고 봅니다.
   그 만큼 시간에 관련된 처리는 자주 발생하는 프로그램 사항입니다.
  
   그런데 시간의 의미는 여러가지가 있읍니다.
  
   가장 먼저 생각 나는 것은 달력 시간입니다. 달력 시간이라는 것은
   우리가 흔히 알고 있는 날짜와 관련된 시간을 말합니다.
  
   "2004년 11월 12일 12시 30분 29 초"
  
   이렇게 표시되는 것이죠...
  
   이런 정보를 얻기 위해서는 어떻게 해야 할까요?
   리눅스 응용프로그램에서는 어떤 함수를 사용해야 할까요?

2.1 time 함수

   우선 가장 오래되고 전통적인 방법인 time 이라는 함수에 대하여 알아 봅시다.

   이 함수는 다음과 같은 형태를 갖고 있읍니다.

    time_t time (time_t *result) ;

   이 함수를 사용하려면 #include <time.h> 를 포함해야 합니다.

   이 함수는 협정 세계시간 1970년 1월 1일 00: 00: 00 시간 이후 경과된 초의 수를 result 에 지정된 변수에
   넘겨 줍니다.
  
   함수 반환값은 대부분의 경우 무시해도 됩니다. 이 함수가 실패할 가능성은 거의 없기 때문에
   고민해서 저처럼 머리털 빠지지 않았으면 합니다.

   이 함수의 사용은 다음과 같이 하면 됩니다.

    time_t     cur_time;

    time( &cur_time );

    printf( "time value %ld\n", cur_time );

   이 예는 현재 시간을 받아서 초로 표시해 주는 예 입니다.

   자..

   여기서 한가지 이 함수와 관련된 문제를 밝혀 드리겠읍니다.

   time_t 란 변수는 long int 형입니다.

   그래서 이 값은 134217727 에서 -134217728 사이의 값을 가집니다.

   이 값으로 표현할수 있는 최대 연도는 2038년으로 제한 됩니다.

   그렇다면 이후의 날짜는 어떻게 설정할까요?

   현재 glibc 를 사용하는 한 불가능합니다.

   다른 유닉스 시스템 중 일부에서는 time64 라는 함수와 time64_t 라는 변수형을 선언해서
   이 문제를 해결 하고 있지만 리눅스에서는 그 해결 방한을 아직까지 안 내놓은 것으로
   압니다. ( 물론 제가 해결책을 인터넷에서 아직 발견하지 못했기 때문일 수도 있읍니다. )

   이 시간의 문제는 단순히 함수만의 문제는 아닙니다.
   리눅스 운영체제에서 사용하는 각종 시간 관련 함수들을 사용하는 유틸리티나 응용 프로그램에서
   동시에 발생하는 문제 입니다.

   이 문제를 해결하고 싶다면 시간과 관련된 함수를 자신이 직접 작성해서 해결하는 방법 이외에는
   현재 대안은 없읍니다. 아니면 glibc 에서 해결해 주기를 기다려야 겠지요....


2.2 gmtime, localtime 함수

    time 함수로 반환된 것은 초 단위로 연산하기 때문에 시간에 대한 연산을 하기에 편리합니다.
    하지만 LCD 에 시간을 표시하거나 시간과 관련된 스케쥴링 처리를 다룰 경우에는
    우리가 보통 표현하는 방법인 년 월 일 시 분 초 로 구별되어 표현되는 함수가 필요합니다.

    이런 시간 형식으로 시간을 구하는 함수에는 gmtime 과 localtime이라는 두 함수가 있읍니다.

    이 두 함수의 차이는 무엇일까요?

    여러분이 국내에다 팔고 말 물건을 만든다면 그냥 gmtime 과 localtime 은 무시하고 쓰셔도 무방합니다.
    이런 경우에는 그냥  localtime을 사용하실 것을 권장합니다.
    이때 환경 변수에 설정할 것이 있는데 이것에 대한 것은  이 글을 계속 읽고 나시면 아실겁니다.
   
    여러분이 시스템을 만든다고 합시다.

    문제는 두 장비가 하나는 우리나라에 또 하나는 미국에 있다고 합시다.
    여기서 생기는 문제는 우리나라와 미국은 시간대가 틀리다는 겁니다 .
   
    우리나라보다 미국은 12시간 정도의 차이가 납니다.

    이런 경우 두 장비간에 10:00 시간에 상호 동기를 맞추기 위한 통신을 시작 하자고 하거나
    어떤 데이터의 기록 시간을 정한다고 한다면 문제가 생깁니다. 우리나라의 10:00와
    미국의 10:00는 다른 시간 시점이 되기 때문입니다.

    이럴 경우에는 세계 기준 시간을 사용하는 것이 정확한 것입니다.
   
    즉 세계 기준 시간인 10:00 에 서로 통신하기로 한다면
    우리나라와 미국이 서로 시간대가 틀리더라도 아무 문제가 없다는 것입니다.

    왜냐하면 세계 기준 시간은 서로 다른 위치에 있어도 동일한 시간을 가리키기 때문입니다.

    리눅스 시스템은 원칙적으로 커널에서 관리하는 내부 시간은 세계 기준 시간이라고 봅니다.

    이 세계 기준 시간을 가져오는 함수가 gmtime 이라는 함수 입니다.

    반면에 세계 기준 시간에 해당하는 우리나라의 시간을 구하는 함수는 localtime 입니다.

    그래서 gmtime으로 구한 함수는 시스템에 내부적으로 설정된 시간이고 localtime은
    그 시간을 각 지역에 맞도록 변환한 시간입니다.

    이 localtime 은 환경 변수 TZ 라는 것에 영향을 받습니다.

    이 TZ 값을 보려면 다음과 같은 명령을 사용하면 보입니다.

    
    # echo $TZ

    아마도 ESP-NS 에서는 이 값 이 아무런 값으로도 설정 되어 있지 않을 겁니다.

    이런 경우에는 gmtime 에서 구한 시간과 localtime에서 구한 시간은 동일한 시간값으로
    구해집니다.

    이때의 시스템 시간을 보려면 다음 명령을 치면 됩니다.

    # date
    Thu Jan  1 01:14:30 Universal 1970

    이렇게 나온 것은 ESP-NS 가 초기 생산시에는 시간을 설정하지 않아서 입니다.
    이 부분은 이후에 RTC에 대한 설명을 할때 다시 설명할 것입니다.

    일단 임시로 시간을 설정하려면 다음과 같이 설정해야 합니다.
    설정하려는 시간이 2005년 3월 8일 23시 21분 00 초 라면
      
    # date -s 030823212005.00
    Tue Mar  8 23:21:00 Universal 2005

    와 같이 해 주어야 합니다.

    보시는 바와 같이 date -s 명령의 형식은 다음과 같습니다.

    YYYY년 MM 월 DD 일 HH 시 NN 분 SS 초 라면

    date -s MMDDHHNNYYYY.SS

    이런식으로 들어 갑니다.

    자 이 상태에서 TZ 값을 우리나라 시간대로 맞추어 봅시다.
    다음과 같이 하면 됩니다.

    # export env TZ=GMT-09:00:00

    이렇게 설정한 상태에서 date 명령을 사용하여 시간을 보면 다음과 같이
    나옵니다.

    [root@ez-x5 /acu]$ date
    Wed Mar  9 08:25:13 GMT 2005

    표출된 시간을 보면 알겠지만 시스템에 설정된 시간보다 9시간이 지난 시간이
    표출됩니다.

    여기서 TZ 환경 변수에 대하여 조금 더 자세하게(?) 알아 보겠읍니다.

    이 TZ 환경 변수는 보통 부팅시에 초기화 스크립트에 의해서 설정됩니다.

    리눅스 시스템이 설치된 지역을 나타내는 지명을 쓰는 경우가 일반적입니다.

    예를 들어 TZ=Seoul 과 같은 형식입니다.

    하지만 이렇게 설정하는 것은 timezone 파일이 필요하고 복잡하므로

    저는 GMT-09:00:00 에 대한 것만 설명하려 합니다.
    제 경험으로는 이정도면 충분하고 설정도 간단하기 때문입니다.

    TZ 환경 변수에 GMT형식으로 설정하는 것은
    localtime 함수에 얼마의 시간을 더하거나 빼는가를 지정하는 것입니다.

    형식은 다음과 같습니다.
    GMT[+/-][HH:MM:SS]

    GMT 란 문자열은 GMT 형식을 나타내고
    + 또는 - 는 그리니치 천문대를 중심으로 서쪽에 있으면 + 동쪽에 있으면 - 입니다.

    우리나라는 동쪽에 위치하고 9시간의 차이가 있으므로

    TZ=GMT-09:00:00 으로 설정하는 것입니다.

    이 값은 ESP-NS 의 경우에는

    /etc/rc.d/rc.local 파일에 마지막에

    export env TZ=GMT-09:00:00

    란 항목을 넣으시면 됩니다.

    이제 gmtime 의 함수와 localtime 함수의 형식을 알아 봅시다.

    이 함수의 사용법은 다음과 같이 같습니다.

    
    struct tm *localtime (const time_t *time)
    struct tm *gmtime(const time_t *time)

    이 함수에서 사용하는 time 은 time 함수에서 구한 값의 변수 주소를 지정하면 되고
    그 결과로 tm 구조체의 주소를 반환합니다.

    사용 예는 다음과 같습니다.

    time_t     cur_time;
    struct tm *tm_data;

    time( &cur_time );

    tm_data = gmtime( &cur_time );
    printf( "GMT   TIME %04d-%02d-%02d %02d:%02d:%02d\n",
                       tm_data->tm_year + 1900,
                       tm_data->tm_mon +1,
                       tm_data->tm_mday,
                       tm_data->tm_hour,
                       tm_data->tm_min,
                       tm_data->tm_sec );

    tm_data = localtime( &cur_time );
    printf( "LOCAL TIME %04d-%02d-%02d %02d:%02d:%02d\n",
                       tm_data->tm_year + 1900,
                       tm_data->tm_mon +1,
                       tm_data->tm_mday,
                       tm_data->tm_hour,
                       tm_data->tm_min,
                       tm_data->tm_sec );
    

   tm 구조체에서 우리가 주로 쓰고 알아야 하는 필드 변수는 다음과 같습니다.


     int tm_sec          : 0 에서 59 까지의 범위를 갖는 초(second)
     int tm_min          : 0 에서 59 까지의 범위를 갖는 분(minute)
     int tm_hour         : 0 에서 23까지의 범위를 갖는 시(hours)
     int tm_mday         : 1 에서 31까지의 범위를 갖는 달
     int tm_mon          : 0 에서 11까지의 범위를 갖는 달
     int tm_year         : 1900년 이후의 년(year)
     int tm_wday         : 0 에서 6의 범위를 갖는, 일요일을 기점으로 해서 요일에 해당하는 수

   조심해야 하는 것은 구해진 년도는 1900을 더해 주어야 우리가 사용하는 년도가 된다는 것과
   월은 1 을 더해 주어야 우리가 사용하는 월이 된다는 점입니다.

   또 한가지 요일을 표현하는 tm_wday는 일=0 월=1 화=2 수=3 목=4 금=5 토=6 라는 점입니다.
   우리나라 사람들은 일주일의 시작을 월요일 부터라고 생각하지만 
   미국 사람들은 일요일 부터라고 생각합니다.

   시간을 나타내는 문자열의 함수를 glibc 에 있는 것을 사용하면
   우리나라 실정에는 조금 불편합니다. 그래서 저는 위와 같은 형식으로 직접 문자열로
   변환해 사용합니다.


2.3 mktime 함수

  
   지금까지 초를 시간으로 변환하는 함수를 알아 보았는데
   당연히 반대 함수도 있겠죠?

   예를 들어 2005년 9월 12일 18시 30분 40 초를 초로 바꾸려면

   mktime 함수를 사용해야 합니다.

   이 함수의 형식은 다음과 같습니다.

   time_t mktime(struct tm *brokentime)

   tm 구조체는 앞에서 설명한 것과 같습니다.

   위 시간을 초로 바꾸는 예제는 다음과 같습니다.

    time_t     timev;
    struct tm  tm_src;
    struct tm *tm_data;

    tm_src.tm_year = 2005 - 1900;
    tm_src.tm_mon  =    9 - 1;
    tm_src.tm_mday =   12;
    tm_src.tm_hour =   18;
    tm_src.tm_min  =   30;
    tm_src.tm_sec  =   40;

    timev = mktime( &tm_src  );

    tm_data = gmtime( &timev );
    printf( "GMT   TIME %04d-%02d-%02d %02d:%02d:%02d\n",
                       tm_data->tm_year + 1900,
                       tm_data->tm_mon +1,
                       tm_data->tm_mday,
                       tm_data->tm_hour,
                       tm_data->tm_min,
                       tm_data->tm_sec );

    tm_data = localtime( &timev );
    printf( "LOCAL TIME %04d-%02d-%02d %02d:%02d:%02d\n",
                       tm_data->tm_year + 1900,
                       tm_data->tm_mon +1,
                       tm_data->tm_mday,
                       tm_data->tm_hour,
                       tm_data->tm_min,
                       tm_data->tm_sec );

3. gettimeofday 함수 ( 정밀 시간 )

   임베디드 시스템에서는 초 단위의 시간만으로는 I/O 제어를 하거나 통신 재 전송 시간 관리
   같은 매우 짧은 시간 간격의 제어가 불가능합니다.

   그래서 매우 정밀한 시간 측정이 필요한데 이럴때 사용하는 것이 gettimeofday 라는 함수
   입니다.

   이 함수는 다음과 같은 형식을 가지고 있읍니다.

   
   int gettimeofday (struct timeval *tp, struct timezone *tzp)

   여기서 tzp 매개변수는 무조건 NULL 이라고 생각하시면 됩니다.
  
   timeval 구조체는 다음과 필드 변수를 가지고 있읍니다.

   long int tv_sec : time 함수에서 구한 값과 같은 의미로
                     협정 세계시간 1970년 1월 1일 00: 00: 00 시간 이후 경과된 초입니다.

   long int tv_usec : 마이크로초(microsecond)로 표현된 시간값인데 부가적으로 흐른 시간 마이크로 초입니다.


   이 함수를 사용하는 예는 다음과 같습니다.

    struct timeval tp;
    int lp;

    for( lp = 0; lp < 1000; lp++ )
    {
        gettimeofday ( &tp, NULL );
        printf( "Sec %ld uSec %ld\n", tp.tv_sec, tp.tv_usec );
    }

    이 함수를 저는 주로 지나간 시간을 측정하는 용도로 사용합니다.
    또한 대부분의 경우 미리 초 단위로 측정하면 되기 때문에
    다음과 같은 함수를 만들어서 사용합니다.


    static struct timeval  __mclock_start;   // 기준 시작 시간
   
    void mclock_init( void )
    {
        gettimeofday( &__mclock_start, NULL );
    }
   
    long int mclock( void )
    {
        struct timeval timecurrent;
        struct timeval timeresult;
       
        gettimeofday( &timecurrent, NULL );
       
        timeresult.tv_sec  = timecurrent.tv_sec  - __mclock_start.tv_sec;
        timeresult.tv_usec = timecurrent.tv_usec - __mclock_start.tv_usec;
   
        if( timeresult.tv_usec < 0 )
        {
            timeresult.tv_sec--;
            timeresult.tv_usec += 1000000;
        }
       
        return timeresult.tv_sec * 1000 + ( timeresult.tv_usec / 1000 );
    }
   
    void mclock_test( void )
    {
        long int start_mclock;
        int lp;
   
        mclock_init();  // 프로그램 전체에서 한번만 설정해 주면 된다.
   
        for( lp = 0; lp < 10000; lp++ )
        {
            printf( "mclokc : %ld m Sec \n", mclock() );
        }
       
    }

*** clock 함수의 문제

   10mSec 이상의 정밀한 측정시에 리눅스 커널에서 제공하는 CPU 의 경과 시간을 측정함수 중에는
   clock 이라는 함수가 있읍니다. 이 함수가 정상적으로 동작만 한다면야 50mSec 이상의 처리
   시간 간격을 허용하는 경우라면 무척 편리한 함수가 될 겁니다.

   그러나 실제로 제가 시험해 보았을때는 이 함수는 제대로 동작하지 않았읍니다.
   원인을 찾아 보고 싶었지만 저의 귀차니즘으로 인하여 관뒀읍니다.

   getimeofday라는 막강한 함수가 있어서지요
   시간이 나시는 분들이 계시면 한번 쯤 도전해 보시기 바랍니다.
   왜 동작하지 않는지 ....

4. sleep,usleep 함수

    프로그램을 작성하다보면 일정 시간을 멈출 필요가 있을때가 있읍니다.
    이럴때 사용할수 있는 함수는 두가지가 있읍니다.

    하나는 sleep 이고 또 하나는 usleep 입니다.

    두 함수다 모두 프로세스를 잠들게 합니다.
    sleep 는 초 단위 간격으로 잠들게 하고 usleep 는 마이크로초 즉 0.000001 단위로 잠들게 제어할수있습니다.

    그...러...나

    usleep 이 정말 0.000001 초 단위로 잠들게 할수 있는냐?

    아니죠.. 왜냐?

    프로세스가 잠들기 때문이죠....

    제 실험 결과는 19 미리 초 이하 즉 0.019 초 이하로는 잠들게 할수 없읍니다.
    무척 슬픈 일이죠...

    이말은 usleep 함수에서 문제가 될수 있읍니다.

    그래서 정확하게 잠들게 하고 싶다면
    ( 물론 하나의 프로세스만 수행되고 있어서 프로세스 스케쥴이 전환되지 않는다는 가정하에서 )

    gettimeofday 함수를 이용하여 반복문을 사용하는 것이 더 정확합니다.
   
    sleep 함수와  usleep의 형식은 다음과 같습니다.

       #include <unistd.h>

       unsigned int sleep (unsigned int seconds);
       void         usleep(unsigned long usec);

    sleep 함수를 사용하는 예제는 다음과 같습니다 .

    printf( "sleep start\n" );
    sleep(2);
    printf( "sleep end\n" );


    usleep 함수를 사용하는 예제는 다음과 같습니다 .

    long int old,cur;

    mclock_init();
    printf( "usleep start\n" );
   
    old = mclock();
    usleep(1);
    cur =  mclock();
    printf( "mclock : %ld m Sec \n", cur - old );
    printf( "usleep end\n" );

5. RTC 와 시간 설정 및 관리

   이제 시간과 관련한 마지막 문제인 RTC 라고 불리는 시계칩과
   리눅스 시간설정과 관리에 대한 문제에 대하여 이야기 해야 겠습니다.

   임베디드 시스템에서는 시간 설정은 반드시라고 할 만큼 일어나는 문제 입니다.

   그런데

   임베디드 리눅스 응용 프로그램을 작성하는 프로그래머가 부딪히는 문제 중 하나는
  
   RTC라고 하는 시계 칩과 리눅스 시스템의 시간과의 동기화 문제와
   시간이 변경될때 뜻하지 않는 여러가지 문제점에 봉착한다는 것입니다.

  이 문제를 하나씩 따져 봅시다.

5.1 RTC 와 시스템 시간 관리

   보통 임베디드 시스템에는 RTC 라는 시계 칩을 가지게 됩니다. CPU에
   포함되었든 외부장치에 붙었든 이 칩은 시스템에 전원이 나가도
   동작합니다.

   ESP-NS 보드에는 DS1307 이라는 RTC 를 사용합니다.

   자 여기서 질문이 있읍니다.

   리눅스 커널에서 사용하는 시간은 어디에 기준을 맞출까요?
  
   1) RTC와 연동되어 시간을 관리한다.
   2) OS 타이머를 이용하여 관리한다.
   3) 내부 타이머를 이용하여 관리한다.

   예...

   정답은 2 번이 되겠읍니다.

   이말은 여러분이 아무리 date 명령을 사용하여 리눅스 시간을 변경해도
   보드 전원을 내렸다 재 부팅하면 말짱 꽝이라는 말입니다 ㅜㅜ

   물론 RTC를 커널 소스에서 연결한다면 180 초 단위로
   ( 정확히 맞는지 오래 전에 기억했던 것이라 자신없음 ^^ ) 로 동기를 시킬 겁니다.

   하지만 ESP-NS 나 기타 대부분의 임베디드 보드의 커널에는 동기 처리가 빠진것으로 알고 있습니다.

   리눅스 시스템은 자주 껐다가 켜졌다가 하는 시스템으로 사용되기 보다는 계속 켜져 있는 시스템으로
   적합한 시스템입니다.

   또한 RTC는 생각보다 오차가 큰 시계입니다.
   전원에도 영향을 받고 온도에도 영향을 받습니다. (물론 미미합니다만 ㅜㅜ )

   그에 반해서 대부분의 시스템에서 사용하는 OS 타이머는 분해능이 높기 때문에
   무척 높은 정확도를 가집니다.

   그래서 리눅스 시스템은 RTC를 믿지 않고 자신이 계산하는 시간을 신봉합니다.

   즉.....

   리눅스 시스템은 주기적으로 자신의 시간으로 RTC를 설정할 필요가 있는 겁니다 .

   RTC의 시간을 가져와서 자신의 시간과 맞추는 경우는 부팅할때 딱! 한번 뿐입니다.

   그 외에는 RTC를 못 믿기 때문에 리눅스 커널 시간을 RTC에 주기적으로 설정하는 것이
   맞는 방법이라는 것입니다.

   물론 RTC의 오차라는 것이 몇년에 몇초 정도의 오차이기 때문에 아주 크리틸컬한
   시스템이 아니라면 굳이 동기화 시킬 필요는 없읍니다
   그래서 저는 동기화 처리 프로세스 프로그램을 따로 만들지는 않는 매우 게으른
   프로그래머입니다. ( 아.. 이 성격 고쳐야 하는데 쩝 )

   그...러...나

   외부에서 또는 사용자의 조작으로 인하여 시간을 수정한다면
   당연히 시간을 설정하는 함수 또는 유틸리티를 알고 있어야 합니다.

   자...

   그런데 앞에서 이야기 했지만 이럴 경우 date 명령이나  settimeofday 함수를 이용해 봤자
   RTC의 시간은 수정되지 않습니다. 그래서 전원을 내렸다가 올려 버리면 말짱 꽝입니다.

   그렇다면 이것을 어떻게 해결해야 할까요?

   우선 보드가 부팅 되었을때 RTC 에서 시간을 가져와서 시스템 시간을 설정하도록 해야 겠지요?

   가장 쉬운것은 보드에 hwclock 라는 프로그램이 있을 경우입니다. busybox에서 옵션으로
   이 프로그램을 선택할수 있을 겁니다.

   그런데 제가 게으른 관계로 ESP-NS 에 있는 파일 시스템에는 hwclock 이라는 유틸리티가
   탑제 되어 있지 않습니다.

   그래서 저희는 아주 무식하게 ds1307 디바이스 드라이버를 수정해서 다음과 같은 형식을
   이용하면 RTC에서 시간을 가져 오도록 하고 있읍니다. 이 것은 ESP-NS 에서만 사용할수
   있는 방법입니다. 다른 시스템에서는 사용할수 없음에 주의 하세요...

    /etc/rc.d/rc.local 에 다음 명령이 추가되도록 수정하면 됩니다.

    if [ -f /proc/driver/ds1307 ]; then
        echo "sync" > /proc/driver/ds1307
    fi

    이러면 알아서 동기화 시켜 줍니다.

    그렇다면 이런 비표준적인 방법 말고 리눅스에서 사용 가능한 방법은 무엇일까요?

    다음 소스 예처럼 함수를 하나 만들어서 부팅시에 실행 시켜 주면 됩니다.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <time.h>
    #include <sys/time.h>
    #include <linux/rtc.h>

    void check_rtc_devicefile( void )
    {
        if( access( "/dev/rtc" , F_OK ) == 0 ) return;
   
        mknod( "/dev/rtc" , S_IRWXU|S_IRWXG|S_IFCHR,(10<<8|(135)));
    }
   
    void rtc2systemtime( void )
    {
        int              rtc;
        struct rtc_time  rtc_time_data;
        struct tm        tm_src;
        struct timeval   tv = { 0, 0 };
   
        check_rtc_devicefile();
   
        rtc = open ( "/dev/rtc", O_RDONLY );
        if( rtc < 0 )
        {
            perror( "/dev/rtc open error" );
            return;
        }
   
        if ( ioctl ( rtc, RTC_RD_TIME, &rtc_time_data ) < 0 )
        {
            perror( "/dev/rtc rtc read time error" );
            close( rtc );
            return;
        }
   
        tm_src.tm_year = rtc_time_data.tm_year;
        tm_src.tm_mon  = rtc_time_data.tm_mon;
        tm_src.tm_mday = rtc_time_data.tm_mday;
        tm_src.tm_hour = rtc_time_data.tm_hour;
        tm_src.tm_min  = rtc_time_data.tm_min;
        tm_src.tm_sec  = rtc_time_data.tm_sec;
   
        tv.tv_sec = mktime( &tm_src );
   
        settimeofday ( &tv, NULL );
    
        close( rtc );
   
    }

    check_rtc_devicefile 함수는 /dev/rtc 라는 장치파일을 체크해서 없으면 만들어 주는 함수입니다.
    rtc2systemtime 함수가 실제로 RTC에서 시간을 읽어서 시스템 시간으로 설정하는 함수입니다

    소스를 보시면 아시겠지만 ioctl 함수를 이용하여 /dev/rtc에서 RTC 시간을 얻어 온 후
    mktime 함수를 이용하여 초로 환산한후  settimeofday 함수를 이용하여 시스템 시간을 설정합니다.

    자....

    이제 반대로 설정하는 함수를 알아 봅시다.

    나중에 사용하기 편리하게 시스템 시간 설정과 RTC를 한꺼번에 설정하도록 만들면
    다음과 같이 됩니다.

    int set_systemtime( int year, int mon, int day, int hour, int min, int sec )
    {
   
        int              rtc;
        struct rtc_time  rtc_time_data;
        struct tm        tm_src;
        struct timeval   tv = { 0, 0 };
   
        tm_src.tm_year = rtc_time_data.tm_year = year - 1900;
        tm_src.tm_mon  = rtc_time_data.tm_mon  = mon  - 1;
        tm_src.tm_mday = rtc_time_data.tm_mday = day;
        tm_src.tm_hour = rtc_time_data.tm_hour = hour;
        tm_src.tm_min  = rtc_time_data.tm_min  = min;
        tm_src.tm_sec  = rtc_time_data.tm_sec  = sec;
   
        tv.tv_sec = mktime( &tm_src );
   
        settimeofday ( &tv, NULL );
       
        check_rtc_devicefile();
   
        rtc = open ( "/dev/rtc", O_WRONLY );
        if( rtc < 0 )
        {
            perror( "/dev/rtc open error" );
            return -1;
        }
   
        if ( ioctl ( rtc, RTC_SET_TIME, &rtc_time_data ) < 0 )
        {
            perror( "/dev/rtc rtc write time error" );
            close( rtc );
            return -1;
        }
   
        close( rtc );
   
       return 0;
   
    }

    이렇게 함수를 이용하여 시간 설정 프로그램을 짜기 싫다면 당근 hwclock 유틸리티를 이용하여
    설정하면 되는데 설명하기 귀찮으므로 여러분이 알아서 하십시오 ( 메롱... )

    이젠 마지막으로 아주 중요한 문제를 제기하고 여러 고수분들에게 해답을 구하면서
    마치도록하겠읍니다.

    여러분이 여러장치를 다루기 위해서 다중 입출력 프로그램을 이용하거나 이벤트 구조형태의
    프로그램을 작성하는 경우 select 나 poll 함수를 사용하게 됩니다.

    그런데 이 함수에는 타임 아웃을 설정하도록 하고 있는데
    시스템 시간 설정을 하는 프로세스가 따로 있다면 잘못하면 타임 아웃이 무한대로 걸리지
    않을 경우가 있습니다.

   이 버그를 만나면 아주 심각한데 이런 경우의 한 예로 다음과 같은 시스템을 구성할 경우에
   해당합니다.

   여러분이 아주 까다로운 고객이나 시간에 매우 크리티컬한 장치를 만들어야 하는 경우에
   타임서버나 GPS 를 이용하여 1초 간격으로 시간을 받아서 시스템 시간과 틀릴 경우에는
   자동으로 시간을 수정하도록 하는 요구를 받았다고 합시다.

   이런 경우 1초마다 시간을 설정하면 아주 치명적인 문제가 발생하는데 아까 이야기 했던
   select나 poll 기타 시간을 기다리는 부분에서 무한 대기가 발생하는 경우가 있다는 겁니다

   이런 경우 저는 전체 시스템에 시간을 처리하는 변수를 두고 현재 시스템 시간과 외부에서 강제로
   정해지는 정확한 시간간에 차를 구한 후 이를 사용하는 방법을 사용하여 가급적이면
   시스템 시간을 바꾸지 않도록 프로그램을 작성합니다. 이 방법도 완전한 처리 방법은 아닌것으로
   알고 있읍니다.

   이런 방법이외에 adjtime 이라는 함수를 사용하는 방법이 있는데 이 함수는 시간을 아주 천천히
   맞추어 주지요....

   하지만 이 함수는 제가 쓴 적이 없으므로 이글을 읽는 분중에서 고수분이 계시면 저에게
   사용 방법을 알려 주셨으면 하네요...

   그외 위에서 제기한 문제를 어떻게 효율적으로 수정하는지 알려 주셨으면 고맙겠읍니다.

   그럼 .... 시간과 관련하여 임베디드 리눅스에서 어떻게 다루는가에 대한 글은 이것으로 마치려 합니다.
첨부 파일: time.zip
--------------------------------------------------------------------------------
답장(2)

KELP - Korea Embedded Linux Project




[kernel]플래쉬 메모리 컨트롤

제목: 하드웨어 강좌 (5)

글쓴이: 푸지
글쓴날: 2002년 04월 19일 오전 11:28
URL : http://kelp.or.kr/korweblog/?story=02/04/19/3325938

플래시 메모리 쉽게 끝내기 (2)

*플래시메모리 읽고 쓰기


오랜만에 강좌를 올립니다. 먹고 살려니 어쩔수가 없군요..
이번강좌는 플래시를 직접 제어하겠습니다. 물론 말로만..


2.2.0 스트라타 플래시 읽기
넘쉽다. 기냥주소 주고 읽는다. 메모리하고 같다.

2.2.1 스트라타,부트블럭 플래시 ID 읽기
모든 플래시의 제어에 있어 ID 읽기가 기본이다. (읽어온 값을 미리
알고 있으니까..)
이것만 읽는다면 하드웨어의 연결이나 타이밍은 95퍼센트 완벽하다고
믿을수 있다. 이후는 소프트웨어에서 해결할 문제만 남는다.

모든 플래시는 각각의 고유한 ID가 있다. 우선 제조사번호와 디바이스
번호(크기,타입)등이 반드시 있다.

스트라타 메뉴얼의 Table-4 (12-page)를 보면 Command가 나와있다.
부트블럭은 Table-5 (15-page)이다.
그중 Read Identifier Codes 란 명령어를 보자 해석하면 기냥 ID읽기 이다.

#define BASE_ADDR
unsigned short *fadr; // 플래시의 주소

처음 할일은 플래시의 상태를 특별한 상태로 변경하는 것이다.
명령에 따라 상태는 변경되며 명령이 끝나면 다시 정상상태(읽기)로
바꾸어 준다. 다시한번 설명하면 최초의 플래시 상태는 읽기만 가능한
상태이다, 플래시이기 때문에 쓰는 동작은 금지되어 있다.

ID읽기 커멘드는 0x90이다.

//=== 명령어 ====//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x90; // command Read ID

커멘드 줬으면 ID를 읽어보자

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+0*2;// 제조사코드가있는 주소
(제조사)= *fadr;

fadr = BASE_ADDR+1*2;// 플래시의 디바이스코드가 있는 주소
(코드) = *fadr; // 코드값을 보고 크기를 분석한다.

커멘드를 끝낸다.(반드시 이동작을 해야만 한다.)

//=== 명령 끝 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0xff; // 읽기모드로 변경한다.

너무 쉽다. 여기까지는..

!!!!!!!! 주의 !!!!!!!!!!!!

주의) BASE_ADDR + offset * 2 라는 것을 주의하자
offset은 메뉴얼에 명시된 주소이다. 이것에 2를 곱하는 이유는
메뉴얼에 나온 주소는 Word 단위의 주소이기 때문이다.


2.2.2 스트라타 플래시 정보읽기
이명령은 플래시의 쓰기버퍼크기, 지우기 시간, 쓰기(퓨징)시간,
이때 소모되는 전류와 필요한 전압등이 기재되어 있다.

//=== 명령어 ====//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x98; // command Read Query

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+0x27*2;// 플래시의 정확한 크기가 있는 영역
(크기) = 2^(*fadr); // 16MByte 일경우 2^24

fadr = BASE_ADDR+0x2A*2;// 쓰기버퍼의 크기가 있는 영역
(버퍼크기) = 2^(*fadr); // 2^5 = 32

//=== 명령 끝 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0xff; // 읽기모드로 변경한다.


폼나는 프로그램을 작성하려면 이것을 읽어 사용한다.
하지만 여기서는 사용하지 않는다. (시간이 없어서.. ^^)
우리는 스트라타 또는 부트블럭 플래시라는 것과 위에서 읽은 디바이스
크기만을 가지고 작성한다.
스트라타이면 버퍼의 크기는 32-Byte(16-Word) 이다.
부트블럭이면 1-word 이다.


2.2.3 플래시 블럭 쓰기금지/쓰기금지해제
플래시에 데이타를 쓴다는 것은 지워지지 않는 데이타가 필요하기 때문이다.
함부로 지우지 못하도록 하드웨어 핀으로 안전장치를 했으며 소프트웨어적으로는
블럭마다 락(LOCK)을 걸수있다.
플래시를 쓰거나 지우기위해서는 이 락을 풀어야 한다. 물론 락이 걸려있지
않다면 풀이유는 없다.

이것에 대한 사용예는 2.2.4에서 다룬다.

락을 푼이후에 데이타를 쓴후 다시 락을 걸면 데이타가 임의로 지워지는 것을
막을수 있다. 만일 락을 걸고 MTD를 올리면 이 영역은 쓰기가 금지된다.


2.2.4 플래시 블럭 지우기
쓰기위해서 지워야 하는것은 누구나 알고있는 사실이다.
스트라타 플래시 블럭의 크기는 128KByte 이다.
숫자로 보면 0x20000 로 증가하는 주소이다.
부트블럭은 0x10000 증가한다.(64KByte)

부트블럭에서 주의할점은 처음블럭 또는 마지막블럭이 8개로 나누어져 있다는
점이다. 부트블럭 Bottom의 경우
0x00000 0x02000 0x04000 0x06000 .... 0x0E000 // 8개 블럭
0x10000 // 9번째 블럭
0x20000 // 10번째 블럭
:


#define BA // 플래시 블럭주소

블럭에 락이 걸려있는지 확인한다.

//=== 명령어 ====//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0x70; // command Read Status

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+BA+2*2;// 락정보가 있는 주소
(락정보) = *fadr; // 락정보의 d0 비트가 1이면 락이 걸린것이다.

락이 걸려있으면 락을 푼다.

//=== 명령어 ====//
fadr = BASE_ADDR+BA; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x60; // command Set Block Lock-Bit

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0xd0; // 락을 푼다.

//=== 명령 수행이 끝났는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
while (1)
{
// status 레지스터 d7 비트 확인
if (0x0080 & *fadr) break; // SR.7 = 0 이면 busy
}

SR 이라는 것은 status 레지스터를 말하며 위의 코드는 status 레지스터
첫번째 바이트를 읽은 것이다. status 레지스터를 읽기 위해서는 0x70 명령을
줘야 하지만 지우기,쓰기,락 등의 명령를 준 이후에 읽기를 하면 status
레지스터를 읽을수 있다.

이제 블럭을 지운다.

//=== 명령어 ====//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0x20; // command Block Erase

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0xd0; // command Confirm

//=== 명령 수행이 끝났는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
while (1)
{
// status 레지스터 d7 비트 확인
if (0x0080 & *fadr) break; // SR.7 = 0 이면 busy
}

//=== 상태를 읽어 성공했는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
SR = *fadr;

// SR.3 = 0 프로그램전압 OK
// SR.1 = 0 unlock
// SR.5 = 0 지우기성공
if ( ( SR.3 == 0 ) && ( SR.1 == 0 ) && ( SR.5 == 0 ) )
{
지우기 성공
}
else
{
지우기 실패
}

//=== 상태비트 클리어 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x50; // command Clear Status

//=== 명령 끝 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0xff; // 읽기모드로 변경한다.

지우기 동작은 while 문에서 최대 1초정도 대기하므로 멀티수행을 고려해 본다.
아울러 시간초과 루틴을 필요로 한다.


2.2.4 플래시에 1바이트 또는 1워드 쓰기
쓰기버퍼가 없다면 한개의 워드씩 다음과 같이 써야한다.
스트라타 플래시도 한개씩 쓸수 있다.

#define PA // 쓰고자 하는 주소
#define DA // 쓰려는 데이타

//=== 명령어 ====//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x40; // command Write Word/Byte

//=== 명령어에 따른 부가적인 행동 ===//
fadr = BASE_ADDR+PA; // 쓰고자 하는 주소
*fadr = DA;

//=== 명령 수행이 끝났는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
while (1)
{
// status 레지스터 d7 비트 확인
if (0x0080 & *fadr) break; // SR.7 = 0 이면 busy
}
// 위의 while 문은 최대 2msec 소요된다.

//=== 상태를 읽어 성공했는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
SR = *fadr;

// SR.3 = 0 프로그램전압 OK
// SR.1 = 0 unlock
// SR.4 = 0 프로그램 성공
if ( ( SR.3 == 0 ) && ( SR.1 == 0 ) && ( SR.4 == 0 ) )
{
프로그램 성공
}
else
{
프로그램 실패
}

//=== 상태비트 클리어 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x50; // command Clear Status

//=== 명령 끝 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0xff; // 읽기모드로 변경한다.

여러개의 테이타를 쓰기위해서는 위의 루틴을 반복한다.
속도가 느리겠죠...


2.2.5 스트라타 플래시에 한번에 32바이트 쓰기
스트라타만이 이 명령을 지원한다.

#define LEN 16 // 쓰기버퍼최대 크기
unsigned short buff[LEN];// 데이타버퍼
int wdcnt; // 워드데이타 카운트

//=== 명령어 ====//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0xE8; // command Write Word/Byte

//=== 명령 수행이 끝났는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
while (1)
{
// status 레지스터 d7 비트 확인
if (0x0080 & *fadr) break; // SR.7 = 0 이면 busy
}

//=== 버퍼에 쓸 갯수를 알려준다 ===//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
wdcnt = 16; // 임의로 최대값을 넣었다.
wdcnt --; // 0 이면 한개의 워드이다.
*fadr = count;

버퍼의 크기는 32-byte 이므로 16-word 가 된다.
그러므로 wdcnt 는 0 ~ 15 사이의 값이 된다.

//=== 카운트만큼 쓴다 ===//
fadr = PA; // 쓰려는 첫번째 주소
for (idx=0; idx<wdcnt; idx++)
{
*fadr = buff[idx];
fadr += 2; // 16비트 버스이므로 2씩 증가한다.
}

//=== 프로그램 명령을 준다===//
fadr = BASE_ADDR+BA; // 플래시의 블럭주소
*fadr = 0xd0; // command Confirm

//=== 명령 수행이 끝났는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
while (1)
{
// status 레지스터 d7 비트 확인
if (0x0080 & *fadr) break; // SR.7 = 0 이면 busy
}
// 위의 while 문은 최대 2msec 소요된다.

//=== 상태를 읽어 성공했는지 확인 ===//
fadr = BASE_ADDR; // status 레지스터 첫번째 주소
SR = *fadr;

// SR.3 = 0 프로그램전압 OK
// SR.1 = 0 unlock
// SR.4 = 0 프로그램 성공
if ( ( SR.3 == 0 ) && ( SR.1 == 0 ) && ( SR.4 == 0 ) )
{
프로그램 성공
}
else
{
프로그램 실패
}

//=== 상태비트 클리어 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0x50; // command Clear Status

//=== 명령 끝 ===//
fadr = BASE_ADDR; // 플래시의 CS가 움직이는 영역이면 된다.
*fadr = 0xff; // 읽기모드로 변경한다.

여러개의 테이타를 쓰기위해서는 위의 루틴을 반복한다.
다시한번 쓰기 위해서는 블럭이 지워져 있어야 한다.

2.2.6 기타의 명령어들
그외에 몇개의 명령이 있지만 설명하지 않겠다.
메뉴얼을 보면 나와있으니 필요하면 참고한다.


이번 강좌에 기술된 내용은 모두 메뉴얼에 나온것이다. 메뉴얼 뒷쪽에 플로워
챠트가 있으니 확인한다.
인텔플래시가 아닌 다른 플래시도 이와 비슷한 형식을 따른다.
직접 제어해보지 안는 이상 확실한 이해는 어려울 것이다. 시간이 되면 직접
코딩하여 플래시에 데이타를 쓰자,


듣고싶은(?) 강좌가 있다면 답글을 주기 바랍니다.. 아는 내용이면 이후의 강좌에
포함하도록 하죠..
으 2시간 걸렸다..... ㅡ.ㅡ


다음 강좌는 NAND형에 대해 알아보겠음당

[소유권]
이 문서 지적 소유권은 (주)제이닷디엔티에 있다.
문서에 관련된 문의 사항이 있다면 freefrug@falinux.com으로 연락 바란다

[kernel]램디스크 or MTD ?

제목: [응용] 램디스크를 쓸것인가? MTD를 쓸것인가?

글쓴이: 유영창
글쓴날: 2004년 09월 08일 오후 10:57
URL : http://kelp.or.kr/korweblog/?story=04/09/08/1828786


APK003 램디스크를 쓸것인가? MTD를 쓸것인가?
==============================================

1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램을
실장하는 파일 시스템으로 램디스크를 사용할것인지
아니면 MTD를 사용할 것인지를 결정할때 도움을
주기 위한 문서입니다.

작성자 : 유영창 frog@falinux.com
작성일 : 2004년 9월 6일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

없음

2. 파일 시스템

응용 프로그램이 동작하기 위해서는 파일 시스템이 반드시 필요합니다.

윈도우즈 프로그램만 하시던 분들은 이 파일 시스템에 대한 관심이
필요한 시점은 딱 한번! 윈도우 운영체제 설치시에만 필요합니다.
NTFS 라는 이름을 가지는 것이 바로 그것입니다.
(물론 설치가 빈번해지면 이 한번이 여러번 되죠 ㅜㅜ)

그래서 파일 시스템이 왜 필요한지도 모르고 살아갑니다.
(부럽습니다.)

그러나 임베디드 시스템에 리눅스를 사용하고 응용 프로그램을
적재하는 과정에서는 이 파일 시스템에 대한 이해가 필요합니다.
더불어 램디스크나 MTD와 같은 블럭 디바이스 드라이버에 대한 이해도
필요합니다.

솔찍히 저도 윈도우 프로그램만 작성하다가 리눅스로 넘어 오면서
뭐 이딴것을 알아야 하는가에 대한 고민도 했읍니다.

하지만 어쩔 수 없이 알아야 하는 내용입니다.

우리는 두가지 개념에 대하여 이해가 필요합니다.

첫번째는 저장 매체를 다루는 블럭 디바이스 드라이버와 관련된
내용입니다.

두번째는 저장 매체에 파일을 저장하고 관리할 수 있는 파일시스템
에 대한 내용입니다.


2. 블럭 디바이스 드라이버

임베디드 시스템은 전원이 소실되어도 데이터를 지속적으로 보관하거나
보드에서 동작하는 응용프로그램( 펌웨어라고도 합니다. )을 담고 있는
물리적인 매체가 필요합니다.

이런 저장 매체에는 ROM을 사용하기도 하고 RAM을 사용하기도 하고 플래쉬
메모리를 사용하기도 하고 하드 디스크를 사용하기도 합니다.

그러나 이 글은 ESP 보드에서 동작하는 응용 프로그램울 작성하는 방법에
대한 내용이므로 RAM 과 플래쉬 메모리와 관련된 이야기만 하겠읍니다.

여기서 의문을 제기하시는 분이 있을 겁니다.

"RAM 이라니? 그건 전원이 나가면 내용이 소실되는데?"

맞습니다. RAM은 전원이 나가면 내용이 소실됩니다.
그러나 이야기 전개상 필요하므로 넘어 갔으면 좋겠읍니다.
( 싫으면 이 글을 더 이상 읽지 마세요.. 쩝 )

리눅스에서 저장 장치를 다루는 디바이스 드라이버가 블록 디바이스 드라이
버 입니다. 물론 이 저장 장치라는 것은 소프트웨어 관점으로 보았을때
하드 디스크와 같은 특성을 가지는 것들을 말합니다.

임베디드 리눅스에서 블록 디바이스 드라이버로 주로 사용되는 것에는
램디스크,MTD 가 있읍니다.

램디스크는 램을 이용해서 하드 디스크를 흉내내는 것입니다.
MTD는 플래쉬 메모리를 이용해서 하드 디스크를 흉내 내는 것입니다.

램디스크는 보드에 장착된 램을 이용합니다.

MTD는 NOR 나 NAND 플래쉬 메모리를 이용하여 구현합니다

여기서 잠깐 저장 장치로서 NOR 와 NAND 플래쉬중 어떤 것이 우수한가를
살펴 봅시다.

제 경험상 NOR 계열의 플래쉬보다는 NAND 계열의 플래쉬가 저장장치로
사용하기에는 더 좋습니다. 데이터를 써 넣는 속도가 더 빠르고
데이터를 읽어 오는 속도나 용량 대비 단가가 더 싸고 안정적입니다.

하드웨어 설계자 입장에서 보면 NAND 계열의 플래쉬보다는 NOR 계열의
플래쉬가 더 유리합니다. 왜냐하면 부팅 롬으로 바로 이용이 가능하고
부가 회로가 별로 필요 없기 때문입니다.

대량 양산 보드에서는 가격적인 문제라면 NAND가 유리합니다. 반면
양산 생산 관리에서는 NOR가 유리합니다

ESP 보드는 NAND 플래쉬를 사용한 저장방식입니다
(저희 회사 보드들이 다 그렇죠... 다 경험의 산물입니다. )

아무래도 이 글은 ESP 와 관련된 보드를 중심으로
설명하는 것이므로 NAND 플래쉬 관점에서 이야기 해야 겠죠...

어찌 되었든 ESP 보드는 두가지 블럭 디바이스 드라이버를
사용할 수 있읍니다.

1) 램을 이용한 램디스크 시스템
2) NAND 플래쉬를 이용한 MTD 시스템

ESP 보드는 처음 판매될 때 램디스크를 이용하고

/app 디렉토리에 NAND 플래쉬에 접근할수 있는
MTD 시스템이 마운트되어 있읍니다.

이 두가지 장치는 모두 동시에 사용 가능합니다.

3 파일 시스템

램디스크나 NAND 플래쉬를 이용한 MTD가 있다고 해서
파일을 읽거나 쓸수는 없습니다.

왜냐하면 해당 저장 장치에 어떤식으로 파일을 기록해야
하는 방법이 없기 때문입니다

즉 블록 디바이스 드라이버라는 것은 단순히 섹터단위로
데이터를 어떻게 기록하고 읽는 방법만 제공하기 때문입니다.

그러나 파일을 읽고 쓰기 위해서는 디렉토리도 관리해야 하고
파일 명이나 기타 정보도 관리해야 합니다.

이렇게 섹터단위로 읽고 쓸수 잇는 장치에 파일을 저장하고
읽도록 하는 것이 파일 시스템입니다

리눅스는 이런 파일 시스템으로 사용할수 있는 것은 여러가지가
있읍니다. 가장 대표적인것인 EXT2 라는 파일 시스템입니다.

ESP 보드에서는 EXT2 파일 시스템은 램디스크에 사용하고 있습니다.
PC 의 하드 디스크에는 최근에는 EXT3 가 가장 많이 사용되고 있습니다.
(배포판이 이 파일 시스템을 사용하기 때문이 가장 큰 이유죠 )

이외에도 ESP 보드에서는 YAFFS 파일 시스템을 사용합니다.
이 YAFFS 파일 시스템은 리눅스에 정식으로 포함되어 있지 않습니다.
하지만 NAND 파일 시스템에 사용하기에는 제 경험상 이놈이 딱! 입니다.

NAND 플래쉬나 NOR 플래쉬에 사용되는 것으로는 JFFS2 가 있읍니다.
NOR 플래쉬라면 아무래도 JFFS2 가 더 좋습니다.

ESP 보드에는 YAFFS 를 사용하기 때문에 JFFS2 에 대한 이야기는 하지
않겠지만 여러분에게 주의 사항은 이야기 하고 싶습니다.

JFFS2는 치명적인 결함이 있습니다. ( 지금은 고쳐졌는지 잘 모르겠읍니다. )
JFFS2에 사용하는 파티션이 2에 지수 단위로 나누어 지지 않으면
지속적인 파일을 기록하는 경우에는 리눅스 시스템이 죽어 버립니다.
원인은 저도 잘 모르겠읍니다. ㅜㅜ
또 플래쉬의 크기가 커지면 시스템 메모리를 많이 사용해 버립니다.
32M 정도의 NOR 플래쉬라면 큰 문제는 없지만 그 이상의 메모리를 사용하게 되면
문제가 될 소지가 있읍니다.

이 점은 JFFS2 파일 시스템을 사용하시는 분들은 조심하시기 바랍니다.

어쨌든 여기서 파일 시스템을 정리하면 다음과 같습니다.

RAM : 램디스크 : EXT2
NAND 플래쉬 : MTD : YAFFS

이것이 ESP 보드에 사용되는 파일 시스템의 구성 정리 입니다.


4. 램디스크와 램디스크 이미지

램디스크는 램에 하드 디스크처럼 저장을 할수 있도록 합니다.

그..런..데

램은 전원이 나가면 소실됩니다.

원래 램디스크는 다음과 같은 과정을 거쳐야 사용 가능합니다.

[root@ESP /] mkfs.ext2 /mtd/ram0
[root@ESP /] mount /mtd/ram0 /test

이렇게 하면 /test 디렉토리에 파일을 만들면 램디스크에 데이터를
저장할 수 있읍니다.

그러나 보드에 전원이 나가면 데이터는 소실됩니다.
또 보드가 부팅될 때 램디스크를 루트로 사용하려고 해도
파일 시스템도 안 만들어져 있고 내용도 없기 때문에
루트 파일 시스템으로 사용이 불가능합니다.

그래서 이런 점을 해결하기 위해서 램디스크 이미지라는 것을 사용합니다.

램디스크 이미지는 램디스크에 EXT2 포맷으로 만들고 필요한 파일을
모두 넣은 후에 램 디스크의 내용을 모두 파일 형태로 복사한 파일입니다.
리눅스 커널은 이 파일의 내용이 있는 위치를 지정하면 해당 내용을
부팅시에 램디스크에 모두 옮겨 넣습니다.
이때 커널은 램디스크 이미지가 압축되어 있다고 가정합니다.

ESP 에서는 이지부트가 이 압축된 램디스크 이미지를 특정 램에 복사해
놓고 커널 부팅전에 커널에 이 이미지 데이터가 어디 잇는지를 알려
줍니다.

그래서 커널은 부팅 후 루트로 램디스크 이미지를 사용 할수 있는 겁니다.

5. 램디스크를 사용할 것인가 아니면 MTD에 YAFFS를 사용할 것인가

이제 결론적으로 이 두가지에 대한 결론을 내려 봅시다.

램디스크 이미지를 이용하여 응용 프로그램을 탑재하고 사용할것인가
아니면 YAFFS 파일 시스템을 이용하여 NAND 플래쉬를 이용하여
응용 프로그램을 탑재하고 사용할 것인가..

이 두가지 중 하나를 선택하기 위해서는 각각의 장단점을 알아야 합니다.

5.1 램디스크 이미지를 이용하는 방법의 장점 과 단점

램디스크 이미지를 이용하는 장점은 딱! 두가지 입니다.
그 외에는 장점이 별로 없읍니다.

첫번째 장점은 시스템이 안정적이라는 것입니다.

램디스크는 응용 프로그램이나 기타 등등의 이상이 있을 때
파일 시스템을 박살내더라도 전원만 껐다가 키거나 리부팅이
되면 원위치가 됩니다.

두번째 장점은 많은 수량의 보드를 제작할때 쉽게 전체 시스템을 설치할 수 있습니다.
즉 부트로더 + 커널 + 램디스크 이미지 를 한꺼번에 실장하면 됩니다.

그...러...나

가장 큰 단점은 개발시에 무척 불편하다는 것입니다.
항상 램디스크 이미지를 매번 만들어야 합니다.
(물론 nfs 파일 시스템을 이용해서 개발하고 나중에 한번에 써 넣어도 되죠 )

또 다른 단점은 응용 프로그램의 업그레이드를 하려면 골치아프다는 것입니다.
원격지에서 자동으로 응용 프로그램을 업데이트 하려면 결국
램디스 이미지를 통째로 바꾸어야 하는데 이게 만만한 작업이 아닙니다.


5.2 YAFFS 파일 시스템을 사용할때의 장점 과 단점

YAFFS 파일 시스템을 이용해서 개발할 때 가장 큰 장점은
개발할때 편리하다는 것입니다.

하드 디스크에 저장하는 것과 같은 기분으로 응용 프로그램이나
기타 데이터를 그대로 복사하여 사용하면 됩니다.

/etc/ 나 기타 등등에 필요한 파일을 복사만 하면 전원을 껐다가
켜도 보관이 됩니다.

이에 반해 많은 제품을 양산하기에는 일일히 복사해야 하는 단점이 있고
파일 시스템이 깨지는 경우 ( 거의 없읍니다만 ) 시스템이 사용 불가능해
지는 단점이 있습니다.

5.3 결론

저는 램디스크 이미지 형식을 사용하는 것보다 MTD에 YAFFS 을 이용하기를 권합니다.

개발시에 편하고 생각보다도 안정적이기 때문입니다.
실제로 제가 YAFFS에서 데이터가 깨지는 경우는 본적이 없읍니다.
(하드웨어가 고장나서 깨지는 경우는 보았읍니다. ㅜㅜ )

가장 큰 이유는 저 같은 게으른 사람에게는 개발시에 무척 편리하다는
것입니다.


6. ESP 보드에서 램디스크를 MTD 시스템을 자동으로 바꾸는 명령

ESP 보드는 초기 생산시에 루트 파일 시스템을 램디스크를 이용하도록 되어 있읍니다.

이것을 NAND 플래쉬가 루트가 되도록 하기 위해서는 다음과 같은 명령을 사용합니다.

[root@ESP /]$ cd /root
[root@ESP /root]$ ./mkflashroot

이 명령을 사용하면 루트 파일 시스템을 NAND 플래쉬로 사용하도록 해 줍니다.

이 명령은 스크립트로 다음과 같은 내용을 가지고 있읍니다.

=====[/root/mkflashroot]=================================================
#!/bin/sh
echo "wait about 20sec ..."
cd /
echo "copy /bin"
cp -pfR bin /app
echo "copy /dev"
cp -pfR dev /app
echo "copy /etc"
cp -pfR etc /app
echo "copy /home"
cp -pfR home /app
echo "copy /lib"
cp -pfR lib /app
echo "copy /root"
cp -pfR root /app
echo "copy /sbin"
cp -pfR sbin /app
echo "copy /tmp"
cp -pfR tmp /app
echo "copy /usr"
cp -pfR usr /app
echo "copy /var"
cp -pfR var /app
mkdir /app/proc
mkdir /app/mnt
mkdir /app/mnt/nfs
mkdir /app/mnt/cf
mkdir /app/mnt/mmc
echo "mkdir ...done"

cp -f /root/.fstab /app/etc/fstab
cp -f /root/.rc.local /app/etc/rc.d/rc.local

echo "change EZBOOT->set->Kernel Command"
echo " [noinitrd root=/dev/mtdblock2 console=ttyS02,115200]"
echo " "
=====[/root/mkflashroot]=================================================

무식하죠?

마지막에 표출되는 메세지인

change EZBOOT->set->Kernel Command
[noinitrd root=/dev/mtdblock2 console=ttyS02,115200]

는 이지부트 명령 모드 에서 set 명령을 이용하여 커널에 전달되는 명령 문자열에

noinitrd root=/dev/mtdblock2 console=ttyS02,115200

내용을 포함라는 것입니다.
글쓴이: Anonymous
글쓴날: 2005년 04월 16일 오전 11:53

저희회사에서 메모리 디스크(SSD)를 취급하는데
관심있으면 연락주세요
물론 인터페이스는 하드디스크와 같습니다.
용량은 155GB까지 나오고
레이드0-5까지 가능
하드디스크와 똑같이 씀니다.
017-391-1561
www.bitmicrokorea.com
www.n1yes.co.kr
sjahappy@n1yes.co.kr

로 연락주세요


글쓴이: 조형기
글쓴날: 2004년 12월 19일 오후 08:39

유영창 이사님.
플래시 메모리 인터페이스 강좌를 준비하며 읽고 갑니다.
음... 내용이 좋네요.
강죄 쓸 필요가 없겠는데요. ㅎㅎㅎ


글쓴이: Anonymous
글쓴날: 2004년 11월 19일 오전 11:55

정말 초보자라서 그런지 단번에 이해가 안갔습니다
ㅠㅠ 이글 두여번 읽었습니다
이제야 이해가 가는군요^^*
강의 유익하게 읽었습니다


글쓴이: Anonymous
글쓴날: 2004년 11월 08일 오전 11:05

유영창 님 정말 감사드립니다.
리눅스 포팅하는데.. ram disk가 뭔지 몰라서 고생했습니다.
감사합니다.....


글쓴이: Anonymous
글쓴날: 2004년 09월 10일 오후 12:54

정말 잘 읽었습니다....


KELP - Korea Embedded Linux Project



[커널] mtd 관련 sst

제목: mtd가 안되요

글쓴이: 초보왕
글쓴날: 2002년 09월 25일 오전 11:42
URL : http://kelp.or.kr/korweblog/?story=02/09/25/1740122


안녕하세요
mtd에 대해서 너무 모르는것이 많아서 질문을 올림니다.
우선 지금 제 앞에 있는 보드는
MPC850 cpu에 SST39VF040 128K를 부트롬으로 쓰고요
SST39VF160 2M를 두개부처서 4M를 커널 이미지와 루트파일을
initrd형태로 넣어 사용합니다. 램디스크로 부팅하고요
4M에서 남는 여분을 mtd를 써서 사용하고 싶은데요
제가 이쪽으로는 아는게 아무것도 없어서 많은 가르침 부탁드립니다.

소스를 쭉보니까 drivers/mtd 에 관련된것들이 있는거 같은데요
physmap.c 와 tqm8xxl.c 를 많이 쓰는거 같은데 뭐가 다른건지
모르겠어요
그리고 CFI와 JEDEC의 차이점도 모르겠거던요 어떤걸 쎠야하느지도
감이 안잡히고요 jedec.c와 jedec_probe.c에 칩 정보가 없길래
자료를 보면서 넣어보긴 했거던요.. 흠..
그리고 menuconfig할때 mtd설정을 모르겠어요
호스트 pc는 86계열이고 타겟은 ppc니까 빅엔디안 으로 하는게 맞는지..
2M짜리 칩을 두개써서 32bit 4M로 만들었는데 buswidth와 interleave는 어떻게 잡아야하는지..
SST칩에대한 옵션은 하나도 없는데 AMD껄로 해도 되는지..
주소가 부트롬이 E0000000이고 bank2가 E1000000으로 시작하는데 CFI Flash
physical memory map에다가 어떻게 써넣어야 하는지..
전혀 모르겠어요..
그래서 SST39VF160과 mtd로 검색한 결과.. 이런 말이 있더라고요

Add the device ID and Size info to jedec_probe.c
That code should handle the 55/aa bits already
- dwmw2

그래도 감이 안잡혀요.. 답변좀 해주새요~~~~
글쓴이: 초보왕
글쓴날: 2002년 10월 01일 오전 09:34

답변 감사드립니다.
지금 저의 상태는

jedec_probe.c 에

mfr_id: MANUFACTURER_SST,
dev_id: SST39VF160,
name: "SST 39VF160",
DevSize: SIZE_2MiB,
CmdSet: P_ID_AMD_STD,
NumEraseRegions: 1,
regions: {ERASEINFO(0x1000,512),
//ERASEINFO(0x10000,32),}
이부분을 추가했고요
drivers/mtd/chips/에있는 jedec_probe.c과 cfi_cmdset_0002.c를
수정하며 결과를 보고 있습니다.
unlock에 관한것은 잘은 모르지만..
unlock1 에 0x5555를, unlock2에 0x2aaa를 넣어주면 동작은 되는것
같던데요..

jedec_probe.c에서는

//cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, CFI_DEVICETYPE_X8, NULL);
//cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, CFI_DEVICETYPE_X8, NULL);
cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, CFI_DEVICETYPE_X16, NULL);
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, CFI_DEVICETYPE_X16, NULL);

이렇게 8을 16으로 바꾸니까 잡히더군요.. 왜그런지는 모름니다.. 우선 잡히는게 급했지요.

그래서 나온 커널 메시지는

MTD Sharp chip driver <ds@lineo.com>
physmap flash device: 400000 at e1000000
Search for id:(bf 56) interleave(2) type(1)
Probe at addr_unlock1(0x00000000 + 0x00555555) past the end of the map(0x003fffff)
Search for id:(bf 2782) interleave(2) type(2)
Found: SST 39VF160
Physically mapped flash: Found 2 x16 devices at 0x0 in 16-bit mode
number of JEDEC chips: 1
mtd: Giving out device 0 to Physically mapped flash
Using physmap partition definition
Creating 3 MTD partitions on "Physically mapped flash":
0x00000000-0x000c0000 : "kernel"
mtd: Giving out device 1 to kernel
0x000c0000-0x003c0000 : "initrd"
mtd: Giving out device 2 to initrd
0x003c0000-0x003f0000 : "User FS"
mtd: Giving out device 3 to User FS

이렇게 나오더군요 Giving out device라는 메시지의 뜻이 뭔지 궁금합니다.

cat /proc/mtd 하면
[root@WHITE_STONE admin]# cat /proc/mtd
dev: size erasesize name
mtd0: 00400000 00002000 "Physically mapped flash"
mtd1: 000c0000 00002000 "kernel"
mtd2: 00300000 00002000 "initrd"
mtd3: 00030000 00002000 "User FS"

이렇게 나오고요
파일 시스템을 minix로 쓰려고 하는데 명령을 내리면..
[root@WHITE_STONE admin]# mkfs.minix /dev/mtdblock3
64 inodes
192 blocks
Firstdatazone=6 (6)
Zonesize=1024
Maxsize=268966912

이렇게 나오면서 프롬프트로 빠져 나갑니다. 하지만 파일시스템은 안잡히네요
[root@WHITE_STONE admin]# mount -t minix /dev/mtdblock3 /mnt
VFS: Can't find a Minix or Minix V2 filesystem on device 1f:03.
mount: wrong fs type, bad option, bad superblock on /dev/mtdblock3,
or too many mounted file systems

아래에 있는것은 mkfs.minix와 mount를 할때 커널 메시지 입니다.
mtdblock_open
ok
mtdblock_release
ok
mtdblock_open
ok
mtdblock: read on "User FS" at 0x1000, size 0x1000
mtdblock: read on "User FS" at 0x0, size 0x1000
mtdblock: write on "User FS" at 0x0, size 0x1000
mtdblock: write on "User FS" at 0x1000, size 0x1000
mtdblock_release
mtdblock: writing cached data for "User FS" at 0x0, size 0x2000
ok
mtdblock_open
ok
mtdblock: read on "User FS" at 0x400, size 0x400
VFS: Can't find a Minix or Minix V2 filesystem on device 1f:03.
mtdblock_release
ok
mtdblock_open
ok
mtdblock_release
ok

지금 여기까지 왔는데요 잘 가고 있는것인지도 잘 모르겠습니다.
어떻게 하면 파일시스템을 사용할수 있을까요?


글쓴이: 즐거운이
글쓴날: 2002년 09월 27일 오후 04:42

안녕하세요? 저와 같은 플래시를 사용하시는 군요..
일단.. 저와는 사용하는 CPU가 다르군요.. 저는 arm720T 계열입니다.
아마 많은 점이 다르겠지요?
일단 저는 kernel 버전 2.4.18를 사용했습니다.
저는 구형의 AMD_flash.c를 고쳐서 사용했습니다.
SST_flash.c로 ^ ^,
일단 님이 사용하고 계시는 flash는 많은 부분에서 AMD와
비슷합니다. AMD는 Block단위로만 다루고 있는데.. SST는
Block과 sector를 다 다룰 수 있습니다.
명령을 주는 주소와 명령만 약간 차이가 있습니다.
이점 알고 계시구요.. 아 그리고 칩을 전체를 지우는 명령어도
SST는 가지고 있네요.. 이거 편하던데.. 저에겐요.. ^ ^
block단위이면 32개의 Block으로 크지만.. sector면 약 512개을
사용할 수 있어서 좋습니다. 머 지우는데..조금 시간은 걸리죠..
참 저는 unlock에 대한 부분은 찾질 못했습니다. 혹시
님이 아니면 알려주시면 감사..
mtd_info *amd_flash_probe에서 적당하게..
amd_flash_info table을 만들어 주세요..
sector로 한다면..

mfr_id: MANUFACTURER_SST,
dev_id: SST39LF160,
name: "SST AM29LV160DB",
size: 0x00200000,
numeraseregions: 1,
regions: {
{ offset: 0x000000, erasesize: 0x01000, numblocks: 512 }

이렇게 되겠네요...

아참 만드실 때 주의 이건 chip 하나에
대한 겁니다. size는 0x00200000이 되겠죠? 2M인깐요..
32bit니.. buswidth는 4입니다. 8bit가 1이죠..





[kernel] jffs2를 이용한 부팅시 에러 메세지

jffs2 를 이용한 부팅시 에러 메세지 #


  • 문제

    jffs2_scan_empty(): Empty block at 0x0000fffc ends at 0x00010000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00010000, not first node in block (0x00000000)
    jffs2_scan_empty(): Empty block at 0x0001fffc ends at 0x00020000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00020000, not first node in block (0x00000000)
    jffs2_scan_empty(): Empty block at 0x0002ff48 ends at 0x00030000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00030000, not first node in block (0x00000000)
    jffs2_scan_empty(): Empty block at 0x0004fffc ends at 0x00050000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00050000, not first node in block (0x00040000)
    jffs2_scan_empty(): Empty block at 0x0005fffc ends at 0x00060000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00060000, not first node in block (0x00040000)
    CLEANMARKER node found at 0x00070000, not first node in block (0x00040000)
    jffs2_scan_empty(): Empty block at 0x0008fffc ends at 0x00090000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00090000, not first node in block (0x00080000)
    jffs2_scan_empty(): Empty block at 0x0009fffc ends at 0x000a0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x000a0000, not first node in block (0x00080000)
    jffs2_scan_empty(): Empty block at 0x000aff40 ends at 0x000b0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x000b0000, not first node in block (0x00080000)
    jffs2_scan_empty(): Empty block at 0x000cfffc ends at 0x000d0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x000d0000, not first node in block (0x000c0000)
    jffs2_scan_empty(): Empty block at 0x000dfffc ends at 0x000e0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x000e0000, not first node in block (0x000c0000)
    jffs2_scan_empty(): Empty block at 0x000efffc ends at 0x000f0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x000f0000, not first node in block (0x000c0000)
    jffs2_scan_empty(): Empty block at 0x0010fffc ends at 0x00110000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00110000, not first node in block (0x00100000)
    jffs2_scan_empty(): Empty block at 0x0011fffc ends at 0x00120000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00120000, not first node in block (0x00100000)
    jffs2_scan_empty(): Empty block at 0x0012fffc ends at 0x00130000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00130000, not first node in block (0x00100000)
    jffs2_scan_empty(): Empty block at 0x0014fffc ends at 0x00150000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00150000, not first node in block (0x00140000)
    jffs2_scan_empty(): Empty block at 0x0015fffc ends at 0x00160000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00160000, not first node in block (0x00140000)
    jffs2_scan_empty(): Empty block at 0x0016fffc ends at 0x00170000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00170000, not first node in block (0x00140000)
    CLEANMARKER node found at 0x00190000, not first node in block (0x00180000)
    jffs2_scan_empty(): Empty block at 0x0019fffc ends at 0x001a0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x001a0000, not first node in block (0x00180000)
    jffs2_scan_empty(): Empty block at 0x001aff9c ends at 0x001b0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x001b0000, not first node in block (0x00180000)
    jffs2_scan_empty(): Empty block at 0x001cfffc ends at 0x001d0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x001d0000, not first node in block (0x001c0000)
    CLEANMARKER node found at 0x001e0000, not first node in block (0x001c0000)
    jffs2_scan_empty(): Empty block at 0x001efffc ends at 0x001f0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x001f0000, not first node in block (0x001c0000)
    jffs2_scan_empty(): Empty block at 0x0020fffc ends at 0x00210000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00210000, not first node in block (0x00200000)
    jffs2_scan_empty(): Empty block at 0x0021fffc ends at 0x00220000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00220000, not first node in block (0x00200000)
    jffs2_scan_empty(): Empty block at 0x0022fffc ends at 0x00230000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00230000, not first node in block (0x00200000)
    jffs2_scan_empty(): Empty block at 0x0024fffc ends at 0x00250000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00250000, not first node in block (0x00240000)
    jffs2_scan_empty(): Empty block at 0x0025fffc ends at 0x00260000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00260000, not first node in block (0x00240000)
    jffs2_scan_empty(): Empty block at 0x0026fffc ends at 0x00270000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00270000, not first node in block (0x00240000)
    jffs2_scan_empty(): Empty block at 0x0028fffc ends at 0x00290000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x00290000, not first node in block (0x00280000)
    jffs2_scan_empty(): Empty block at 0x0029fffc ends at 0x002a0000 (with 0x20031985)! Marking dirty
    CLEANMARKER node found at 0x002a0000, not first node in block (0x00280000)
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70c4: 0x8f85 instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70c8: 0x6b7f instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70cc: 0x21b3 instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70d0: 0xc1cf instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70d4: 0x2ee6 instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70d8: 0xecc4 instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70dc: 0xb184 instead
    jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x002a70e0: 0x6f8e instead
    Further such events for this erase block will not be printed


  • 해결
    일단 file system의 정상적인 동작에는 이상이 없는 것으로 보인다.
    [http]http://lists.infradead.org/pipermail/linux-mtd/2001-June/002737.html
    에 이전 버젼의 scan.c 를 사용하는것으로 보아 크게 문제가 없는 것으로 보인다.
    따라서 linux/fs/jffs2/scan.c 파일에서 위의 에러에 관련된 프린트 문을
    일단 주석 처리 한다.

[u-boot]램디스크 이미지 읽어오는 부분 재수정

lib_arm/armlinux.c에 do_bootm_linux()안에 ramdisk 이미지를 불러와서 dram에 위치시키는 루틴이 있다.

이 부분을 고쳐 주어야지만, mkimage 시 지정한 주소로 램디스크 이미지를 위치시킨다.

******************************************************************************************

    if (data) {

#if 0

        initrd_start = data;

        initrd_end = initrd_start + len;

#else

        /* shellbt,

         * ramdisk 가 현재 어느 위치에 있던간에 mkimage시

         * -a option으로 지정한 위치로 점프하게 만든 것

         */

        initrd_start = ntohl(hdr->ih_load);

        initrd_end = initrd_start + len;

        memmove((void*)initrd_start,(void *)data,len);

        printf ("Ramdisk load address change from 0x%08x to 0x%08x with %u bytes\n",data,initrd_start,len);

#endif

    } else {

        initrd_start = 0;

        initrd_end = 0;

    }



    SHOW_BOOT_PROGRESS (15);

******************************************************************************************

mtd 관련 소프트웨어 구하고 포팅하기







 


MTD
관련 소프트웨어 구하고 포팅하기





 


1. 개요



이 문서는 MTD와 관련된 유틸리티를 구하고 이지보드용으로 포팅하는 방법을 진행한
순으로 기록한 문서이다.



2. 사전 조건



이 과정은 ARM용으로 포팅된 zlib 관련 라이브러리가 크로스 컴파일 환경에
설치되었다는 것을 조건으로 둔다.


이 조건에 만족되지 않는다면 zlib
라이브러리 포팅 ( arm )
강좌를 참조하여 환경 구성후 처리하여야 한다.



3. 사이트 소개



MTD 관련 공식 사이트는



http://www.linux-mtd.infradead.org/



이다



4. 구하기 설치하기



다음으로 접속하면 mtd관련 화일을 얻을수 있다.



ftp://ftp.uk.linux.org/pub/people/dwmw2/mtd/cvs/



이곳에서 보면 날짜별로 snapshot 형식으로 압축하여 관리하고 있는데 이중 가장
최근 것을 받아 오면 된다.


본인은 mtd-snapshot-20020731.tar.bz2을 받아 내렸다.


다음은 이 화일 적당한 디렉토리에 놓고 다음과 같은 과정을 취하여 푼다.



bunzip2 mtd-snapshot-20020731.tar.bz2


tar xvf mtd-snapshot-20020731.tar



이 과정이 끝나면 mtd란 디렉토리가 생긴다.



5. 포팅하기



유틸리티만 포팅하는 것에 중점을 두기로 하였다


util 디렉토리로 이동한다.



cd util



Makefile 에 다음을 추가 한다.



CC = arm-linux-gcc

LD = arm-linux-ld

AR = arm-linux-ar

AS = arm-linux-as

RANLIB = arm-linux-ranlib

NM = arm-linux-nm


CFLAGS += -I../include -O2 -Wall 이 있는 라인 위 쪽에
추가하면 된다.



컴파일 한다.



make




6. PC용 포팅하기



mkfs.jffs2 유틸은 pc용이 필요하다.


이때는 5. 포팅하기에서 설명한
것과는 다르게 수행한다.


util 디렉토리로 이동한다.



cd util



컴파일 한다.



make



target시스템에 zlibㅏ이브러리 포팅하기(arm)









 



zlib
라이브러리 포팅 ( arm )





 


1. 개요



이 문서는 zlib와 관련된 라이브러리를 구하고 이지보드용으로 포팅하는 방법을 진행한
순으로 기록한 문서이다.



2. 사이트 소개



zlib 관련 공식 사이트는 http://www.gzip.org/zlib/
이다



3. 구하기 설치하기



다음으로 접속하면 zlib관련 화일을 얻을수 있다.



http://www.gzip.org/zlib/zlib-1.1.4.tar.gz



다음은 이 화일 적당한 디렉토리에 놓고 다음과 같은 과정을 취하여 푼다.



tar zxvf zlib-1.1.4.tar.gz



이 과정이 끝나면 zlib-1.1.4란 디렉토리가 생긴다.



4. 포팅하기



zlib-1.1.4 디렉토리로 이동한다.



cd zlib-1.1.4



환경을 설정한다.



./configure



Makefile의 내용중 다음을 고친다.


CC=cc 를 CC=arm-linux-gcc 로 수정


AR=ar rc 를 AR=arm-linux-ar rc 로 수정


RANLIB=ranlib 를 RANLIB=arm-linux-ranlib 로 수정


prefix = /usr/local 를 prefix = /usr/arm-linux 로 수정


컴파일 한다.



make



인스톨 한다.



make install





 

ramdisk와 ext2의 차이점



제목: ramdisk와 ext2의 차이점

글쓴이: 강정호
글쓴날: 2006년 10월 02일 오후 12:37
URL : http://kelp.or.kr/korweblog/?story=06/10/02/0246639



임베디드리눅스 관련 서적에서, 파일시스템관련 설명을 보면...

파일시스템의 종류는
ext,2ext3,FAT,FAT32,NFS,cramfs/romfs,ramfs(ramdisk파일시스템),jffs,jffs2와 같은 것이 있으며,
그 특징에 대해서도 각각 설명이 되어 있습니다.

그리고, ramdisk만드는 방법에 대해서 다음과 같은 순서로 설명되어 있습니다.

==========================<순서>=================================
1.빈 램디스크 파일 생성
=> dd if=/dev/zero of=ramdisk_image_file bs=1k count=5120
2.ext2 파일시스템을 탑재
=> mke2fs ramdisk_image_file
3.램디스크 이미지를 마운트시킬 디렉토리(=ramdisk_mount_dir)를 생성
4.ramdisk_mount_dir에 필요한 파일 생성하고 파일 복사
5.램디스크 이미지를 마운트시킴
=>mount -t ext2 -o loop ramdisk_image_file ramdisk_mount_dir
6.umount시킴
7.램디스크 이미지를 압축함
===================================================================
본 과정은 ramfs(ramdisk파일시스템)을 만드는 과정인데,
왜, ext2파일시스템을 탑재하고(=2과정),마운트(5과정)하는지요 ?

그리고, 2번과정후에 "file ramdisk_image_file"
이라는 명령어를 실행하면,
" ramdisk_image_file: Linux rev 1.0 ext2 filesystem data "라는 결과가 나타납니다.
즉.."만들어진 이미지파일이 ext2파일시스템이라는 의미"인 것 같습니다.

ramfs와 ramdisk는 서로 동일한 파일시스템인지요,다른 파일시스템인지요 ?
ext2와 ramfs는 서로 동일한 파일시스템인지요,다른 파일시스템인지요 ?
정말...개념이 너무 혼돈됩니다.
아시는 분 꼭 알려 주시면 너무 감사하겠습니다.


[u-boot] 랜덤 mac 주소와 dhcp - 061017

훗~ 드뎌, 랜덤 mac주소 설정과 설정값 flash & eeprom 저장을 끝냈다.
일단 첫번째 작업은 난수를 발생 시켜서, 리셋할 때 마다 새로운 mac주소를 생성하고, 저장할 수 있도록 해야한다
mac.c
***********************************************************************************

static unsigned long next = 1;

int s_rand(void)
{
    // 난수 생성 함수, 반환값 : 난수
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}

void s_srand(unsigned seed)
{
    // 난수 발생을 위한 seed값 설정
    next = seed;
}

int rand_mac(char *buf)
{
    // buf에 랜덤 mac주소를 복사한다.
   
    uchar mac1,mac2,mac3;       // mac random값 생성

    s_srand(get_timer(0));
    mac1 = (uchar)s_rand();
    mac2 = (uchar)s_rand();
    mac3 = (uchar)s_rand();

    sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", 00,05,169, mac1, mac2,mac3);

    // debug
    printf("Random Mac = %s\n",buf);
   
    return 1;
}

void save_mac(void)
{
    // mac값을 랜덤으로 할당해서, 이값을 ram & flash에 쓰고,
    // cs8900의 eeprom에도 쓴다.
    char ethaddr[20];

    rand_mac(ethaddr);
    setenv("ethaddr", ethaddr);

    do_saveenv(find_cmd("saveenv"),0,1,ethaddr);
   
    // save to cs8900 eeprom
    cs8900_e2prom_write("ethaddr", ethaddr);
}
***********************************************************************************

여기서 save_mac()함수를 board.c 에 main_loop()전에 보드 초기화 마지막에 하도록 해준다.
여기까지 랜덤 mac 작업이 끝났다.

* cs8900 eeprom에 mac값쓰기
driver/cs8900.c에 관련 함수들이 나와 있다.(cs8900_e2prom_write("ethaddr", ethaddr);

다음 dhcp설정은
#define CONFIG_COMMANDS 에 CFG_CMD_DHCP 설정을 추가하면 된다.
dhcp관련 함수들은 common/cmd_net.c & net/bootp.c 에 나와 있다.
dhcp()함수는 DhcpRequest()와 연결되어 있고, DhcpRequest()함수는 BootpRequest()함수와 같다.

'임베디드 > 7202' 카테고리의 다른 글

[커널] mtd 관련 sst  (0) 2006.10.26
[u-boot]아싸~ 성공 1.1.4 - 061013  (0) 2006.10.13
[U-Boot] 아~ 결국 1.1.4는 포기 - 061012  (0) 2006.10.12
[U-BOOT] 실패! flash 가 8bit.... - 061011  (0) 2006.10.11
prev 1 ··· 7 8 9 10 11 12 next

openclose