아웃사이더, 어떤이의 꿈 - 봄여름가을겨울





한참 노래방이 유행하던 대학시절,
개강모임, 종강모임 등등으로 얼큰하게 취해 들어가면 목이 터져라 부르던 노래들...
저거 뭔 노래야?... 하는 표정으로 아무도 호응을 해주지 않았지만
나 혼자 열심히 돼지 멱따는 소리로 고성방가를 했었지... 움흐흐...
그래서 친구가 없나?...

간단한 OS 자작 (SOS - Simple OS)


아주 오래전 80C196(16bit)과 TMS320C6416T(32bit) 컨트롤러에 uC/OS-II를 포팅하여 사용한적이 있다. 이번엔 ARM 기반 컨트롤러에 포팅해보려고 자료를 뒤져 봤는데 uC/OS-II가 오래전 유료화 되었댄다.
아... 이 청천벽력 같은 소식을 우째야 하나...
뭐 열심히 만든 SW를 편히 쓰게 해준것 만으로도 감지덕지다.
참 잘만든 SW가 유료화 되는것은 어쩌면 지극히 당연한 것이라 본다.
대신 지금 나에겐 대안이 필요하다.
아주 간단하고 무료로 쓸만한 OS를 찾아보니 요샌 FreeRTOS가 대세인듯 하다.
FreeRTOS 포팅 하려고 자료를 찾던중 문득

'요놈도 나중에 유료화 될려나?...'
'Free를 붙여놨는데 설마...'
'요놈에 의지하다 또 당하면?...'
'그냥 하나 만들어 버릴까?...'

이런 생각이 들었다.

예전에 이런일이 있었다.
MATLAB이라는 아주 좋은 툴에 완전히 의존해 개발을 하다보니 라이센스를 비롯해, 메모리, 저장장치 공간등의 문제로 MATLAB 라이브러리들을 못쓰게 되는 상황이 발생되니 사람이 바보가 되더라.
그 뒤로는 실개발에 필요한 라이브러리들 (FFT해석, 물리엔진 시뮬레이션용 적분기, 필터등등)을 C로 자체 개발해 사용하고 있다.

그런데, 비슷한 상황이 또 발생 되었다.
중대형 프로세서 기반의 Embedded System에서 자유롭게 사용가능한 OS가 필요할 경우 역시 Embedded Linux이다.
여기에 실시간성이 필요할 경우 Embedded Linux-Xenomai가 좋은 솔루션이 될수 있다.
소형 프로세서기반의 Embedded System에서 OS가 필요할 경우 uC/OS를 점찍어 놓고 있었는데 유료화 되면서 다른 대안이 필요한 상황이다.
또 바보가 되지 않기 위해 그냥 하나 만들기로 했다.

우선 소형 Embedded System을 목표로 하는 OS이므로 최대한 간단하게 구성하기로 한다.
OSless 펌웨어로 하기엔 좀 부족하고, OS를 넣자니 거대한 커널과 복잡한 메모리 관리, 태스크 관리, IPC툴등은 부담스러운, 그런 애매한 상황에 필요한 솔루션.
그래서 간단하게 만들어 보기로 했다. 뭐 거창한 OS만들 능력도 안된다. 크크크...
소형 Embedded System은 주로 주기적인 제어가 목적이므로 Periodic Task를 기본 지원하는 커널로 한다 (uC/OS나 FreeRTOS에서 Periodic Task를 구성하기가 좀 까다로운듯 하다).

Periodic Task와 관련하여 부연 설명을 좀 하자면 이렇다.
보통 디지털 제어기를 코딩할때 중요한 것이 바로 샘플링 시간 (Ts)이다.
아날로그 제어기법을 디지털 제어로 변경하면서 필연적으로 이 Ts라는 변수가 생기게 되는데 좋은 제어를 위해서는 이 Ts가 일정하게 유지되는 것이 중요하다.
이 Ts를 일정하게 유지하기 위해 보통 OSless 펌웨어에서는 하드웨어 타이머를 사용하던지 아니면 RTOS를 사용하게 된다.
Ts가 100ms인 Non-Periodic Task를 예로 보면 다음과 같다.

void NonPeriodicTask( void )
{
  for( ;; ) {
    Sensor();
    Control();
    Sleep( 100 );
  }


센서에서 값을 읽고 그 값에 따라 제어기를 돌린다.
그리고 100ms를 쉰다음 같은일을 반복한다.
디지털 Feedback 제어의 가장 단순한 예시이다.
Sensor()함수와 Control()함수를 수행하는데 시간 소모가 제법 있고 이 시간이 항상 일정 하다면 그 시간만큼 뺀 시간 동안 Sleep을 하면 된다.
그러나 보통 그 시간을 측정해 적용하는 일은 아주 드물며(바보같은 일) 수행시간이 매번 변한다면 Ts가 일정치 않아 정확한 제어가 되지 않을 것이다.
이를 위해 Periodic Task가 필요하다.

void PeriodicTask( void )
{
  for( ;; ) {
    Sensor();
    Control();
    SleepPeriod();
  }


void main( void )
{
  ...
  CreateTask( priority=10, period=100, task=PeriodicTask );
  ...
  StartKernel();
}

위와같이 Periodic Task를 커널에서 지원 한다면 Sensor()함수와 Control()함수의 수행시간과 상관 없이 (둘이 합쳐 100ms이내 종료된다는 조건) for루프는 매 100ms마다 반복된다.
커널이 Realtime을 지원 한다면 RTOS가 되는 것이다.
Periodic Task는 얼핏 타이머와 비슷하지만 다른 개념이다.
uC/OS에서 이 Periodic Task를 구현 하려면 커널 타이머와 이벤트, 세마포어등을 결합해 사용해야 한다 (지금은 커널에서 지원하는지 모르겠다).
Linux-Xenomai의 경우 커널 옵션에서 Periodic Task를 지원하는 커널로 설정하여 빌드하면 위와같이 사용 가능하다.

어찌 되었건 Periodic Task를 지원하도록 만들면 제어기 개발시 많이 편리하다.
스케쥴링 방식은 실시간성 보장을 위해 우선순위 기반과 Preemptive 방식으로 구현하고 다른 기능들은 넣지 않는다 (정확히는 넣을 능력이 안된다 크크크). 
기타 OS 커널에서 스케쥴링시 고려되어야 하는 많은 이론적 발생 가능한 문제점들을 응용 개발자에게 모두 떠넘겨 버리면 스케쥴러 구조는 아주 쉬어질 수 있다 움하하.
간단한 예로 우선순위 기반 스케쥴러에서 발생하기 쉬운 Starvation문제는 어플리케이션 개발시 주의하면 충분히 해결되거나 발생되지 않게 만들수 있다.
타겟역시 소형 Embedded System용 이므로 공룡 그림 그려진 OS이론 책에서 설명하는 기능들은 많은 부분이 필요 없다.
Task역시 많아야 10개 내외이고 ISR은 직접 구성하여 사용하면 된다.
태스크간 그리고 태스크-ISR간 동기화에는 공유 메모리와 Flag를 사용하면 소형 Embedded System환경에서의 어플리케이션은 대부분 개발 가능할 것이다.
뮤텍스나 세마포어, 이벤트등도 있으면 좋겠지만 스케쥴러를 최대한 간단하게 구성하기 위해 구현하지 않았다.
뮤텍스와 이벤트를 구현하여 기본 동작하는 것은 확인 하였으나 스케쥴러가 복잡해지고 잠재적 버그를 많이 가지고 있을수 있어 테스트만 해보고 넣지 않았다.
큰 그림으로 보자면 OSless 펌웨어와 OS-based 펌웨어의 어중간한 중간즘?...
커널 구조가 워낙 간단해 하루이틀 정도 코딩만으로 완성 되었다.
테스트는 일전에 Reverse Engineering으로 사용 가능해진 S3C3410(ARM7TDMI) 기반 무선전화기 보드를 활용했다.


[S3C3410(ARM7TDMI) 기반 무선전화기 보드 - 11MHz 동작]

잠재적 버그의 수를 줄이고 재사용 편의를 위해 간결한 코드 유지목적으로 다음 제한사항을 걸어 두었다.

<> 실시간성 확보를 위해 우선순위 기반의 Preemptive 스케쥴러로 구성
<> 필요한 파일수는 헤더 파일(h)과 구현 파일(c) 달랑 2개
<> 위 2개 파일 이외 추가 의존성 없이 구현
<> CPU의존 코드를 커널 파일안에 포함: 커널 관련 코드보다 CPU의존 코드가 더 많음(주객전도)
<> 인터럽트 컨텍스트 스위칭 주기는 1ms로 고정: 시간관리가 편해지고 추가 나눗셈 곱셈 연산이 필요 없어짐
<> 컨텍스트 스위칭 ISR은 여유 레지스터가 많은 FIQ사용

만들어 놓고 보니 OS라는 타이틀을 붙이기에는 너무 심하게 뭔가 없다. 흐흐...


[8개 태스크 동작 시험]

위 그림은 자작한 OS의 실제 동작 시험 결과 이다.
총 8개 태스크를 만들어 동시 구동 시켰으며 이중 우선순위가 가장 낮은 T7 태스크는 Non-Periodic Task로 Idle Task가 된다. 즉, 절대 Sleep이 일어나지 않도록 한다.
T0부터 T5까지는 각각 10ms, 20ms, 40ms, 80ms, 160ms로 두배씩 증가하는 주기로 설정하여 내부 카운터를 증가 시키고, T6는 200ms주기로 위 상태 정보 문자열을 출력한다.
T7은 Idle Task로 남는 시간동안 CPU 사용률을 계산한다.
위 출력 메시지의 맨 좌측값이 시스템 시간으로 1ms단위로 측정된다.
태스크 카운터 값을 보면 T0의 경우 시스템 타임의 1/10로 증가되며 나머지 태스크는 각 1/2씩 작은 값으로 증가되어 설정한 주기로 모든 태스크가 정확히 수행되는 것을 볼 수 있다.
T6의 경우 메시지 출력을 위해 200ms 주기로 설정되어 있으므로 매 메시지가 200ms 간격으로 출력되고 있는것을 볼 수 있다.
우선순위 기반 스케줄링 이므로 우선순위가 높은 태스크일 수록 실시간성이 잘 보장된다.
Idle Task를 제외한 T6의 우선순위가 가장 낮음에도 커널이 안정된 이후 정확한 200ms 간격을 보여 주고 있다.
해당 보드가 32bit기반 ARM임에도 약 11MHz정도 저속으로 동작하는데 태스크 8개와 1ms 스케쥴러에 약 20%정도 CPU사용량을 보인다. 

아직 실 어플리케이션에 적용한게 없어 어떤 상황에서 어떤 버그가 생겨날지 모른다.
추후 사용해 가면서 다듬어 봐야 겠다.

OS기반의 소규모 펌웨어 개발에 필요한 솔루션이 얼추 만들져 기쁘다.
의존성이 없어 아주 홀가분하기도 하다. 움하하...


<> 2014/08/21 내용 추가
CPU 사용률 측정에 버그가 있었다.
위 이미지에서는 11MHz S3C3410(ARM7) CPU 사용률이 약 20%정도이나 버그 수정후 재측정결과 약 95~96%정도 CPU 사용률을 보여 주었다.
8개 태스크와 시간 소모가 많은 printf()문에다 1ms 컨텍스트 스위칭 주기를 설정했는데 제아무리 32비트 코어라도 11MHz로 동작하는데 20%대는 무리인듯 하다.
추가로 CPU코어가 33MHz로 동작하는 S3C3400(ARM7) 코어에 똑같은 상황에서 측정해 보았는데 CPU 사용률은 약 48~49%대를 보여 주었다.