임베디드 레시피

Egloos | Log-in





Task 구조와 Signal

Embedded System Software는 기본적으로 무한 loop구조를 가졌기 때문에, 한정된 일만 할 수 있는 한계가 있지요. 아함~ 한정된 일만 하다 보니까, 무료한 일상입니다요. 아침에도 Lamp를 깜빡 깜빡, 점심에도 Lamp를 깜빡 깜빡, 저녁에도 Lamp를 깜빡 깜빡.
 
void main ()
{
 
     Lamp_init();
 
     while (1)
      {
            Lamp_on();
            time_wait (100); /* wait 100uS */
 
            Lamp_off();
            time_wait (100); /* wait 100uS */
      }
}
 

~ 깜빡만 말고, 나한테는 Motor 있는데, Motor 움직이고 싶다. 그러면 하나의 main()함수를 만들어 내면 되는 겁니다요.

 

void main()
{
  
     Motor_init();
 
     while (1)
      {
            Motor_on();
            time_wait (100); /* wait 100uS */
            Motor_off();
            time_wait (100); /* wait 100uS */
      }
}
 
Motor를 움직이려면 또 이런 구조의 무한 Loop가 필요하지요. 그냥 Lamp 무한 Loop에 Motor도 끼워 넣지 머.
 
void main ()
{
  
     motor_init();
 
     while (1)
      {
            Lamp_on();
            time_wait (100); /* wait 100uS */
            Lamp_off();
            time_wait (100); /* wait 100uS */
 
            Motor_on();
            time_wait (100); /* wait 100uS */
            Motor_off();
            time_wait (100); /* wait 100uS */
      }
}
 
아.. 난 이제.. Lamp도 깜빡일 수 있고, Motor도 움직일 수 있어. 아.. 하지만.. 또.. 똑같은 일의 반복이네.. 좀 더.. 많은 일을 할 수는 없을까? 좀 지루하지요. 그래서 Embedded System에 생기를 불어 넣어준 것이 바로 RTOS라는 거에요. Real Time OS인데요, 실은 RTOS라는 말은 좀 그렇죠. RTOS라는 말보다는 Embedded OS라는 말이 더 맞다고 봐야겠네요. RTOS는 뭐시깽이 Hard/ Soft 뭐 이런 구분이 있거든요. Hard는 응답을 정해진 시간 안에 주는, 그리고 Soft는 응답을 하기만 하면 되는 뭐 그런 건데,  예전같이 Embedded에 탑재되는 Processor가 느릴 때는 그게 중요했었지만, 요즘은 워낙 Computing Power가  좋아져서 Soft도 웬만한 시간 안에 응답을 주니까, 그다지 큰 구분은 많이 없어 졌어요.  물론 돈이 많이 들어가는 미사일이나, 위성 같은데 사용하는 것들은 시간 응답성이 워낙 좋아야 하니까, Hard type의 RTOS라는 말이 공공연히 쓰여지고 있으나, 우리가 보통 만드는 Embedded System은 그렇진 않으니까, 굳이 그렇게 까지 구분하는 건 무리가 아닌가 싶어요. 긁적.
 
뭐, 막상 Embedded OS는 Scheduler라는 기능을 가져요. Embedded System의 매니저에요. 할 일들, 스케쥴을 관리해서 그때그때 그 일을 하게 해주죠. Lamp의 무한 Loop의 함수 이름을 Lamp_task() 라고 이름 지어주고요, Motor의 무한 Loop의 함수 이름을 Motor_task() 라고 이름 지어줘 볼까요? - 보통은 한가지 일 단위에 따라서 task라는 이름의 형태를 갖고요, Scheduling의 기본 단위가 되어요 -
 
Lamp_task만 보면요,
 
 
void Lamp_task ()
{
     Lamp_init();
 
     while (1)
      {
            Lamp_on();
            time_wait (100); /* wait 100uS */
 
            Lamp_off();
            time_wait (100); /* wait 100uS */
      }
}
 
뭐 이렇게 이름 지을 수 있겠네요. Lamp Task는 무한히 깜빡이는 일을 하죠. 그러면, 또 다른 Motor를 돌리는 Motor Task도 같이 돌아야겠죠. 그러면 Lamp_task()와 Motor_task()가 서로 주거니 받거니 일을 했으면 좋겠는데 말이죠. 그래서 signal이라는 걸 만들어줘 보죠. 이건 Task끼리 signal을 주고 받을 수도 있고, 그걸로 서로 일을 시킬 수도 있는 거라 생각하시면 되요. wait(signal); 이라는 함수를 약속해 볼까요? wait (signal)은 signal을 기다린다는 의미이고요, signal을 기다리기만 해서는 안되겠죠. signal을 주는 일도 해야 하니까, send (task, signal) 이라는 함수를 약속해 보면, 어떤 task한테 signal을 주는 역할을 한다고 해보죠. 그러면, 위의 task를 이런 식으로 만들면 서로 주거니 받거니 될까요?

/* 여기에서 Make_Ready() 함수는 대상 함수를 Ready, 즉 Scheduling이 일어 났을 때 제일 먼저 실행 될 
    Task로 설정해 주는 함수라고 보시지요 */
 
void main ()
{
     Make_Ready (Lamp_task());       
     wait (DONE);        /* Lamp task는 init을 하고 DONE을 주고 WORK을 기다리겠지? */
     Make_Ready (Motor_task());     
     wait (DONE);        /* Motor task는 init을 하고 DONE을 주고 WORK을 기다리겠지? */
     send (LAMP, WORK)  /* Lamp task야 깜빡여 보렴 */
  
     return;   /* Good bye forever */
}
 
void Lamp_task ()
{
 
     Lamp_init();
                                    /* 우선은 초기화는 무조건 하네 */
     send (main, DONE);   /* wait을 만나면 main으로 돌아가~ */

     while (1)
     {
          wait (WORK);        /* 여기서 WORK이라는 signal을 일단 무작정 기다리죠 */
                                    /* while(1)이니까 한번 깜빡이면 항상 여기서 signal을 기다리죠 */
                                    /* Signal을 받기만 하면, 일을 시작 할테야 */
           clear (WORK);       /* WORK을 받았으니 다음번에도 WORK을 받을 수 있도록 초기화 해주자 */
            Lamp_on();
            time_wait (100); /* wait 100uS */
 
            Lamp_off();
            time_wait (100); /* wait 100uS */
 
          send (MOTOR, WORK);  /* Motor task야 Motor를 돌려봐 */
      }
}
 
void Motor_task ()
{
 
    Motor_init();
                                    /* 우선은 초기화는 무조건 하네 */
    send (main, DONE);   /* wait을 만나면 main으로 돌아가~ */

     while (1)
     {
          wait (WORK);        /* 여기서 WORK이라는 signal을 일단 무작정 기다린다 */
                                    /* while(1)이니까 한번 깜빡이면 항상 여기서 signal을 기다린다 */
                                    /* Signal을 받기만 하면, 일을 시작 할테야 */■
          clear (WORK);       /* WORK을 받았으니, 다음번에도 WORK을 받을 수 있도록 초기화 해 주자 */
            Motor_on();
            time_wait (100); /* wait 100uS */
 
            Motor_off();
            time_wait (100); /* wait 100uS */
 
            send (LAMP, WORK);   /* Lamp task야 Lamp를 깜빡여봐 */
      }
}
 
뭐 이런 걸 그림으로 그려보면?



뭐 이런 식의 동작을 무한히 반복하겠죠 뭐. 오라, 간단하죠? 이것이 바로, Embedded System에서의 Task의 구조인 거에요. Task를 이렇게 만들어 놓으면 죽을 때 까지 죽지 않고요, 시킬 때 마다 자기에게 주어진 일을 하고 다시 기다리는 구조로 되어 있는 거죠. 차암~ 쉽죠~잉?


Task라는 말이 나왔으니, Thread와 Process라는 말도 같이 언급하는 게 좋을 것 같네요.  아~주 헷갈리는 말이지요.
Embedded System에서 Task는 Scheduling (비스므리한 의미로 Context Switching)의 기본 단위를 말하고요. Task 단위로 일을 나누어 System을 만드는 게 기본이랍니다. 그런데, 자주 Thread라는 말이라든가 Process라는 말이 자꾸 나와서 헷갈리게 하는데요.
Task = Process + Thread 라고 봐야 할 듯합니다. - 사실 이 말도 애매모호 하긴 하네요 -

아 좋은 표현이 생각 났어요. Task  ∋ Process, Task  ∋ Thread 가 더 맞겠네요.
다시말해!! Task는 Process로 구현되거나, Thread로 구현된다는 말이에요.
중요한 건 Process와 Thread는 Scheduling (Context Switching)의 기본 단위임에는 틀림이 없구요, 다만 Process는 자신 만의 공유한 Memory Space를 할당 받아서 Process끼리는 Memory 공간이 분리되어 있어서 서로 다른 Process끼리는 특별한 방법을 이용하지 않고는 Data 공유가 되지 않아요.
하지만, Thread는 다르답니다. Thread는 모든 주소 공간이 공유가 되고요, 어렵지 않게 다른 Thread의 주소 공간을 자유자재로 넘나 들 수 있어요. 기냥~ 마구 갖다 쓰는 거죠. 그래서 전역 변수 공유 문제가 발생하곤 한답니다. 주의를 기울여야 한다구요.
보통 Process가 자신의 Memory Space에서 다른 Scheduling의 단위인 Thread를 만들어 내는데, 이런 경우에는 어미 Process 자신도 자식과 주소 공간을 공유하니까, 자식의 입장에서 봤을 때 다~같이 Thread가 되는거죠. - 다른 Process가 보았을 때는 엄연한 Prcoess 이지만요 -
이건 Linux의 예 이었고요, 보통의 Embedded System에서는 Thread로 Task를 구현하는 일이 많아요. 그러다 보니까 모든 주소 공간을 모두 공유할 수 있는거에요. 예를 들어, 전역변수를 들자면, Thread들 끼리는 전역변수를 특별한 방법을 사용하지 않고도 직접 Access가 가능합니다만, Process의 경우에는 다른 Process에서 사용하고 있는 전역변수에 Access하기 위해서는 System call같은 특수한 interface를 이용해서 접근해야 하는 거죠. 그러니까 Task의 구현 방법으로는 Process인 거냐, Thread 인거냐로 나눌 수 있다고 봐야 하겠지요. 냠냠.

by 히언 | 2009/08/10 20:55 | RTOS팩토리(커널) | 트랙백 | 핑백(1) | 덧글(8)

Linked at 친절한 임베디드 시스템 개발자.. at 2009/09/20 18:57

... ⓒ Task의 구조와 Signal ... more

Commented by at 2009/08/10 23:59
정말 잘 보고 있습니다. 감사합니다.
그런데 질문이 있어서.... 위 예제의 lamp_task() 와 motor_task()에서 wait(WORK) 하면 work 신호가 없으면 올때까지 여기서 죽치고 기다리지 않을까요? 아무것도 안하고.... 그럼 시스템이 정지하지 않을까요?
그리고 lamp_task()를 끝내지 않고 motor_task()를 호출해 버리고,motor_task()가 또 lamp_task()를 호출하고...이렇게 한도 끝도 없이 반복하면 stack 이 바닥나지 않을까요? 어떻게 하면 두 함수를 끝내면서 반복할수 있죠??
Commented by 히언 at 2009/08/11 00:54
ㅋ 밑에 그림 잘 보시면, wait(WORK)후에 원래 다른 넘으로 순서를 넘기는데, sendsig를 main에게 해주지요? 그러니까 main으로 돌아오고요, - 두개 task 모두 그러겠네요? - main 마지막에 send signal하지요? LAMP한테. 그러면 Lamp_task()가 wait을 벗어나겠지요. 그 끝에는 Motor_task()한테 날리고요. 이렇게 주거니 받거니 하지요?

실은 Make_Ready()니 뭐니 하는 개념이 나중에 나와서 고의로 누락 했는디, 가만히 보니 나중에 나오더라도 지금 요로코롬 넣는 것이 낫겠네요.

그리고 stack이 바닥 나려면 뭔가 계속 쌓아야겠지요? 그런데 뭔가를 처리하는 함수에 들어갔다가 다시 나와서 다시 기다리는 형태이니 무한히 쌓을 것은 없구요...

ㅎㅎ 피드백 감사함다
Commented by 나빌레라 at 2009/08/13 17:57

안녕하세요,
arm scatter loading에 대한 자료를 찾다가 히언님의 블로그를 발견했습니다.
너무나 좋은 글과 방대한 자료에 감탄하며 (히언님께 감사하며) 강좌를 찬찬히 읽고 있습니다.

위 예제에서 스케줄러 부분을 나중에 설명하시려다 보니 send(), wait()를 이용해서 두 태스크가 도는 모습을 보여주셨는데요.

사실 커널에서 태스크가 멀티 태스킹으로 도는 모습을 설명하기엔 부적절해 보입니다.

차라리 의사코드 형식의 스케줄러와 컨텍스트 스위칭 코드를 넣고 두 태스크간의 호출 처리를 커널이 직접 해주는 것을 미리 설명하는 것이 이후 컨택스트 스위칭 부분을 설명할 때 더 명료하게 이해 되리라 생각 됩니다.

본 편 강좌에서는 Lamp가 Motor를 동작시키고 Motor가 다시 Lamp를 동작시키는 것을 반복하는데,
분명 나중에 컨텍스트 스위칭과 스케줄러가 나오면 커널이 이 둘을 동작시키게 되는 부분이 개념상 상충한다고 생각되네요.

저도 웹상에 강좌를 올려본 경험이 있는데, 쉽지 않은 일 꾸준히 해주시는 히언님께 경의를 표하며 좋은 강좌 끝까지 써주시기 바랍니다.

감사합니다.
Commented by 히언 at 2009/08/13 21:48
넹~ 그럴수도 있겠네요~ 감사요~
Commented by 진석 at 2009/08/15 17:54
인터넷 검색 하다가 좋은 곳을 발견하게 되었네요 ^^

강의 올려주셔서 감사합니다. 저에게 많은 도움이 되네요.

Commented by 히언 at 2009/08/16 23:38
으흐흐~ 저에게도 도움을~~ 주세요~~ 크흐흐
Commented by 신입사원 at 2009/09/09 16:50
드디에 댓글을 다네요ㅎㅎ
항상 고맙다는 말씀 전하고 싶었습니다
앞으로도 많은 지도(?) 부탁드리겠습니다하하하
Commented by 히언 at 2009/09/09 22:32
드디에~ 감사합니다~ 저역시 많은 지도 편달 부탁합니다. ~
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.

◀ 이전 페이지          다음 페이지 ▶