Ethernet i AVR–y, cz.4.pdf

(988 KB) Pobierz
108-111_enut_cz4.indd
KURS
Ethernet i AVR–y
Ethernut od podstaw, część 4
Oprócz stosu TCP/IP NutOS wyposażono również
w podstawowe możliwości systemu operacyjnego
czasu rzeczywistego (RTOS). Najważniejszą z nich
jest wielowątkowość, czyli równoległe wykonywanie
kilku fragmentów programu. Jest ona bardzo
przydatna przy tworzeniu aplikacji sieciowych,
dlatego czwarty odcinek kursu będzie poświęcony
obsłudze wątków w systemie Ethernut.
Większość przeglądarek interne-
towych inicjuje jednocześnie kilka
połączeń z serwerem. Możliwe jest
wówczas równoległe pobieranie np.
tekstu (kod HTML) oraz grafiki (pli-
ki GIF, JPG), dzięki czemu strony
WWW otwierają się szybciej. Opisa-
ny w poprzedniej części kursu ser-
wer WWW mógł obsługiwać w da-
nej chwili tylko jedno połączenie.
Wskutek tego, przeglądarka czasami
nie wyświetlała obrazka ze strony
testowej (logo EP), nie było rów-
nież możliwe jednoczesne otwarcie
strony przez kilku klientów. Nasz
serwer powinien więc obsługiwać
więcej niż jedno połączenie na raz.
Jednym z rozwiązań
tego problemu jest wyko-
rzystanie wielowątkowości ( multith-
reading od thread – wątek). Pozwa-
la ona na równoległe wykonywanie
określonych fragmentów programu,
zwanych wątkami. Wielowątkowość
można traktować w wielkim uprosz-
czeniu jako prymitywną formę wie-
lozadaniowości ( multitasking ), czyli
mechanizmu, dzięki któremu sys-
temy operacyjne mogą wykonywać
jednocześnie więcej niż jeden pro-
gram (zwany w terminologii infor-
matycznej procesem ). Zasadniczą
różnicą między procesami a wąt-
kami jest współdzielenie danych
globalnych przez wątki (różne wąt-
ki mogą korzystać ze wspólnych
zmiennych globalnych). Zmienne
lokalne (czyli stos) są oddzielne
dla każdego wątku/procesu.
Prosty przykład
Kod prostego programu pokazu-
jącego działanie wątków ( Simple-
Threads ) znajduje się na list. 6 .
Efektem jego działania jest wysyła-
nie przez port szeregowy tekstów:
Tu watek nr 1 (co 0,5 sekun-
dy) i Tu watek nr 2 (co 1 se-
kundę). Świadczy to o tym, że wąt-
ki watek1 i watek2 pracują rów-
nolegle, ale nie oznacza wcale, że
działają one w tym samym czasie.
Wykonywanie więcej niż jednego
fragmentu kodu w tej samej chwi-
li jest możliwe tylko w systemach
wieloprocesorowych lub z procesora-
mi wielordzeniowymi (np. kompu-
tery PC z Intel CoreDuo mogą wy-
konywać w tym samym czasie dwa
procesy lub wątki).
W praktyce „jednoczesne” wyko-
nywanie się wątków jest realizowa-
ne przez szybkie przełączanie się
procesora między nimi. Przełączanie
wątków polega na:
– przerwaniu obecnie pracujące-
go wątku i zapisaniu jego stanu
w strukturze nazywanej w języ-
ku angielskim switchframe . Każ-
dy wątek ma własną strukturę
switchframe , zawiera ona wszyst-
kie parametry procesora (zawar-
tości rejestrów, licznik rozkazów,
wskaźnik stosu, wartości flag)
List. 6. Prosty przykład ilustrujący działanie wątków
THREAD(watek1, args)
{
int licznik = 0;
for(;;)
{
printf(„Tu watek nr 1 (licznik = %d)!\n”, licznik++);
NutSleep(500);// czekamy ok. pó? sekundy
}
}
THREAD(watek2, args)
{
int licznik = 0;
for(;;)
{
printf(„Tu watek nr 2 (licznik = %d)!\n”, licznik++);
NutSleep(1000);// czekamy ok. 1 sekundy
}
}
int main(void)
{
initialize();
int i;
printf(„Kurs EP Ethernut – czesc 4 – prosty przyklad uzywania watkow\n\r\
n\r”);
// tworzymy 2 watki – „Watek1” i „Watek2”
NutThreadCreate(„Watek1”, watek1, NULL, NUT_THREAD_MAINSTACK);
NutThreadCreate(„Watek2”, watek2, NULL, NUT_THREAD_MAINSTACK);
// watek „Main” idzie spac
for(;;)NutSleep(10000);
}
108
Elektronika Praktyczna 3/2007
682288728.117.png 682288728.128.png
KURS
jednoznacznie określające jego
stan.
– załadowaniu do rejestrów pro-
cesora wartości ze struktury
switchframe wątku, na który się
przełączamy.
Można tu zauważyć podobień-
stwo do obsługi przerwań – gdy
wystąpi przerwanie, stan procesora
jest zapisywany na stosie, a po za-
kończeniu jego obsługi do rejestrów
ładowane są uprzednio zapisane
wartości.
List. 7. Przykład – animacja tekstu wyświetlanego na LCD zestawu ZL9AVR
// tekst dla wyswietlacza LCD
static char lcd_text[128];
THREAD (lcd_scrolling, args) // wątek zajmujący się animacją tekstu na wy-
świetlaczu LCD.
{
int len, x=0, dx = 1, i;
printf(„Poczatek watku lcd_scrolling!\n\r”);
for(;;)
{
len = strlen(lcd_text);
if(len<16) // tekst krotszy niz 16 znakow? dopelniamy do 16 spacjami
{
for(i=len;i<16;i++) lcd_text[i] = ‚ ‚;
lcd_text[16] = 0;
}
if(x>len – 16) { x = 0; dx = 1; }
LCD_setpos(0);
LCD_putstringn(lcd_text + x, 16); // 16 – liczba znaków do wyświetlenia na
LCD
//przy koncu i poczatku tekstu czekamy troche dluzej
if(x==0 || x==(len–16)) NutSleep(2000);
x+=dx; // przesuwanie tekstu
if(x==(len–16) || x==0) dx = –dx;
Realizacja wielowątkowości
w systemie Nut/OS
Za przełączanie wątków w sys-
temie Nut/OS odpowiada funkcja
NutThreadSwitch() . Jej kod (dla
architektury AVR) wraz z odpo-
wiednią strukturą switchframe moż-
na znaleźć w pliku arch/avr/os/
context_gcc.c . Funkcja ta nie
jest wykorzystywana bezpośrednio
w aplikacjach, ale warto poznać za-
sadę jej działania.
Podział czasu procesora po-
między poszczególnymi wątkami
w programie z list. 6 przedstawio-
no na rys. 4 . Oprócz utworzonych
przez nas wątków watek1 i wa-
tek2 działają także zainicjowane
przez system wątki main (funkcja
main() ) oraz idle (wątek „jało-
wy” pracujący, gdy pozostałe wątki
są w stanie oczekiwania), a jeżeli
korzystamy z funkcji sieciowych –
jeszcze kilka wątków obsługujących
stos TCP/IP.
O kolejności wykonywania po-
szczególnych wątków przez pro-
cesor decyduje element systemu
operacyjnego nazywany planistą
( scheduler ). Za chwilę pokażę, jak
działa algorytm planisty w systemie
Nut/OS. Ale najpierw trochę defi-
nicji: po pierwsze, każdy z wątków
w Ethernucie może znajdować się
w jednym z niżej wymienionych
stanów:
TDS_RUNNING – obecnie pracu-
jący wątek,
TDS_SLEEP – wątek oczekujący
na wystąpienie zdarzenia, za-
kończenie operacji wejścia–wyj-
ścia lub uśpiony na pewien
czas przez funkcję NutSleep() .
Po zakończeniu okresu uśpienia/
oczekiwania wątek przechodzi
w stan TDS_READY,
TDS_READY – wątek gotowy do
wznowienia pracy.
Po drugie, każdy wątek ma
przypisany priorytet. Jest to liczba
NutSleep(300); // czekamy okolo 300 milisekund
}
}
int CGI_callback(FILE *f, REQUEST *r) // obsługa skryptu CGI – patrz EP
2/2007
{
(....)
if(!strcmp(name,”text”))
{
// parametr text – nowy tekst do wyswietlenia na LCD–ku, kopiujemy go do
zmiennej lcd_text
strcpy(lcd_text, value);
}
(....)
return 0;
}
// watek serwera HTTP – obsluga sterowania aplikacja przez przegladarke WWW
THREAD(http_server, args)
{
TCPSOCKET *s;
FILE *f;
printf(„Poczatek watku http_server!\n\r”);
for (;;) // pętla serwera HTTP – patrz EP 1, 2/2007.
{
(...)
}
}
int main(void)
{
(...) //inicjalizacja
// tworzymy watek serwera WWW
NutThreadCreate(„httpd”, http_server, NULL, NUT_THREAD_MAINSTACK);
// tworzymy watek obslugujacy wyswietlacz LCD
NutThreadCreate(„lcd”, lcd_scrolling, NULL, NUT_THREAD_MAINSTACK);
// ustawiamy najnizszy priorytet watku main
NutThreadSetPriority(254);
// watek main idzie spac...
for(;;) NutSleep(10000);
return 0;
}
z zakresu od 0 do 254, przy czym
wbrew logice: 0 oznacza najwyż-
szy, a 254 – najniższy priorytet.
Priorytet obecnie pracującego wątku
można ustawić za pomocą funkcji
NutThreadSetPriority() . Naj-
wyższe priorytety (od 0 do 31) są
zarezerwowane dla wątków systemu
operacyjnego i nie powinno się ich
ustawiać. Nowo utworzone wątki
mają priorytet równy 64.
Mechanizm działania planisty
w systemie Nut/OS jest następujący:
– jeśli pracujący w danej chwi-
li wątek zwolni procesor, sys-
tem operacyjny przełączy go na
Elektronika Praktyczna 3/2007
109
682288728.139.png 682288728.150.png
KURS
nego z wątków
( list. 6 ). Wów-
czas pracować
będzie tylko ten
wątek, z które-
go usunęliśmy
NutSleep() .
Taka im-
plementacja
wielowątkowo-
ści jest nazywana kooperatywną
( cooperative multithreading ), poza
systemem Nut/OS można ją spo-
tkać w starych Windowsach (wer-
sja 3.x) oraz MacOS do wersji 9.
W nowszych systemach stosuje się
wielowątkowość (wielozadaniowość)
z wywłaszczaniem ( preemptive mul-
titasking ) – wówczas system ope-
racyjny może przerwać aktualnie
pracujący wątek (proces) w dowol-
nej chwili, aby umożliwić pra-
cę innemu. Dzięki temu, system
może działać stabilniej (zawiesze-
nie się jednego procesu nie powo-
duje „zwisu” całego systemu), ale
ceną za to jest mniejsza wydaj-
ność, bardziej skomplikowane jądro
systemu oraz konieczność częstego
stosowania skomplikowanych me-
chanizmów synchronizacji wątków
(muteksy, sekcje krytyczne).
jak w aplikacjach na komputery
PC. Należy przy tym pamiętać,
że mamy do dyspozycji niecałe
32 kB pamięci RAM. Ilość pamię-
ci dostępnej w danej chwili zwraca
funkcja NutHeapAvailable.
Rys. 4. Podział czasu procesora między poszczególnymi
wątkami w programie z list. 6
Wątki w praktyce
Wątki są szczególnie przydatne,
gdy budujemy urządzenie, które ma
wykonywać pewną cykliczną czyn-
ność (np. monitorowanie wejść)
i jednocześnie mieć możliwość ob-
sługi przez sieć Ethernet. Ilustruje
to przykład z list. 7 ( LCD_scroll ).
Jest to program, który wyświetla na
wyświetlaczu LCD zestawu ZL9AVR
animowany tekst, o treści ustawia-
nej za pomocą przeglądarki inter-
netowej. Program tworzy dwa wąt-
ki (patrz rys. 5 ): lcd_scrolling
zajmujący się animacją napisu oraz
http_server – serwer WWW (taki
sam jak w poprzednim odcinku kur-
su) obsługujący stronę umożliwiają-
cą zmianę wyświetlanego tekstu.
wątek znajdujący się w stanie
TDS_READY o najwyższym prio-
rytecie,
– jeśli żaden z wątków nie jest
gotowy (stan TDS_SLEEP ), sys-
tem przełączy się na wątek
idle o najniższym prioryte-
cie.
Zastosowanie takiego algorytmu
ma jedną bardzo poważną konse-
kwencję – przełączenie wątków
jest możliwe tylko gdy obecnie
wykonujący się wątek dobrowolnie
zwolni procesor. Możemy to zro-
bić przez wywołanie jednej z na-
stępujących funkcji (dokładny opis
w ramce):
NutSleep(),
NutThreadYield(),
NutEventWait(),
NutTcpConnect(), NutTc-
pAccept(), NutTcpRece-
ive(), NutTcpSend() lub
innej blokującej operacji wej-
ścia–wyjścia na gnieździe sie-
ciowym.
Jeżeli o tym zapomnimy, do-
prowadzimy do zawieszenia się
programu – aby się o tym prze-
konać, możemy usunąć wywołanie
funkcji NutSleep() w kodzie jed-
Zarządzanie pamięcią w Nut/
OS
Interfejs programistyczny (API)
systemu Nut/OS udostępnia stan-
dardowe funkcje języka C umoż-
liwiające dynamiczną alokację pa-
mięci – malloc oraz free . Moż-
na je używać w taki sam sposób
Rys. 5. Wykorzystanie wątków w pro-
gramie z list. 7
Ważniejsze funkcje używane w przykładach
void NutThreadYield(void);
Wywołanie tej funkcji powoduje natychmiastowe przełączenie bieżącego
wątku zgodnie z algorytmem planisty systemu Nut/OS.
THREAD(nazwa_funkcji, nazwa_parametru)
Makro generuje deklarację funkcji (wraz z odpowiednimi atrybutami dla
kompilatora) mającej pracować jako wątek. Na przykład wywołanie:
THREAD(moj_watek, argumenty) { kod funkcji }
utworzy funkcję o postaci:
// atrybut noreturn jest podpowiedzia dla kompilatora C, ze funkcja nigdy
sie nie konczy
void moj_watek(void *argumenty) __attribute__ ((noreturn));
void moj_watek(void *argumenty) { kod funkcji }
void NutSleep(u_long ms);
Przerywa działający wątek na czas co najmniej ms milisekund i przełącza
go zgodnie z algorytmem planisty. Rzeczywisty czas, na jaki wątek
zostanie uśpiony zależy od rozdzielczości timera systemowego (domyślnie
62,5 milisekundy). Funkcji NutSleep nie wolno używać do odmierza-
nia precyzyjnych opóźnień!
void NutDelay(u_long ms);
Czeka dokładnie ms milisekund. Bieżący wątek nie jest przełączany.
Funkcję można stosować do precyzyjnych opóźnień.
HANDLE NutThreadCreate(char *name, void (*fn)
(void *), void *arg, size_t stackSize);
Tworzy nowy wątek o nazwie name. Wątek zaczyna swoją pracę od
wywołania funkcji fn z parametrem arg . Przez arg można przekazać
funkcji fn dowolny wskaźnik jako argument (lub podać NULL, jeżeli
go nie potrzebujemy). Wartość stackSize określa rozmiar stosu dla
tworzonego wątku. Domyślna wartość to 768 bajtów (makrodefinicja
NUT_THREAD_MAINSTACK ). Nowo utworzony wątek ma priorytet 64.
void *malloc(size_t), free(void *);
Funkcje alokujące i zwalniające pamięć zgodne ze standardową biblioteką
języka C.
void *NutHeapAlloc(size_t), NutHeapFree(void
*);
Ethernutowe odpowiedniki funkcji malloc() i free() . Można uży-
wać zamiennie z malloc() i free().
u_char NutThreadSetPriority(u_char level);
Ustawia priorytet obecnie pracującego wątku na wartość level . Należy
pamiętać o tym, że wątki tworzone przez użytkownika mogą mięć priory-
tety z przedziału 32…254, przy czym mniejsza wartość oznacza większy
priorytet. Funkcja zwraca wartość priorytetu wątku przed zmianą.
size_t NutHeapAvailable();
Zwraca ilość dostępnej pamięci sterty w bajtach.
110
Elektronika Praktyczna 3/2007
682288728.001.png 682288728.012.png 682288728.023.png 682288728.034.png
KURS
Wielowątkowy serwer WWW
Po stosunkowo długim wykła-
dzie na temat wątków, pokażę
wspomniany we wstępie ulepszo-
ny serwer WWW ze styczniowego
odcinka kursu ( HTTPServerMT ),
umożliwiający monitorowanie sta-
nu przycisków oraz sterowanie wy-
świetlaczem i diodami LED w ze-
stawie ZL9AVR. Istotne fragmenty
jego kodu przedstawiono na list. 8.
Program tworzy wątek obsługujący
animację napisu na LCD (taki jak
w poprzednim przykładzie) oraz pięć
identycznych wątków, rozpoczyna-
jących swoją pracę od wywołania
funkcji httpd_thread . Każdy
z nich wykonuje opisany w poprzed-
nich odcinkach kursu kod serwera
WWW. Jedyną różnicą jest spraw-
dzanie ilości wolnej pamięci przed
wywołaniem funkcji NutHttpPro-
cessRequest – funkcja ta wymaga
co najmniej 8 kB wolnego RAM–u.
Dzięki równoległej pracy kilku wąt-
ków serwera możliwa jest jednocze-
sna obsługa wielu połączeń.
List. 8. Najistotniejsze fragmenty kodu wielowątkowego serwera WWW
// watek serwera HTTP. Uruchamiamy 5 takich na raz ;)
THREAD(httpd_thread, arg)
{
for (;;) // petla serwera WWW
{
(...)// oczekiwanie na polaczenie (NutTcpAccept)
f = _fdopen((int) s, „r+b”);
// Sprawdzamy, czy mamy dostatecznie duzo dostepnej pamieci aby obsluzyc za-
pytanie HTTP – wymagane jest
// co najmniej 8 kB. Jesli nie – czekamy az pozostale watki zwolnia troche
pamieci
while(NutHeapAvailable() < 8192)
{
printf(„Za malo pamieci... czekam az ktos inny ja zwolni :(\n”);
NutSleep(1000);
}
(...) // obsluga zapytania HTTP
}
}
int main(void)
{
char str[10];
int i;
(...) // inicjalizacja
// tworzymy watek obslugujacy animowany napis na LCD
NutThreadCreate(„lcds”, lcd_scrolling, NULL, 256);
// tworzymy 5 watkow serwera WWW.
for(i=0;i<5;i++)
{
sprintf(str,”httpd%d”,i);
NutThreadCreate(str, httpd_thread, NULL, NUT_THREAD_MAINSTACK);
}
NutThreadSetPriority(254);// zmniejszamy priorytet watku main
for(;;) NutSleep(10000); // i idziemy spac
Co dalej?
Niestety ramy tego artykułu nie
pozwalają na wyjaśnienie wszyst-
kich zagadnień związanych z wie-
lowątkowością i wielozadaniowością.
Więcej informacji na temat realizacji
wielowątkowości w Nut/OS można
znaleźć np. na stronie http://www.
ethernut.de/pdf/entet100.pdf . Zainte-
resowanym polecam także lekturę
książki A. Silberschatza „Podstawy
systemów operacyjnych”.
return 0;
}
Za miesiąc wracamy do zagad-
nień stricte sieciowych, czyli Ether-
nut jako klient protokołu TCP/IP.
Tomasz Włostowski, EP
tomasz.wlostowski@ep.com.pl
Kody źródłowe przykładowych programów
znajdują się na płycie CD–EP oraz na stronach
http://ethernut.ep.com.pl oraz
http://wlostowski.ep.com.pl .
arm.ep.com.pl
arm.ep.com.pl
arm.ep.com.pl arm.ep.com.pl
arm.ep.com.pl
Elektronika Praktyczna 3/2007
111
682288728.045.png 682288728.056.png 682288728.067.png 682288728.074.png 682288728.075.png 682288728.076.png 682288728.077.png 682288728.078.png 682288728.079.png 682288728.080.png 682288728.081.png 682288728.082.png 682288728.083.png 682288728.084.png 682288728.085.png 682288728.086.png 682288728.087.png 682288728.088.png 682288728.089.png 682288728.090.png 682288728.091.png 682288728.092.png 682288728.093.png 682288728.094.png 682288728.095.png 682288728.096.png 682288728.097.png 682288728.098.png 682288728.099.png 682288728.100.png 682288728.101.png 682288728.102.png 682288728.103.png 682288728.104.png 682288728.105.png 682288728.106.png 682288728.107.png 682288728.108.png 682288728.109.png 682288728.110.png 682288728.111.png 682288728.112.png 682288728.113.png 682288728.114.png 682288728.115.png 682288728.116.png 682288728.118.png 682288728.119.png 682288728.120.png 682288728.121.png 682288728.122.png 682288728.123.png 682288728.124.png 682288728.125.png 682288728.126.png 682288728.127.png 682288728.129.png 682288728.130.png 682288728.131.png 682288728.132.png 682288728.133.png 682288728.134.png 682288728.135.png 682288728.136.png 682288728.137.png 682288728.138.png 682288728.140.png 682288728.141.png 682288728.142.png 682288728.143.png 682288728.144.png 682288728.145.png 682288728.146.png 682288728.147.png 682288728.148.png 682288728.149.png 682288728.151.png 682288728.152.png 682288728.153.png 682288728.154.png 682288728.155.png 682288728.156.png 682288728.157.png 682288728.158.png 682288728.159.png 682288728.160.png 682288728.002.png 682288728.003.png 682288728.004.png 682288728.005.png 682288728.006.png 682288728.007.png 682288728.008.png 682288728.009.png 682288728.010.png 682288728.011.png 682288728.013.png 682288728.014.png 682288728.015.png 682288728.016.png 682288728.017.png 682288728.018.png 682288728.019.png 682288728.020.png 682288728.021.png 682288728.022.png 682288728.024.png 682288728.025.png 682288728.026.png 682288728.027.png 682288728.028.png 682288728.029.png 682288728.030.png 682288728.031.png 682288728.032.png 682288728.033.png 682288728.035.png 682288728.036.png 682288728.037.png 682288728.038.png 682288728.039.png 682288728.040.png 682288728.041.png 682288728.042.png 682288728.043.png 682288728.044.png 682288728.046.png 682288728.047.png 682288728.048.png 682288728.049.png 682288728.050.png 682288728.051.png 682288728.052.png 682288728.053.png 682288728.054.png 682288728.055.png 682288728.057.png 682288728.058.png 682288728.059.png 682288728.060.png 682288728.061.png 682288728.062.png 682288728.063.png 682288728.064.png 682288728.065.png 682288728.066.png 682288728.068.png 682288728.069.png 682288728.070.png 682288728.071.png 682288728.072.png 682288728.073.png
Zgłoś jeśli naruszono regulamin