AVR FreeRTOS : Resource Management 1. 이장의개요 Multitasking System에서한 Task가어떤 Resource를사용하고있는도중에 Running State에서벗어나는일이생길수있고, 이상태에서다른 Task나 Interrupt가동일한 Resource를사용하려고시도할수있다. 이경우 Data 가충돌하거나손상될수있다. 다음은이러한경우가발생할수있는예이다. A. Accessing Peripherals 다음시나리오는두장치가하나의 LCD에 Data를서로쓰려고시도하는경우의예이다. i. Task A가실행되고있는중에문자열 Hello world 를 LCD에출력하기시작하였다. ii. iii. Task A는문자열을 Hello w 까지출력한상태에서 Task B에게 pre-empted(task A의 Run 상태가 Task B에의하여대체됨 ) 되었다. Task B가 Blocked State로들어가기전에 Abort, Retry, Fail? 메시지를 LCD에출력한다. iv. 다시 Task A가 Run 상태가되어 orld 를 LCD에출력한다. 그결과 LCD에는출력문자열이서로충동하여 Hello wabort, Retry, Fail?orld 이출력된다. B. Read, Modify, Write Operation 아래의프로그램예는 C Code와이 Code의 AVR128 프로세서 Assembly Language Output 이다. 이예에서먼저 PORTD의값을 Rg24에읽고, 이 Rg의값을 Modify 하고, 그결과를다시 PORTD에쓴다. 이러한과정을 Read, Modify, Write Operation 이라한다. PORTD = 0x03; // C code Program // Assembly Language Output in r24, PORTD ; PORTD 값을읽고,
ori r24, 0x03 ; Modify 하고, out PORTD, r24 ; Write 한다. 위예는명령이하나실행된다음, 나머지명령이실행되기전에 Interrupt가발생할수있기때문에 non-atomic Operation 이다. 다음의시나리오는 2개의 Task가 PORTD를 Update 하려고시도하는경우이다. i. Task A가 PORTD의값을 Load 한다. : Read Operation ii. Task A 가 Modify, Write Operation 부분까지완료하기전에 Task B 에게 pre-empted(task A 의 Run 상태가 Task B 에의하여대체 됨 ) 되었다. iii. Task B가 PORTD를 Updates 하고, Blocked 상태가된다. iv. Task A가다시 pre-empted 되어, Rg24의값을 Modify 하고, PORTD를 Update 한다. 위의시나리오에서 Task B 에서수행한 Operation은 Task A에의하여변경되었기때문에결과적으로 PORTD의값이잘못된결과일수있다. 위예는 Peripheral Register(I/O Port) 를사용한예이지만동일한일이 Global Variables에도발생할수있다. C. Variable의 Non-atomic Access 프로세서 Architecture 보다큰변수 ( 프로세서가메모리에 R/W 할때한번에 R/W 하는 Word 폭보다더큰폭 (Bit 수 ) 을갖는변수 ) 를 Updating 하는경우도또다른형태의 Non-atomic Operation 이다. D. Function Reentrancy 만약함수가 Task나 Interrupt 로부터 Call 되고, 아직종료되지않은상태에서다른하나이상의 Task나 Interrupt 로부터안전하게 Call 될수있다면이함수는 Reentrant 한함수이다. 각 Task는자신의 Stack과자신의 Core Register Set의값을유지 ( 보존 ) 하고있다. 만약함수가 Register에저장된 Data와 Stack에저장
된 Data(Local Variable) 만을사용한다면이함수는 Reentrant 한함 수이다. 아래예는 Reentrant Function 의예이다. /* Function에 Pass되는 Parameter는 CPU Register 또는 Stack를이용하기때문에, 각 Task는자신의 Stack과자신의 Core Register Set 의값을유지 ( 보존 ) 하여야한다. 는조건을충족한다. */ Int AddInt( int var1, var2) /* 이함수는 Register에저장된 Data와 Stack을이용하는 Data(Local Variable) 만을사용한다. */ Int sum; Sum = var1 + var2; /* Return 값은 Register와 Stack 만을이용하여전달한다. Return sum; 아래예는 Not Reentrant Function의예이다. /* sum은 Global Variable로각 Task가이함수를 Call 할때변경될수있다. */ Int sum; Int AddTen( int var1)
/* 변수 var2는 Static 변수로 Stack에할당되지않는다. 각 Task 가이함수를 Call 할때이변수가복사되어반복사용된다. */ static int var2 = 10; sum = var1 + var2; return sum; Mutual Exclusion Task 사이에공유되는자원과 Task와 Interrupt 사이에공유되는자원은 Mutual Exclusion 기술로관리되어야한다. 한 Task가이용하기시작한자원 (Resource) 은이 Task의사용이종료되어 Returned 될때까지배타적으로사용되어야한다. FreeRTOS는 Mutual Exclusion을구현하는방법을제공한다. 그러나최상의방법은하나의자원은하나의 Task만사용하도록설계하는것이최선이다. 이장의개요 i. 언제, 왜 Resource Management와 Control이필요한가? ii. Critical Section 이란무엇인가? iii. Mutual Exclusion의의미는? iv. Scheduler를 Suspend 한다는의미는? v. Mutex를어떻게사용하나? vi. vii. Gatekeeper Task의생성과사용에대하여 Priority Inversion에대한이해와어떻게 Priority 상속이 Priority Inversion 때문에발생하는문제를해결 ( 완전한해결은아님 ) 하는지에대한이해
2. Critical Sections과 Scheduler의 Suspending A. Basic Critical Sections(Regions) Basic Critical Sections은아래의예와같이 taskentyer_critical(), 과 taskexit_critical() Macros에둘러싸인 Code 영역이다. /* Critical Section 내부의 Code가실행되는동안에는 Interrupted 되지않는다 */ taskenter_critical(); /* taskenter_critical() 과 taskexit_critical() 사이에서는다른 Task로 Switch 되지않는다. Priority가 configmax_syscall_interrupt_priority 상수보다높은 Interrupt는 Interrupt Nesting를할수있다. 그러나이들 Interrupt에서 FreeRTOS API 함수를 Call 하는것은허용되지않는다. */ PORTD = 0x01; taskexit_critical(); 다음예는여러개의 Task에서 Call 되는 vprintstring() 함수의예이다. 이예에서는한 Task에서표준출력장치를사용중일때이장치를다른 Task에서 Access 하는것을 Protect 하기위하여 Critical Section를사용한다. Void vprintstring( const char *pcstring ) /* 문자열을표준장치에출력하는동안다른 Task가이장치를사용하지못하도록하는데 Mutual Exclusion의초보적인방법으로 Critical Section를사용하였다. */ taskenter_critical();
printf( %s, pcstring ); fflush( stout ); taskexit_critical(); 위예와같이 Critical Section를사용하는것은매우초보적인방법으로만약 Critical Section 내의프로그램 Code가길어지면 Interrupt 응답이지연되는등문제가발생할수있기때문에 Critical Section내의 Code 길이는가능한짧게하여야한다. 위예와같이 Critical Section 내에터미널장치에출력하는 Code가있는경우에터미널장치의동작은매우느리기때문에 Interrupt를사용하는시스템에오동작이발생할가능성이크다. 그러므로이러한방식으로자원 ( 프린터등 ) 을사용하는것은피하여야한다. Critical Section은 Kernel이 Nesting Depth를 Count 하기때문에 Nest 될수있다. Nesting Counter 값이 0 일때 Critical Section이종료된다. B. Scheduler의 Suspending(or Locking) Critical Section은 Scheduler를 Suspending 하는것에의하여생성될수있다. Scheduler의 Suspending은 Scheduler을 Locking 하는것과같다. Scheduler를 Suspending 하는것에의하여구현된 Critical Section은다른 Task에의하여해당영역 Code를 Protect 하는것만가능하다. 이경우 Interrupt은사용가능하기때문에 Interrupt에의한 Access 가발생할가능성이있다. C. vtasksuspendall() API 함수 vtasksuspendall() API Function의 Prototype void vtasksuspendall ( void);
Scheduler는 vtasksuspendall() 함수를 Call 하는것에의하여 Suspend 된다. Scheduler를 Suspending 하면 Context Switch은발생하지않지만, Interrupt는 Enable 될수있다. 만약 Scheduler가 Suspending 된동안 Interrupt에의한 Context Switching 요구가발생하면, 그요구는 Hold 되고, Scheduler가 Un-suspended 된후에실행된다. FreeRTOS API 함수는 Scheduler가 Suspending 된동안 Call 될수없다. D. xtaskresumeall() API 함수 xtaskresumeall() API Function의 Prototype portbase_type xtaskresumeall ( void); Scheduler 는 xtaskresumeall() 함수를 Call 하는것에의하여 Unsuspend 된다. Returned value Returned value Description Scheduler가 Suspending 된동안요구된 Context Switch는 Hold 되었다가 Scheduler가 Un-suspended 된후 ( 그러나 xtaskresumeall() 함수가 Return 되기전 ) 에실행된경우 pdtrue 가 Return 된다. 그이외의경우에는 pdfalse 가 Return 된다. vtasksuspendall() 과 xtaskresumeall() 는 Kernel 이 Nesting Depth 를 Count 하기때문에 Nest 될수있다. Nesting Counter 값이 0 일때 Scheduler 는 Un-suspended 된다. 다음예는 Scheduler 를 Suspend 하는방법에의하여터미널출력을 Protect 하는예이다. Void vprintstring( const char *pcstring )
/* 문자열을표준장치에출력하는동안다른 Task가이장치를사용하지못하도록하기위하여 Scheduler를 Suspend 하는방법에의하여 Mutual Exclusion를구현하였다. */ vtasksuspendscheduler(); printf( %s, pcstring ); fflush( stout ); xtaskresumescheduler(); 3. Mutexes (and Binary Semaphores) Mutex는 Binary Semaphore의특별한형태로 2개이상의 Task가자원을공유할때사용한다. MUTEX는 MUTual Exclusion 의의미이다. Mutex를이용하여자원을공유하는경우, Task가자원을이용하기위하여는먼저 Token을 Take 하여야한다. Token을갖은 Task는자원의사용이종료되면 Token을 Give 하여다른 Task가자원을사용할수있도록하여야한다. 이러한과정의예는아래그림과같다.
Mutex 를사용한 Mutual Exclusion 의구현 Mutex 는 Resource 를보호한다 Task A Task B Resource 는 Mutex 에의하여보호된다 Guarded Resource 2 개의 Task 가자원을사용하기를원한다. 그러나아직 Mutex 가 Token 을갖고있기때문에 Task 는자원을 Access 하지못한다 Task A xsemaphoretake() Task B Guarded Resource Task A 가 Token 을획득하고자원을사용한다. Task A Task B xsemaphoretake() X Guarded Resource Task B 가동일한 Mutex Take 를시도한다. 그러나아직 Task A 가 Token 를가지고있기때문에 Task B 는자원을사용할수없다.
Task A xsemaphorgive() Task B Guarded Resource xsemaphoretake() Task B 는 Blocked State 가되어 Mutex 를 Take 할수있기를기다리고있다. Task A 가 Resource 사용을종료하고 Token 을 Give 한다. Task A Guarded Task B Resource xsemaphoretake() Task B 가 Un-Blocked 되고, Mutex 를획득하여 Resource 를사용한다. Task A Guarded Task B Resource xsemaphoregive() Task B 가 Resource 사용을종료하고 Token 을 Give 하여 Mutex 는다시두 Task 가사용가능한상태가된다.
A. xsemaphorecreatemutex() API 함수 Mutex 는 Semaphore 의한형태이다. FreeRTOS Semaphore 의여러형태의 Handle 은모두 xsemaphorehandle Type 변수에저장된다. Mutex 는사용되기전에생성되어야한다. Mutex Type Semaphore 는 xsemaphorecreatemutex() API 함수에의하여생성된다. xsemaphorecreatemutex() API 함수의 Prototype xsemaphorehandle xsemaphorecreatemutex( void ) Returned value Returned value Description NULL : Mutex data를저장하기위한 Memory 부족등의이유로 Mutex 생성에실패한경우 Non-NULL : Mutex가성공적으로생성되고 Mutex의 Handle이 Return 된다. B. MUTEX 를이용한자원관리프로그램예 1) RT_mutex_LED // 실험목표 // Mutex Semaphore 를이용한 Resource Management // vtasksuspend, vtaskresume 를이용한 Task State Management // // 실험방법 // 처음프로그램이실행되면 LED0, LED1, LED2 가랜덤한속도로점멸한다. ( vled1task, vled2task, vled3task 가모두 Running 하고있는상태 ) // SW0 를누르면 vled1task 이 Suspending 되고 vled2task, vled3task 만 Running. // SW1 를누르면 vled1task 이다시 Running 상태가되어 LED0, LED1, LED2 가모두점멸한다. // // LED // LED 0Bit : vled1task 의출력 ( vled1task 이 Mutex Semaphore 를획득하였을때 Turn On 된다. // LED 1Bit : vled2task 의출력 ( vled2task 이 Mutex Semaphore 를획득하였을때 Turn On 된다. // LED 2Bit : vled3task 의출력 ( vled3task 이 Mutex Semaphore 를획득하였을때 Turn On 된다. // LED 6Bit : vled1task 이 Suspending 상태일때 Turn On 된다.
// LED 7Bit : vled1task 이 Running 상태일때 Turn On 된다. #include <stdio.h> #include <stdlib.h> #include <avr/io.h> #include <avr/interrupt.h> #include <compat/deprecated.h> //FreeRTOS include files #include "FreeRTOS.h" #include "task.h" #include "croutine.h" #include "semphr.h" //User include files #define SW_DEBOUNCE_TIME 20 // xsemaphorehandle type 의변수로서 mutex type semaphore 를참조하는데이용한다. // LED 를배타적으로사용할수있도록하는데사용한다. xsemaphorehandle xmutex; // xsemaphorehandle type 의변수로서 Binary type semaphore 를참조하는데이용한다. // Switch Interrupt(Interrupt0,1) 과 Switch Handling Task(vtaskControlTask) 을동기시키는데사용한다. xsemaphorehandle xkeyinsemaphore = NULL; xtaskhandle xhandlecontrol, xhandleled1, xhandleled2, xhandleled3; unsigned portbase_type taskpriorityno; void vledcontrol(char ledno) // Semaphore 획득에실패한 Task 는 Semaphore 를획득할때까지 Blocking 상태가된다. // Semaphore 를획득하면자원사용을완료할때까지자원을사용할수있다. xsemaphoretake( xmutex, portmax_delay ); // 만약 Semaphore 를성공적으로획득한경우아래 Code 가실행된다.
PORTF &= ~0x0f; PORTF = (1 << ledno); // LCD 또는프린터에출력하는경우처럼한번자원을점유하면일정시간사용하는효과를주기위함. vtaskdelay( 100 ); // 사용이완료된자원 (Resource) 을되돌려준다. xsemaphoregive( xmutex ); // vledtask 의 Instance 3 개 (vled1task, vled2task, vled3task) 가생성되어실행되고, // 각각의 Instance 는 "0", "1", "2" 를인수로전달받는다. // 전달받은인수를수로변환하여해당위치의 LED 를 Turn On 한다. void vledtask( void *pvparameters ) char LEDTaskNo, *pcchar; pcchar = ( char * ) pvparameters; // 문자열 Pointer(*pvParameters) 에의하여전달받은문자를 LEDTaskNo 에저장 LEDTaskNo = *pcchar; // LEDTaskNo 에저장돤숫자문자를정수로변환한다. LEDTaskNo -= '0'; for( ;; ) // Task에서직접자원을사용하는함수를 Call 한다. // Mutex를획득에성공하면자원을사용할수있고, // 실패하면이 Task 보다우선도가낮은 Task가자원을사용하고있는경우에도먼저 Mutex를획득한 Task가 Give 할때까지 Blocked 상태가지속된다. // 점멸할 LED 번호를 vledcontrol 함수에전달한다. vledcontrol( LEDTaskNo ); // 이 Task 가자원을사용한후, 랜덤하게발생된지연시간동안 Blocked 상태에있도록하여, 낮은우선도의 Task 가자원을사용할수있는기회를준다. vtaskdelay( ( rand() & 0x03FF ) );
// SW0, SW1 의상태에따라 LED1Task 을 Suspending 또는 Running 상태되도록제어한다. void vtaskcontroltask( void *pvparameters ) PORTF = 0x80; // vled1task : Running State for( ;; ) if(xsemaphoretake( xkeyinsemaphore, portmax_delay ) == pdtrue) vtaskdelay(sw_debounce_time); // SW0 가 Push 되면 vled1task 이 Suspending 되고 vled2task, vled3task 만 Running. if((pind & 0x01) == 0x00) // SW0 가 Push 되면 vled1task 를 Suspending 상태로천이시킨다. vtasksuspend(xhandleled1); PORTF = 0x40; // vled1task 이 Suspended State 인것을 LED 에표시한다. // SW1 가 Push 되면 vled1task 이다시 Running 상태가되어 LED0, LED1, LED2 가모두점멸한다. if((pind & 0x02) == 0x00) // SW1 가 Push 되면 vled1task 를 Running 상태로천이시킨다. vtaskresume(xhandleled1); PORTF = 0x80; // vled1task 이 Running State 인것을 LED 에표시한다. EIMSK = 0x03; // External Interrupt 0, 1 Enable static void init_device(void); portshort main(void) init_device();
// Semaphore 는사용하기전에생성되어야한다.. // Mutex type semaphore 를 create 한다. xmutex = xsemaphorecreatemutex(); vsemaphorecreatebinary( xkeyinsemaphore ); // LED 제어 Task 는각각 Pseudo Random 하게 Delay 된다. // Pseudo random delay 를발생시키기위한 Seed Number(random) 를설정한다. srand( 567 ); // Semaphore 가성공적으로생성되었는지확인한다. if( (xmutex!= NULL) & (xkeyinsemaphore!= NULL) ) xtaskcreate(vtaskcontroltask, (signed portchar *) "vtaskcontroltask", configminimal_stack_size, NULL, tskidle_priority + 4, &xhandlecontrol ); // vledtask 의 Instance 3 개 (vled1task, vled2task, vled3task) 가생성된다. // 각각의 Instance 는 "0", "1", "2" 를인수로전달한다. xtaskcreate(vledtask, (signed portchar *)"vled1task", configminimal_stack_size, "0", tskidle_priority + 1, &xhandleled1 ); xtaskcreate(vledtask, (signed portchar *)"vled2task", configminimal_stack_size, "1", tskidle_priority + 1, &xhandleled2 ); // vprint3task 의 Priority 를가장높게한다. // 그러나 vprint1task, vprint2task 가자원을먼저선점하면자원사용이완료될때까지 Blocked 상태에있게된다. xtaskcreate(vledtask, (signed portchar *)"vled3task", configminimal_stack_size, "2", tskidle_priority + 2, &xhandleled3 ); // RunSchedular vtaskstartscheduler(); for(;;) static void init_device(void) cli(); //disable all interrupts // output 1, input 0 outp(0x00,ddrd);
outp(0xff,portd); //let pull up resistor work, so pin x will be one all the time. outp(0xff,ddrf); outp(0x00,portf);//clear LED. EICRA = 0x0a; // External Interrupt 0, Falling Edge Asynchronously Interrupt EIMSK = 0x03; // External Interrupt 0, 1 enable sei(); //re-enable interrupts // SW0, SW1 의 Interrupt 처리를위한 Interrupt Service Routine SIGNAL (INT0_vect) static portbase_type xhigherprioritytaskwoken; EIMSK &= ~0x03; // External Interrupt 0, 1 Disable xhigherprioritytaskwoken = pdfalse; /* Semaphore Give 하여 Semaphore 가준비되기를기다리는 (SW 가 Push 되기를기다리는 ) Blocking 상태의 Task 중 Priority 가높은 Task 가 Unblocking 상태가되게한다. */ xsemaphoregivefromisr( xkeyinsemaphore, &xhigherprioritytaskwoken ); if( xhigherprioritytaskwoken == pdtrue ) /* Semaphore 'Give' 에의하여 Unblock되는 Task의 Priority가현재실행중인 Task 보다높은경우 Interrupt는직접 Unblock된 Task로 Return 된다. */ taskyield(); ISR(INT1_vect, ISR_ALIASOF(INT0_vect)); 2) RT_mutex_printf 프로그램참고요
Mutex 를이용한자원관리예 ( Task2 의 Priority 가 Task1 보다높은경우 ) 2.Task1이 Mutex를 Take 하고, 문자열을출력하는도중에우선순위가높은 Task2가실행된다.. Task2 Task1 Idle 3.Task2가 Mutex Take를시도하였으나아직 Task1 이 Mutex를갖고있기때문에 Task2가 Blocked 상태가되고다시 Task1이실행되어문자열출력을종료한다. 5.Task2가문자열출력을종료하고, Mutex를 Give Back 한다. Task2는다음실행을기다리기위하여 Blocked 상태가된다. Task1이다시실행된다음, 종료되고 Idle 상태가된다. t1 Time 1.Task1 의 Delay 주기 가종료되고 Task1 이 실행된다. 4.Task1 이문자열출력을종료하고 Mutex 를 Give Back 한다. Task2 가 Blocked 상태 를종료하고실행상태가된다. C. Priority Inversion 위예에서낮은 Priority Task( LP Task) 가먼저 Mutex 을 Take 하여높은 Priority Task( HP Task) 가 LP Task 가 Mutex 를 Give 할때까지기다리는경우가발생하는것이가능한것을알수있다. 이와같이 HP Task 가 LP Task 에의하여 Delay 되는것을 Priority Inversion 이라고한다. 만약의경우, HP Task 가 Semaphore 를기다리는동안중간 Priority 을갖는 Task( MP Task) 가실행되는경우 LP Task 가실행기회를갖지못하게되고, LP Task 가 Mutex 를 Give 하지못하기때문에 HP Task 가계속실행되지못하는문제가발생한다. 이러한경우의시나리오예는아래와같다.
Priority Inversion 경우중가장나쁜경우의시나리오 2.HP Task가 Mutex를 Take하려고시도하지만 LP Task가 Mutex를 Take한상태이기때문에 Blocked 상태가된다. HP Task 5.MP Task가실행되고있다. HP Task는아직 LP Task로부터 Mutex가 Return 되기를기다리고있는상태이다. 그러나 LP Task가실행되지못하기때문에 HP Task가계속실행되지못하게된다. MP Task LP Task t1 Time 1.LP Task 가 HP Task 보다 먼저 Mutex 를 Take 하고자 원을사용한다. 3.LP Task 실행되는도중 ( 아직 Mutex 를 Give 하지못한상태 ) 에보다우선도가높 은 Task(MP Task) 가실행된다. Priority Inversion 은심각한문제이지만소규모시스템에서는시스템설계시이러한문제를피할수있다. D. Priority 상속 FreeRTOS 에서 Mutex 와 Binary Semaphore 는매우유사하다. Mutex 와 Binary Semaphore 의차이는 Mutex 는 Priority 상속기능을갖고있다는것이다. Priority 상속개념은 Priority Inversion 문제를최소화한다. Priority 상속에관련된 System Time 해석은매우복잡하고, 또한 Priority Inversion 문제를완전히해결하지는못한다. Priority 상속은현재 Mutex 를 Take 하고있는 Task 보다더높은 Priority 를갖는 Task 가동일한 Mutex 를 Take 하려고시도하는경우에현재실행중인 Task 의 Priority 를임시적으로 Mutex 를 Take 하려고시도하는 Task 중에서가장높은 Priority 를갖는 Task 와동일하게높은 Priority 를부여하는것이다. 현재 Mutex 을갖고있는 Task 가 Mutex 를 Give 하는경우이 Task 는원래자신이갖고있던 Priority 를갖게된다. 한 Task 는어느한순간에하나의 Mutex 만갖는다는가정하에 Mutex 에의한 Priority 상속구조는구현되었다.
Priority 상속기능을갖는 Mutex 를이용한자원공유예 2.HP Task가 Mutex를 Take하려고시도하지만 LP Task가 Mutex를 Take한상태이기때문에 Blocked 상태가된다. LP Task가 HP Task의 Priority를상속받아높은 Priority를갖게된다. HP Task 4.LP Task가 Mutex를 Return 하였기때문에 HP Task가 Blocked 상태에서벗어난다음, Mutex를 Take 하여자원사용을완료하고 Mutex를 Give 한다. HP Task가대기상태가되면 MP Task가실행된다.. MP Task LP Task t1 Time 1.LP Task 가 HP Task 보다 먼저 Mutex 를 Take 하고자 원을사용한다. 3.LP Task는현재높은 Priority를갖고있기때문에 MP Task에의하여선점되지않고현재사용중인자원의사용을완료하고 Mutex를 Give 한다. LP Task의 Priority는이전 Priority로복귀된다 E. Deadlock Mutex 를이용하여자원을배타적으로사용하는경우 Deadlock 이 l 발생할수있다. Deadlock 은 2 개의 Task 가서로상대가갖고있는자원사용권한을기다리는상태가되어두 Task 모두가실행될수없도록되는경우이다. Task A 와 Task B 가모두 Mutex X 와 Mutex Y 가필요한경우 1) Task A 가실행중 Mutex X 를 Take 한다. 2) Task A 가 Task B 에의하여선점 (Pre-empted) 된다. 3) Task B 가 Mutex Y 를 Take 한다. 또한 Mutex X 를 Take 하기위한시도를한다. 그러나 Mutex X 는 Task A 가 Take 하고있기때문에 Task B 는 Blocked 상태가되어 Mutex X 가 Release 되기를기다리고있다. 4) Task A 가실행되고있는상태에서 Mutex Y 를 Take 하기위한시도를한다. Mutex Y 는 Task B 가 Hold 하고있기때문에 Task
A 는 Blocked 상태가되어 Muntex Y 가가 Release 되기를기다리고있다. 위의시나리오결과 Task A 는 Task B 가 Hold 하고있는 Mutex 를기다리고, Task B 는 Task A 가 Hold 하고있는 Mutex 를 Blocked 상태에서기다리고있기때문에두 task 모두실행될수없게되는 Deadlock 이발생한다. Priority Inversion 과마찬가지로시스템설계시 Deadlock 를피하기위한노력을하여야한다. 4. Gatekeeper Tasks Priority Inversion 과 Deadlock 를피하기위하여 Gatekeeper Tasks 를사용한다. Gatekeeper Task 만자원에대한소유권 ( 자원을사용할수있는권한 ) 을갖는다. Gatekeeper Task 만이유일하게자원을직접사용할수있는권한을갖고, 다른 Task 는 Gatekeeper 서비스를이용하여간접적으로만자원을사용할수있다. F. Gatekeeper Task 를이용하는예다음은 RT_gatekeeper_printf 프로그램에서 Gatekeeper Task 를이해하는데도움이되는부분만발췌한내용이다. // Tasks 와 Interrupt 이 Gatekeeper 를이용하여출력할문자열을정의한다. static char *pcstringstoprint[] = "Task 1 ****************\r\n", "Task 2 ----------------\r\n", "Tick hook interrupt ###\r\n" ; // xqueuehandle 형의변수를선언한다. // 이 Queue 는 Print Task 가 Gatekeeper Task 를이용하여문자열을출력하는데이용한다. xqueuehandle xprintqueue; void valtstartmutexprinttasks( void ) // Queue 는사용하기전에 Create 되어야한다. xprintqueue = xqueuecreate( 5, sizeof( char * ) ); // prvprinttask Task 는각각 Pseudo Random 하게 Delay 된다.
// Pseudo random delay 를발생시키기위한 Seed Number(random) 를설정한다. srand( 567 ); // Queue 가성공적으로 create 되었는지확인한다. if( xprintqueue!= NULL ) // prvprinttask 의 Instance 2 개 (vprint1task, vprint2task) 가생성된다. // 각 Instance 에서출력할문자열의 Index 가 Task Parameter 로 Pass 된다. // Task 는서로다른 Priority 로생성되어낮은 Priority 를갖은 Task 도문자열을안전하게출력하는예를보여준다. xtaskcreate( prvprinttask, "vprint1task", configminimal_stack_size * 2, ( void * ) 0, tskidle_priority + 1, NULL ); xtaskcreate( prvprinttask, "vprint2task", configminimal_stack_size * 2, ( void * ) 1, tskidle_priority + 2, NULL ); // Gatekeeper Task 의우선순위를가장낮게설정하고 Task 를생성한다. 이 Task 만 Standard Out Device 를 Access 할수있다. xtaskcreate( prvstdiogatekeepertask, "Gatekeeper", configminimal_stack_size * 2, NULL, tskidle_priority, NULL ); void prvstdiogatekeepertask( void *pvparameters ) char *pcmessagetoprint; // 이 Task 만오직출력장치에직접출력할수있다. // 다른 Task 는출력장치에직접출력할수없기때문에 // 다른 Task 는이 Task 를이용하여출력하여야한다. // 그결과 Gatekeeper Task 를이용하는경우에는 No Mutual // exclusion 나 Serialization 문제가발생하지않는다. for( ;; ) // Queue 에 Message 가도착하기를기다린다. xqueuereceive( xprintqueue, &pcmessagetoprint, portmax_delay );
// 메세지를표준출력장치에출력한다. printf( "%s", pcmessagetoprint ); fflush( stdout ); // Tick hook(or tick callback) function 의사용예 // Tick hook(or tick callback) function 은매 Tick interrupt 주기매다 // Kernel 에의하여 Call 되기때문에 ( 우선순위가높은 Task 에서출력예 ) // 아주간결하게작성되어야하고, // FreeRTOS API 함수에서 Call 하여서는않된다. void vapplicationtickhook( void ) static int icount = 0; portbase_type xhigherprioritytaskwoken = pdfalse; // 매 600 ticks 마다 Tick Hook Interrupt Routine 에서출력하는 // 메세지가 Gatekeeper Task 를이용하여출력된다. icount++; if( icount >= 600 ) // 이예에서 xhigherprioritytaskwoken 은사용되지않지만함수에 Parameter 로포함되어야한다. // 매 600 Tick 마다 3 번째메세지를출력하기위하여 Queue 의앞 (Front) 에메세지포인터를출력한다. xqueuesendtofrontfromisr( xprintqueue, &( pcstringstoprint[ 2 ] ), &xhigherprioritytaskwoken ); // 600 ticks 를만들기위하여 icount 를 Reset 한다. icount = 0; // prvprinttask Instance 2 개 (vprint1task, vprint2task) 가생성되어실행된다. // Instance 는 Instance 에서출력하고자하는문자열의 Pointer 를인수로전달받는다. void prvprinttask( void *pvparameters ) int iindextostring; // 이 Task 의 Instances 를 2 개 Create 한다. Create 된 Task 가실행
되면출력할메시지의 Index Pointer 를 Queue 를이용하여 prvstdiogatekeepertask 에 Pass 하여표준출력장치에출력한다. iindextostring = ( int ) pvparameters; for( ;; ) // 이 Task 에서는문자열을직접출력하지않고, Queue 를이용하여 Gatekeeper Task 에출력정보 ( 문자열의 Pointer 등 ) 를전달하여실제출력은 Gatekeeper Task 에서실행된다. xqueuesendtoback( xprintqueue, &( pcstringstoprint[ iindextostring ] ), 0 ); // 이 Task 가자원을사용한후, 랜덤하게발생된지연시간동안 Blocked 상태에있도록하여낮은우선도의 Task 가자원을사용할수있는기회를준다. vtaskdelay( ( rand() & 0x03FF ) );