Automatyka i Robotyka Systemy czasu rzeczywistego Wykład 3
Typy procesów i wątków procesy i wątki asynchroniczne – aktywowane przerwaniami procesy i wątki synchroniczne – aktywowane układami odmierzania czasu procesy i wątki drugoplanowe – aktywowane gdy procesor ma wolne zasoby
Zdarzenia są reprezentowane przez przerwania RTS mają reagować na zdarzenia (w restrykcyjnym czasie!) Zdarzenia są reprezentowane przez przerwania Przerwanie inicjuje ISR – procedurę obsługi przerwania i dodatkowo może się odbyć: odblokowanie wątku odblokowanie procesu Procesy i wątki asynchroniczne pozostają w stanie zablokowania wznawiane przez przerwania (zdarzenia systemowe) po wykonaniu wracają do stanu zablokowania
W czasie ISR inne przerwania są zablokowane ISR wykonuje proste czynności, które nie podlegają szeregowaniu W czasie ISR inne przerwania są zablokowane Następnie kod obsługi przerwania może przekazać działanie procesowi lub wątkowi Zwykle wątki (bo mają krótszy czas wznowienia) są utworzone dla przerwań i czekają gotowe na przerwanie Wystąpienie przerwania inicjuje: obsługę ISR aktywację wątku dla tego przerwania przejście wątku w stan oczekiwania
wątek obsługi przerwania ISR W2 odblokowanie wątku W2 przerwanie blokada wątek obsługi przerwania proces1 wątek proces3 wątek wątek proces4 wątek proces2 wątek wątek wątek wątek wątek wątek wątek wątek .. do problematyki wątków jeszcze wrócimy….
…podsumowując, mogą być 3 sytuacje: ISR wykona proste czynności ISR wykona proste czynności, resztę zleci procesowi lub wątkowi ISR od razu zleci czynności procesowi lub wątkowi
Inicjacja RTS Trudność studiowania długich instrukcji, aby poznać docelową platformę. Analizowanie tabel pamięci, rejestrów i diagramów przedstawiających wewnętrzną strukturę sprzętową. Problemy: - jakie sposoby załadowania obrazu systemu podział obszarów pamięci, w których poszczególne części systemu powinny się znaleźć jakie metody inicjalizacji aplikacji w jaki sposób pobierane będą dane wejściowe i generowane sygnały wyjściowe.
Po uruchomieniu urządzenia, przejmuje nad nim kontrolę tzw Po uruchomieniu urządzenia, przejmuje nad nim kontrolę tzw. boot image (również obecny w ROM) oraz inicjalizuje sprzęt. Kiedy pamięć oraz podstawowe urządzenia są już zainicjalizowane, zaczyna wykonywać się loader - IPL, czyli właściwy program ładujący, który znajduje się na docelowym urządzeniu - zwykle w pamięci ROM - i służy do pobrania z komputera hosta obrazu systemu - współdziała z oprogramowaniem hosta. Obraz może też być w ROM. Używa się łącza szeregowego, chociaż programy ładujące potrafią korzystać z połączeń sieciowych oraz dokonywać operacji na pamięci flash.
Loader pobiera obraz bezpośrednio do pamięci RAM Loader pobiera obraz bezpośrednio do pamięci RAM. Musi rozumieć format przesyłanego pliku - zawarte są w nim instrukcje określające pod jakimi adresami oraz w jakich częściach pamięci należy umieścić określone sekcje. Po zakończeniu transmisji, program ładujący przekazuje kontrolę do zainstalowanego obrazu.
Po uruchomieniu urządzenia, jego procesor zaczyna pobierać kolejne instrukcje z określonego systemowego obszaru pamięci. Znajduje się tam tzw. wektor resetujący, który ze względu na swoje ograniczone rozmiary, zawiera tylko instrukcję skoku do miejsca zawierającego właściwy kod inicjalizujący.
Zaraz po uruchomieniu urządzenia, rejestry jego procesora przyjmują wartości domyślne. Następnie, program ładujący: wyłącza procedury obsługi przerwań, inicjalizuje pamięć RAM, inicjalizuje pamięci podręczne procesora dokonuje bardzo podstawowego sprawdzenia sprzętu. Przyczyną wyłączenia obsługi przerwań jest brak gotowości systemu do ich obsługi. Pamięć RAM jest o wiele szybsza niż ROM, czy flash oraz dodatkowo umożliwia prostsze wykonanie operacji zapisu, dlatego też program ładujący kopiuje całość lub tylko część obrazu systemu do pamięci RAM. Później, następuje uruchomienie sprzętu, jak np. urządzenia wejścia - wyjścia oraz sterowanie przejmuje właściwy system operacyjny.
Obraz, który jest ładowany do maszyny docelowej, to zbiór wielu pakietów oprogramowania. Są to między innymi: moduły odpowiedzialne za obsługę płyty, liczne sterowniki, system czasu rzeczywistego, który dostarcza podstawowe usługi i umożliwia operacje wejścia - wyjścia
Board Support Packages
Wszystkie te komponenty, są odpowiedzialne za pełną inicjalizację urządzenia. inicjalizacja sprzętu, instalacja procedur obsługi przerwań i wyjątków, inicjalizacja samego systemu czasu rzeczywistego oraz poszczególnych jego aplikacji. Najbliżej sprzętu jest zbiór sterowników dla płyty głównej (BSP - board support packages – pakiety wspomagania płyty) - zazwyczaj są one pisane w języku niskiego poziomu oraz związane są z konkretną architekturą!
Pozostałe moduły implementowane w językach wysokiego poziomu - jak np Pozostałe moduły implementowane w językach wysokiego poziomu - jak np. C. Uruchomienie samego systemu czasu rzeczywistego: inicjalizację jego obiektów i usług, jak np. procesy i wątki, kolejki, semafory, stworzenie stosu pamięci, obsługa komunikacji. Gdy zostanie uruchomiony moduł szeregujący procesy –(scheduler), użytkownik może uruchamiać wszelkie inne obecne w pamięci aplikacje. Wówczas system ma możliwość podziału dostępu do procesora oraz przekazywanie aplikacjom sterowania.
Hardware Database SDP info http://community.qnx.com/sf/wiki/do/viewPage/projects.bsp/wiki/BSPAndDrivers Hardware Database SDP info 2011-02-25 Advantech AIMB-580 6.5.0 Intel® Core™ i7/i5/i3/Pentium® processor with Q57 chipset 2011-02-25 Advantech AIMB-270 6.5.0 Intel® Core™ i7 and i5 mobile processor with Intel QM57 chipset 2011-02-25 Advantech PCM-9562 6.5.0 Embedded Intel® Atom™ processor N450 Single Core/D510 Dual Core 1.66 GHz + ICH8M 2010-06-07 Advantech SOM-6760 6.4.x Intel Atom Z5xx & System Controller Hub US15W with Fastboot IPL and BIOS 2007-11-29 Concurrent Technologies Specifications 2008-06-19 Intel Crown Beach CRB 6.3.2 Intel Atom & System Controller Hub US15W 2008-12-12 Intel EP80579 aka "Tolopai" 6.4 Intel EP80579 SOC 2009-01-30 Kontron nanoETXexpress-SP 6.4.x Intel Atom Z5xx & System Controller Hub US15W with Fastboot IPL and BIOS 2010-07-08 MEN Intel Atom 6.3.0 SP3 Intel Atom MEN Mikro Electronik Commercial Men Mikro Elektronik 2009-04-02 x86 BIOS 6.3.0 SP2, 6.3.2, 6.4.x x86 2007-09-26 Dell PowerEdge 840 BASE Desktop/Server x86 2007-12-14 Dell Lattitude D820 Laptop 6.3.0 SP3, x86 2008-06-27 Dell Lattitude D830 Laptop 6.3.2, 6.4.x x86 …i wiele innych
Rozruch systemu QNX – podsumowanie Procesor wykonuje kod zapisany w BIOS'ie (albo ROM monitorze) BIOS przekazuje sterowanie do loadera - IPL. Tutaj ładowany jest do pamięci program startup Uruchamiany jest startup, ładowana jest pozostała część obrazu (OS boot image) Sterowanie przekazywane jest do procnto. Ten wykonuje skrypty startowe i kontynuuje pracę.
QNX Momentics IDE Ułatwia szybkie tworzenie aplikacji RTS Integrated Developement Environment zintegrowane środowisko programistyczne Ułatwia szybkie tworzenie aplikacji RTS RAD – rapid application development Testowanie systemu: host aplikacji – wykonanie na QNX – target - cel
Kolejność czynności: Uruchomienie agenta qconn w VMPlayer Nowy QNX/C projekt – built variant x86 Kompilacja – (Built project) Ustawienie celu (target) – komunikacja z maszyną wirtualną, gdzie mamy QNX – Run configurations… Wykonanie – Run ze środowiska IDE – target - IP maszyny wirtualnej bezpośrednio z QNX – po udostępnieniu binariów programu z wykorzystaniem IP wirtualnej karty sieciowej VMNet (proces fs-cifs) Obserwacja działającego systemu – perspektywa QNX
Argumenty procesu głównego int main(int argc, char *argv[]) { printf("Pierwszy proces\n"); printf("Arg0:%s\n",argv[0]); printf("Arg1:%s\n",argv[1]); …. Pierwszy proces Arg0:execl1ja13008254188473 Arg1:abc
Pliki nagłówkowe #include <stdlib.h> #include <stdio.h> i inne #include <sched.h> #include <pthread.h> wykorzystamy niebawem
PID – identyfikator procesu Każdy proces ma swój PID – identyfikator. Możemy go wyświetlić korzystając z funkcji: int getpid() Procesy tworzą hierarchię zależności, każdy proces ma swojego rodzica (za wyjątkiem procesu systemowego procnto o identyfikatorze 1). Identyfikator rodzica można uzyskać za pomocą funkcji: int getppid() w systemie QNX polecenie: ps -A
printf("Proces macierzysty: %u\n", pid); Przykład …. int pid= getpid(); int ppid= getppid(); printf("Proces macierzysty: %u\n", pid); printf("Proces rodzic: %u\n", ppid);
Tworzenie kopii procesu macierzystego int pid=fork() Proces potomny w momencie utworzenia posiada: własny segment kodu, własny segment danych i stosu. Wartości zmiennych w procesie potomnym są takie same jak w procesie macierzystym przed wykonaniem funkcji fork, lecz potem oba procesy mogą dokonywać ich zmiany . Funkcja zwraca: 0 - w kodzie procesu potomnego PID nowego procesu - w procesie macierzystym -1 gdy nowy proces nie może być utworzony
Przykład stworzenie kopii procesu potomny macierzysty int x=10; printf("x=%u\n", x); int pm = getpid(); printf("Proces macierzysty: %u\n", pm); int pp = fork(); printf("---%u---%u---\n",getpid(), getppid()); if (pp==0) { printf("To pisze potomny:"); x++; printf("---%u---%u---%u\n",getpid(), getppid(),x); sleep(2); } else { printf("To pisze macierzysty:"); x--; stworzenie kopii procesu potomny macierzysty x=10 Proces macierzysty: 438312 ---438313---438312--- ---438312---176150--- To pisze potomny:---438313---438312---11 To pisze macierzysty:---438312---176150---9
int i,status; int x=10; printf("Proces MAIN start id====%u, x pocz=%u \n",getpid(),x); for (i=1;i<=2;i++) { printf("i: %u\n",i); int pp=fork(); if (pp==0) { x--; printf("Proces potomny odejmuje 1- id: %u id_ojca: %u, x=%u \n",getpid(),getppid(),x); } else { x+=4; printf("Proces macierzysty dodaje 4 - id: %u id_ojca: %u, x=%u\n",getpid(),getppid(),x); printf("OBYDWA: i: %u, x= %u\n",i,x); Proces MAIN start id====1372200, x pocz=10 i: 1 Proces potomny odejmuje 1 - id: 1372201 id_ojca: 1372200, x=9 Proces macierzysty dodaje 4 - id: 1372200 id_ojca: 176150, x=14 OBYDWA: i: 1, x= 9 OBYDWA: i: 1, x= 14 i: 2 Proces macierzysty dodaje 4 - id: 1372200 id_ojca: 176150, x=18 Proces macierzysty dodaje 4 - id: 1372201 id_ojca: 1372200, x=13 Proces potomny odejmuje 1 - id: 1372203 id_ojca: 1372200, x=13 Proces potomny odejmuje 1 - id: 1372202 id_ojca: 1372201, x=8 OBYDWA: i: 2, x= 18 OBYDWA: i: 2, x= 13 OBYDWA: i: 2, x= 8 Proces MAIN start id====1372200, x pocz=10 i: 1 Proces potomny odejmuje 1 - id: 1372201 id_ojca: 1372200, x=9 Proces macierzysty dodaje 4 - id: 1372200 id_ojca: 176150, x=14 OBYDWA: i: 1, x= 9 OBYDWA: i: 1, x= 14 i: 2 Proces macierzysty dodaje 4 - id: 1372200 id_ojca: 176150, x=18 Proces macierzysty dodaje 4 - id: 1372201 id_ojca: 1372200, x=13 Proces potomny odejmuje 1 - id: 1372203 id_ojca: 1372200, x=13 Proces potomny odejmuje 1 - id: 1372202 id_ojca: 1372201, x=8 OBYDWA: i: 2, x= 18 OBYDWA: i: 2, x= 13 OBYDWA: i: 2, x= 8 18 14 14 2 13 1 9 13 13 2 8
ważne priorytety i szeregowanie czyli 4 procesy … ale czasem jest tak! Proces MAIN start id====1388584, x pocz=10 i: 1 Proces macierzysty dodaje 4 - id: 1388584 id_ojca: 176150, x=14 Proces potomny odejmuje 1 - id: 1388585 id_ojca: 1388584, x=9 OBYDWA: i: 1, x= 14 OBYDWA: i: 1, x= 9 i: 2 Proces potomny odejmuje 1 - id: 1388586 id_ojca: 1388584, x=13 Proces macierzysty dodaje 4 - id: 1388584 id_ojca: 176150, x=18 OBYDWA: i: 2, x= 13 OBYDWA: i: 2, x= 18 Proces macierzysty dodaje 4 - id: 1388585 id_ojca: 1388584, x=13 Proces potomny odejmuje 1 - id: 1388587 id_ojca: 1388585, x=8 OBYDWA: i: 2, x= 8 13 14 2 18 1 9 13 2 8 ważne priorytety i szeregowanie
WAIT Funkcja wait umożliwia synchronizację procesów. W przypadku napotkania funkcji wait proces macierzysty oczekuje na zakończenie procesu potomnego (pierwszego, jeśli jest ich wiele) – wówczas dostępny jest status. Wcześniejsze zakończenie procesu macierzystego niż potomnego spowodowałoby, że potomny straciłby swojego przodka. Funkcja zwraca PID procesu potomnego. pid_t wait (int *status) Gdy status jest różny od NULL, stan procesu potomnego jest wskazywany przez zmienną status. Istnieją makro, jak np. WIFEXITED(status), umożliwiające odczytanie informacji ze zmiennej status: 1 – jeśli normalne zakończenie procesu 0 - jeśli błąd
pid=wait(&status); 18 14 14 wait 2 13 13 1 wait 9 2 8 int i,status,pid; int x=10; printf("Proces MAIN start id====%u, x pocz=%u \n",getpid(),x); for (i=1;i<=2;i++) { printf("i: %u\n",i); int pp=fork(); if (pp==0) { x--; printf("Proces potomny odejmuje 1- id: %u id_ojca: %u, x=%u \n",getpid(),getppid(),x); } else { x+=4; printf("Proces macierzysty dodaje 4 - id: %u id_ojca: %u, x=%u\n",getpid(),getppid(),x); pid=wait(&status); printf("OBYDWA: i: %u, x= %u\n",i,x); potomny macierzysty Proces MAIN start id====1404968, x pocz=10 i: 1 Proces macierzysty dodaje 4 - id: 1404968 id_ojca: 176150, x=14 Proces potomny odejmuje 1 - id: 1404969 id_ojca: 1404968, x=9 OBYDWA: i: 1, x= 9 i: 2 Proces macierzysty dodaje 4 - id: 1404969 id_ojca: 1404968, x=13 Proces potomny odejmuje 1 - id: 1404970 id_ojca: 1404969, x=8 OBYDWA: i: 2, x= 8 OBYDWA: i: 2, x= 13 OBYDWA: i: 1, x= 14 Proces macierzysty dodaje 4 - id: 1404968 id_ojca: 176150, x=18 Proces potomny odejmuje 1 - id: 1413161 id_ojca: 1404968, x=13 OBYDWA: i: 2, x= 18 18 14 14 wait 2 13 13 1 wait 9 2 8
pid=wait(&status); printf("pid %u status: %u\n",pid,WIFEXITED(status)); pid 1646634 status: 1
execl(path/fname,arg0,arg1,...,NULL) Przekształca bieżący proces w inny proces, którego kod wykonywalny zawarty jest w pliku fname, przekazuje mu parametry arg0, arg1,arg2, itd. execl(path/fname,arg0,arg1,...,NULL) czyli proces wywołujący jest zakończony
Projekt execl1 printf("Pierwszy proces\n"); int i; for (i=1;i<4;i++) { printf("i=%u\n",i); } execl("/mnt/tmp2/ex2/x86/o/execl2","execl2","10","20",NULL); printf("czy dziala?\n");// tego tekstu już nie będzie!!! return EXIT_SUCCESS; Projekt execl2 Pierwszy proces i=1 i=2 i=3 To drugi ile arg=3 x=execl2 x=10 x=20 aaa: No error printf("To drugi\n"); int x; printf("ile arg=%u\n",argc); for (x=0;x<argc;x++) { printf("x=%s\n",argv[x]); } perror("aaa"); return EXIT_SUCCESS;
Po udostępnieniu fs-cifs…
Tworzenie procesów współbieżnych spawnl Funkcja spawnl używana jest do tworzenia nowych procesów, z możliwością wyboru trybu pracy procesu macierzystego, który może być zakończony, wykonywany współbieżnie lub z oczekiwaniem na zakończenie potomnego. pid_t spawnl(int mode, char * path, arg0,arg1,..., argN,NULL) mode - tryb wykonania procesu: P_WAIT, P_NOWAIT, P_OVERLAY, P_NOWAITO, path – ścieżka z nazwą pliku wykonywalnego, arg0 - argument 0 przekazywany do funkcji main tworzonego procesu. Powinna być to nazwa pliku wykonywalnego ale bez ścieżki, arg1, ...argN – argumenty przekazywane do funkcji main tworzonego procesu. Funkcja zwraca: PID (typ pid_t lub integer) utworzonego procesu -1 gdy wystąpi błąd. Tryby wykonania: P_WAIT - proces czeka na zakończenie procesu potomnego P_NOWAIT - proces potomny wykonywany jest współbieżnie P_OVERLAY - proces bieżący zastępowany jest przez proces potomny
for(i=1;i <= atoi(argv[1]);i++) { printf("Potomny krok: %d \n",i); int pid,i,res,status,test; test=1234; char buf[10]; res = spawnl(P_WAIT,"/mnt/tmp2/spawnl2/x86/o/spawnl2","spawnl2","10",NULL); //res = spawnl(P_OVERLAY,"/mnt/tmp2/spawnl2/x86/o/spawnl2","spawnl2","10",NULL); // res = spawnl(P_NOWAIT,"/mnt/tmp2/spawnl2/x86/o/spawnl2","spawnl2","10",NULL); if(res < 0) { perror("SPAWN"); exit(0); } for(i=1;i < 10;i++) { printf("Macierzysty - krok %d \n",i); sleep(1); pid = wait(status); printf("Proces %d zakonczony, status %d\n",pid,status); Potomny krok: 1 Potomny krok: 2 Potomny krok: 3 Potomny krok: 4 Potomny krok: 5 Potomny krok: 6 Potomny krok: 7 Potomny krok: 8 Potomny krok: 9 Potomny krok: 10 Potomny test: 0 Macierzysty - krok 1 Macierzysty - krok 2 Macierzysty - krok 3 Macierzysty - krok 4 Macierzysty - krok 5 Macierzysty - krok 6 Macierzysty - krok 7 Macierzysty - krok 8 Macierzysty - krok 9 Proces -1 zakonczony, status 0 int id, i,status,test ; for(i=1;i <= atoi(argv[1]);i++) { printf("Potomny krok: %d \n",i); sleep(1); } printf("Potomny test: %d \n",test); exit(i);
Priorytet procesu Jednym z atrybutów procesu jest jego priorytet. Priorytet można uzyskać za pomocą funkcji: getprio() lub sched_getparam() a ustawić funkcjami: setprio() lub sched_setparam() Składnia funkcji getprio i setprio: int getprio(pid_t pid) – pobranie priorytetu procesu, gdy argument pid=0 funkcja zwraca priorytet procesu macierzystego int setprio(pid_t pid, int prio) – ustawienie priorytetu procesu - funkcja zwraca poprzedni priorytet lub -1 gdy wystąpi błąd.
printf("Prio macierzysty:%u\n",getprio(0)); setprio(0,37); printf("Prio macierzysty:%u\n",getprio(0)); spawnl(P_NOWAIT,"/mnt/tmp/spawnl2/x86/o/spawnl2","spawnl2","10",NULL); for(i=1;i <= 10;i++) { printf("Macierzysty - krok %d \n",i); sleep(1); } współbieżne setprio(getpid(),3); printf("Prio potomny:%u\n",getprio(getpid())); for(i=1;i <= atoi(argv[1]);i++) { printf("Potomny krok i=: %d \n",i); sleep(1); } Prio macierzysty:37 Macierzysty - krok 1 Prio pot:3 Potomny krok i=: 1 Macierzysty - krok 2 Potomny krok i=: 2 Macierzysty - krok 3 Potomny krok i=: 3 Macierzysty - krok 4 Potomny krok i=: 4 Prio macierzysty:3 Macierzysty - krok 1 Prio pot:38 Potomny krok i=: 1 Potomny krok i=: 2 Macierzysty - krok 2 Potomny krok i=: 3 Macierzysty - krok 3 Potomny krok i=: 4 Macierzysty - krok 4