Jak realizowana jest wielozadaniowość na pojedynczym CPU oraz jakie są jej konsekwencje? Czy “mutex to taki binarny semafor”? Dlaczego Windows nie jest używany do sterowania rakietami oraz jaki popularny błąd omal nie spowodował utraty jednej z misji NASA?
Wirtualizacja sieci na przykładzie OpenContrail vRouter.
Jak napisać własny RTOS!
1. #3 - BarCamp Semihalf
System wbudowany? - Zrób to sam!
Jak napisać własny RTOS?
Radosław Biernacki
radoslaw.biernacki@gmail.com
2. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Chętnie wyjaśnię wątpliwości, proszę o
zgłaszanie się w przypadku niejasności.
Nagrody za aktywność
3. Czym jest RTOS?
RTOS - ang. Real Time Operating System
RTOS vs inne systemy np Linux, Windows:
- nie dzielą czasu CPU proporcjonalnie do priorytetu zadania
- zawsze wykonuje najważniejsze zadanie kosztem pozostałych zadań
- deterministyczny czas reakcji na zdarzenia
- (zazwyczaj) nie obsługują pamięci wirtualnej
- zastosowania sterowanie procesami fizycznymi (rakiety, motoryzacja,
automatyka)
4. Po co pisać własny RTOS?
- bo nie jest to trudne!
- bo to dobre ćwiczenie z zakresu systemów operacyjnych
- sucha teoria vs praktyka
- git clone https://github.com/rosly/rados.git
5. Ale od czego zacząć? Czym jest zadanie?
Zadanie jest programem, który
używa procesora w taki
sposób, jak gdyby posiadało
go na wyłączność.
Iluzja równoległego wykonania
= okresowe „przełączenia”
procesora pomiędzy
zadaniami.
cpi r19, 0x9A ; 154
and r3, r1
subi r20, 0x3F ; 63
sbci r21, 0x1F ; 31
subi r21, 0x33 ; 51
subi r20, 0x25 ; 37
cpi r21, 0xA4 ; 164
cpi r18, 0x10 ; 16
cpi r18, 0xD0 ; 208
and r3, r13
ori r22, 0x9C ; 156
andi r23, 0x43 ; 67
ori r21, 0x9F ; 159
subi r23, 0xF3 ; 243
ori r22, 0xD5 ; 213
andi r23, 0x40 ; 64
ldd r18, Y+7 ; 0x07
ldd r19, Y+8 ; 0x08
add r24, r18
adc r25, r19
std Y+2, r25 ; 0x02
std Y+1, r24 ; 0x01
ldd r24, Y+1 ; 0x01
ldd r25, Y+2 ; 0x02
call 0xfc2 ; 0xfc2
std Y+4, r25 ; 0x04
std Y+3, r24 ; 0x03
ldd r24, Y+3 ; 0x03
ldd r25, Y+4 ; 0x04
sbiw r24, 0x02 ; 2
std Y+6, r25 ; 0x06
mov r18, r24
ldd r24, Y+7 ; 0x07
ldd r25, Y+8 ; 0x08
movw r30, r24
std Z+20, r18 ; 0x14
ldd r24, Y+5 ; 0x05
ldd r25, Y+6 ; 0x06
movw r30, r24
std Z+11, r1 ; 0x0b
std Z+10, r1 ; 0x0a
ldd r24, Y+5 ; 0x05
ldd r25, Y+6 ; 0x06
adiw r28, 0x09 ; 9
in r0, 0x3f ; 63
cli
out 0x3e, r29 ; 62
out 0x3f, r0 ; 63
out 0x3d, r28 ; 61
8. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Czy są pytania?
9. 0x41
0x56
0xFF
0x12
0xAB
Czym jest kontekst zadania?
R0
R30 [ZL]
R31 [ZH]
R28 [YH]
R29 [YH]
R26 [XH]
R27 [XH]
R25
R1
SREG
PC
SPH SPL
cpi r19, 0x9A ; 154
and r3, r1
subi r20, 0x3F ; 63
sbci r21, 0x1F ; 31
subi r21, 0x33 ; 51
subi r20, 0x25 ; 37
cpi r21, 0xA4 ; 164
cpi r18, 0x10 ; 16
cpi r18, 0xD0 ; 208
and r3, r13
ori r22, 0x9C ; 156
andi r23, 0x43 ; 67
ori r21, 0x9F ; 159
subi r23, 0xF3 ; 243
ori r22, 0xD5 ; 213
andi r23, 0x40 ; 64
Rejestry
RAM
ROM
Co zrobić żeby zapisać to ->
Użyć innego stosu niż
<- ten
I skoczyć poza to
<- to
10. Zapisanie kontekstu do TCB
290 #define arch_contextstore_i()
291 __asm__ __volatile__ (
293 "push r16"
298 "in r16, __SREG__"
299 "sbr r16, 0x80"
300 "push r16"
306 "push r28"
307 "push r29"
310 "push r0"
311 "push r1"
… (z pominięciem r16, r28 i r29)
339 "push r31"
346 "in r28, __SP_L__"
347 "in r29, __SP_H__"
353 "lds r30, task_current"
354 "lds r31, task_current+1"
355 "st Z, r28"
356 "std Z+1, r29"
R0 - R31
PC
SP
TCB
starego
zadania
ret PC
R16
SREG
R28
R29
R0 - R15
R17 - R27
R30 - R31
SREG
task_current
SP
TCB
nowego
zadania
SP
Stos starego zadania
45 os_task_t* task_current;
78 typedef struct {
81 arch_context_t ctx;
84 list_t list;
96 os_taskstate_t state;
...
} os_task_t;
56 typedef struct {
57 uint16_t sp;
58 } arch_context_t;
11. Przywrócenie kontekstu z TCB
391 #define arch_contextrestore_i()
392 __asm__ __volatile__ (
402 "lds r30, task_current"
403 "lds r31, task_current+1"
404 "ld r16, Z"
405 "ldd r17, Z+1"
406 "out __SP_L__, r16"
407 "out __SP_H__, r17"
410 "pop r31"
… (z pominięciem r16, r28 i r29)
438 "pop r1"
439 "pop r0"
440 "pop r29"
441 "pop r28"
447 "pop r16"
455 "out __SREG__, r16"
456 "pop r16"
459 "ret"
R0 - R31
PC
SP
TCB
starego
zadania
ret PC
R16
SREG
R28
R29
R0 - R15
R17 - R27
R30 - R31
SREG
task_current
SP
TCB
nowego
zadania
ret PC
R16
SREG
R28
R29
R0 - R15
R17 - R27
R30 - R31
SP
* skok do wcześniej przerwanego kodu
* podmiana wskaźnika zadania
Stos starego zadania Stos nowego zadania
całkiem nowy
45 os_task_t* task_current;
78 typedef struct {
81 arch_context_t ctx;
84 list_t list;
96 os_taskstate_t state;
...
} os_task_t;
56 typedef struct {
57 uint16_t sp;
58 } arch_context_t;
13. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Czy są pytania?
14. Scheduler i priorytetyzacja zadań
Task1 hi prio
Task2
Task3
Task4 low prio
O(1) Enqueue any O(1) Dequeue top prio
with FIFO for equal prio
os_taskqueue_t ready_queue : READY -> RUNNING
254 os_task_t *task = os_taskqueue_dequeue(&ready_queue)
***
zły projekt = głodzenie
„fair schedule”
18. Blokowanie schedulera vs sekcje krytyczne
135 arch_criticalstate_t cristate;
136
138 arch_critical_enter(cristate);
...
144 arch_critical_exit(cristate);
- użytkownik może wyłączyć
przerwania na czas wywołań OS
- kod przerwań może obsługiwać
zagnieżdżanie (włączenie przerwań
przed powrotem)
62 volatile os_atomic_t isr_nesting = 0;
137 extern volatile os_atomic_t sched_lock;
...
219 static inline void os_scheduler_intlock(void)
220 {
221 os_atomic_inc(sched_lock);
222 }
223
224 static inline void os_scheduler_intunlock(bool sync)
225 {
226 os_atomic_dec(sched_lock);
227
228 if (!sync)
229 {
232 os_schedule(1);
233 }
234 }
0 - kontekst zadania
1 - kontekst przerwania
>1 - zagnieżdżone przerwanie
19. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Czy są pytania?
21. This is your last chance. After this, there is no turning back
Embedded ?
Everything runs in
cloud now ...
How deep the
rabbit hole goes ?
(4 strony listingów)
22. Kod os_sem_down()
78 os_retcode_t OS_WARN_UNUSEDRET os_sem_down(
79 os_sem_t* sem,
80 uint_fast16_t timeout_ticks)
81 {
82 os_retcode_t ret;
83 os_timer_t timer;
84 arch_criticalstate_t cristate;
85
86 OS_ASSERT(0 == isr_nesting); /* przerwanie ? */
87 OS_ASSERT(task_current == &task_idle);
89
92 arch_critical_enter(cristate); /* sekcja krytyczna */
93 do
94 {
95 if (sem->value > 0) /* potencjalna optymalizacja dla ARM */
96 {
102 --(sem->value);
103 ret = OS_OK;
104 break; /* po kłopocie */
105 }
108 if (OS_TIMEOUT_TRY == timeout_ticks)
109 {
111 ret = OS_WOULDBLOCK; /* musiał bym uśpić zadanie */
112 break;
113 }
114
116 if (OS_TIMEOUT_INFINITE != timeout_ticks) /* pominiemy detale */
117 {
119 os_blocktimer_create(&timer, os_sem_timerclbck, timeout_ticks);
120 }
121
123 os_task_block_switch(&(sem->task_queue), OS_TASKBLOCK_SEM);
124
127 os_blocktimer_destroy(task_current);
128
131 ret = task_current->block_code;
132
133 } while (0);
134 arch_critical_exit(cristate);
135
136 return ret;
137 }
powrót dopiero po wybudzeniu
dalej na następnym slajdzie
23. Wstrzymywanie zadania
515 void OS_HOT os_task_block_switch(
516 os_taskqueue_t* task_queue,
517 os_taskblock_t block_type)
518 {
520 os_task_makewait(task_queue, block_type);
524 arch_context_switch(os_taskqueue_dequeue(&ready_queue));
531 task_current->state = TASKSTATE_RUNNING;
532 }
187 static inline void os_task_makewait(
188 os_taskqueue_t *task_queue,
189 os_taskblock_t block_type)
190 {
193 task_current->state = TASKSTATE_WAIT;
194 task_current->block_type = block_type;
195 os_taskqueue_enqueue(task_queue, task_current);
196 }
Pseudo kod (oryginalnie ASM)
61 void OS_NAKED OS_HOT arch_context_switch(
os_task_t * new_task)
62 {
63 arch_contextstore(); /* podobna do już omawianej */
106 task_current = new_task; /* przemilczana poprzednio */
107 arch_contextrestore(); /* podobna do już omawianej */
108 }
skok przy powrocie
???
1
0
1
1
Head list_t list_t
Head list_t
Head list_t list_t
Head
arch_bitmask_t
gdyby ktoś zapomniał
sem->task_queue i ready_queue ->
&(sem->task_queue)
nigdy =NULL bo idle_task
24. Gdzie jesteśmy ?
83 void os_sem_create(os_sem_t* sem, os_atomic_t init_value);
109 void os_sem_destroy(os_sem_t* sem);
140 os_retcode_t OS_WARN_UNUSEDRET os_sem_down(
141 os_sem_t* sem,
142 uint_fast16_t timeout_ticks);
165 void os_sem_up_sync(os_sem_t* sem, bool sync);
54 typedef volatile arch_atomic_t os_atomic_t;
64 typedef struct os_sem_tag {
66 os_taskqueue_t task_queue;
69 os_atomic_t value;
71 } os_sem_t;
value > 0
os_sem_up() nieblokujące
dowolne zadanie
os_sem_down()
uśpienie
wybudzenie
sem->task_queue
26. Wznawianie zadań
467 void OS_HOT os_schedule(uint_fast8_t higher_prio)
468 {
469 os_task_t *new_task;
470
474 if (OS_LIKELY((isr_nesting <= 1) && (0 == sched_lock)))
475 {
478 new_task = os_taskqueue_dequeue_prio(
479 &ready_queue, task_current->prio_current + higher_prio);
480
482 if (NULL != new_task) /* może NULL, patrz higher_prio */
483 {
486 os_task_makeready(task_current); /* bieżące musi przejść do READY */
488 if (0 == isr_nesting) /* czy przerwanie ? */
489 {
490 arch_context_switch(new_task);
491 } else {
495 task_current = new_task; /* przerwanie!!… context switch na końcu ISR */
496 }
497 /* tu wrócimy dopiero po ponownym przydzieleniu CPU */
498 task_current->state = TASKSTATE_RUNNING;
499 }
500 }
501 }
181 static inline void os_task_makeready(os_task_t *task)
182 {
183 task->state = TASKSTATE_READY;
184 os_taskqueue_enqueue(&ready_queue, task);
185 }
Pseudo kod (oryginalnie ASM)
61 void OS_NAKED OS_HOT arch_context_switch(
os_task_t * new_task)
62 {
63 arch_contextstore();
106 task_current = new_task;
107 arch_contextrestore();
108 }
skok przy ret
???
27. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Czy są pytania?
28. Preemption - Wywłaszczanie zadań
Zadanie nisko
priorytetowe
Przerwanie wybudzające
zadanie wysoko priorytetowe
Zadanie wysoko priorytetowe
Czas procesora
Przydział
procesora
czas reakcji na
zdarzenie
sekcja krytyczna
kernela
zgłoszenie przerwania
Zadanie nisko
priorytetowe
Przerwanie wybudzające
zadanie wysoko priorytetowe
Zadanie wysoko priorytetowe
Czas procesora
Przydział
procesora
czas reakcji na
zdarzenie
sekcja krytyczna
kernela
zgłoszenie przerwania
Cooperative multitasking
np Contiki
Preemptive multitasking
omawiany system
29. Przykład przerwania przez takt zegara systemowego
89 #define OS_ISR __attribute__((naked, signal, used, externally_visible))
183 void OS_ISR TIMER1_COMPA_vect(void)
184 {
185 arch_contextstore_i(tick);
186
189 os_tick(); /* potencjalne przełączenie task_current w os_sched() */
194
195 arch_contextrestore_i(tick); /* potencjalny context switch i skok przez reti*/
196 }
31. Rozkład jazdy
1. Wstęp do tematu i motywacja
2. Czym jest zadanie?
3. Przełączanie kontekstu
4. Do czego służy scheduler?
5. Sekcje krytyczne systemu
6. Blokowanie i wznawianie zadań
7. Wywłaszczanie zadań
8. Mutex i inwersja priorytetów
Czy są pytania?
32. Mutex to taki binarny semafor ?
Jaka jest między nimi różnica ?
33. Co zdarzyło się na marsie ?
Zadanie nisko priorytetowe L
Zadanie wysoko priorytetowe H
Zadanie średnio priorytetowe M
Czas procesora
Przerwanie wybudzające
A B C D E F
http://research.microsoft.com/en-us/um/people/mbj/Mars_Pathfinder/Authoritative_Account.html
- mutex to więcej niż binarny semafor
- mutex przeciwdziała inwersji priorytetów poprzez dziedziczenie priorytetów
- mutex posiada odwołanie do aktualnego właściciela
- tylko właściciel może odblokować mutex ***
-
34. Definicja książkowa - czy to wystarczy ?
“Process scheduling algorithm increases the priority of a process (A) to the maximum priority of any other
process waiting for any resource on which A has a resource lock”
Z1 i Z2 reprezentują dwa mutexy chroniące dwa współdzielone zasoby
Opis ponad linją aktywacji wątku reprezentuje blokowany zasób.
Zadanie nisko priorytetowe L
Zadanie wysoko priorytetowe H
Zadanie średnio priorytetowe M2
Czas procesora
Przerwanie wybudzające
A B C D E F G H I
Zadanie średnio priorytetowe M1
Z2
Z1, (Z2)
(Z1)
Problem ten występuje
również w przypadku
mieszania typów
blokad, np drivery IO
(brak propoagacji)
36. Uwaga końcowa
Dziedziczenie priorytetów może pomóc w przypadkach kiedy współdzielenie
danych pomiędzy zadaniami z różnymi poziomami priorytetów jest trudna do
uniknięcia lub wprowadzona została przypadkowo. Nie należy jednak z góry
zakładać że system operacyjny rozwiąże problemy słabo zaprojektowanej
aplikacji.
37. Copyright (c) 2015 Semihalf. Confidential proprietary
Dziękuje za uwagę
Pytania ?