win 00


Windows 2000 funkcje API - komunikaty, semafory i wątki.

API to nic innego jak skrót od angielskich słów Application Programming Interface, czyli Interfejs Programowania Aplikacji. Funkcjom tym podporządkowane są właściwie wszystkie zdarzenia zachodzące w 32-bitowych aplikacjach.

Application Programming Interface zawiera setki funkcji umożliwiających kontrolę, sterowanie wszystkimi zasobami, zaczynając od sterowania myszką, poprzez kontrolowanie procesów Windows, a kończąc na zarządzaniu pamięcią. API posiada ogromny potencjał możliwości został udostępniony przez Microsoft wszystkim programistom, tworzącym dla Windows , a w szczególności dla programistów w C/C++, a także w Basic'u, ObjectPascal'u, a nawet asemblerze. Jest to możliwe dzięki wprowadzeniu standardowego mechanizmu wywoływania funkcji STDCALL, będącego PASCAL'owską konwencją wywołań.

Jako, że nic w systemie Windows nie dzieje się bez użycia funkcji API, są one wykorzystywane do przesyłania i odbierania komunikatów, oraz dają dostęp do mechanizmów programowania współbieżnego, oferując operacje na semaforach i tworzenie wątków.

SYSTEM KOMUNIKATÓW WINDOWS

Mechanizm komunikatów to system wymiany informacji pomiędzy procesami w systemie Windows, stanowią one siłę napędową tego systemu i jest ich całe mnóstwo. Windows wysyła komunikat do okna, aby nakazać mu wykonanie jakiejś czynności lub poinformować je o zaistnieniu określonego zdarzenia. Przykładowo, kiedy okno wymaga odświeżenia Windows wysyła mu komunikat wm_paint; podczas zmiany rozmiaru okna jest do niego wysyłany komunikat wm_windowposchanging informujący, że rozmiar lub/i pozycja okna zmieniły się; kiedy rozmiar okna zostanie ostatecznie ustalony wysyłane są dwa kolejne komunikaty: wm_windowposchanged i wm_size. To tylko przykłady - w środowisku Windows w ciągu jednej sekundy wysyłanych jest przeciętnie dziesiątki lub nawet setki komunikatów. Istnieje ponad sto różnych komunikatów, jakie Windows jest w stanie wysyłać do swych aplikacji. U podłoża wielu zdarzeń, na które reaguje program pisany w środowisku Delphi, leżą właśnie komunikaty Windows. Przykładowo zdarzenie OnCreate generowane jest w odpowiedzi na komunikat WM_CREATE, zdarzenie OnSize jest odpowiedzią na komunikat WM_SIZE, a zdarzenie OnMouseDown odpowiada komunikatom WM_LBUTTONDOWN i WM_RBUTTONDO

PRZYKŁADY KOMUNIKATÓW

WM_PAINT - gdy okno wymaga odświeżenia (przerysowania)

W najbardziej typowym przypadku przetwarzanie komunikatu wygląda następująco:

case WM_PAINT:

PAINTSTRUCT ps;

HDC hdc;

hdc = BeginPaint (hwnd, &ps);

[wywołania funkcji GDI]

EndPaint (hwnd, &ps);

Return 0;

PAINTSTRUCT - struktura informacji o malowaniu, zakładana przez Windows dla każdego

okna.

typedef struct tagPAINTSTRUCT

{

HDC hdc; //uchwyt kontekstu urządzenia

BOOL fErase; //True oznacza że Windows wymazał tło unieważnionego

//prostokąta przy pomocy pędzla określonego w polu

//hbrBackground klasy okna.

RECT rcPaint; // Wyznacza granicę unieważnionego prostokąta. Wymiary

//wyrażone są w pikselach względem lewego górnego rogu

//obszaru roboczego.

BOOL fRestore; //

BYTE rgbReserved;

}

PAINTSTRUCT;

WM_WINDOWPOSCHANGING - komunikat jest wysyłany do okna którego rozmiar, pozycja lub położenie Z uległo zmianie.

typedef struct _WINDOWPOS {

HWND hwnd; // uchwyt okna

HWND hwndInsertAfter; // pozycja na osi Z, może to być uchwyt do okna

// znajdującego się przed oknem do którego wysłano

// komunikat, lub jedna z wartości parametru

// hWndInsertAfter funkcji SetWindowPos.

int x; // pozycja lewej krawędzi okna

int y; // pozycja górnej krawędzi okna

int cx; // szerokość okna

int cy; // wysokość okna

UINT flags; // dodatkowe flagi określające stan okna

} WINDOWPOS;

WM_SIZE - kiedy rozmiar okna zostanie ustalony

wParam - może przyjąć wartości:

SIZE_MAXIMIZED - okno zostało zmaksymalizowane

SIZE_MINIMIZED - okno zostało zminimalizowane

SIZE_RESTORED - rozmiar okna uległ zmianie ( nie doszło ani do maksymalizacji ani do

minimalizacji)

LOWORD(lParam) - szerokość obszaru roboczego okna

HIWORD(lParam) - wysokość obszaru roboczego okna

WM_MOVE - komunikat wysyłany do okna którego położenie uległo zmianie.

LOWORD(lParam) - pozycja lewej krawędzi okna

HIWORD(lParam) - pozycja górnej krawędzi okna

WM_CREATE - to pierwszy komunikat który trafia do procedury okna. Generuje go Windows

przy okazji wywołania funkcji CreateWindows.

WM_CLOSE - komunikat ten oznacza, że okno powinno zostać zamknięte. Jeśli procedura

okna przekaże ten komunikat do DefWindowProc to funkcja ta odpowie na ten komunikat

wywołując funkcję DestroyWindow, która sprawi, że Windows wyśle do okna komunikat

WM_DESTROY. Jeżeli sami obsłużymy ten komunikat to możemy powstrzymać Windows

przed zamknięciem okna.

WM_OVERLAPPED - w przypadku okien nakładających się

{}WM_KEYDOWN z parametrem VK_H - w przypadku naciśnięcia klawisza H w polu obiektu zawartego w {}

Typowy element śledzenia komunikatów wygląda następująco:

000684:00000854 {TMemo} WM_KEYDOWN Dispatched 48h 72d VK_H Scan 23h Down

Elementy tego przykładu są niezwykle zróżnicowane w zależności od typu komunikatu. Mniej więcej oznacza on, że komponent Memo otrzymał komunikat WM_KEYDOWN z parametrem VK_H. Inaczej: użytkownik nacisnął na klawiaturze klawisz h, podczas gdy kursor znajdował się we wnętrzu obiektu Memo.

Komunikaty klawiatury:

Komunikaty związane z klawiaturą dzielimy na dwie grupy:

Klawisz wciśnięty Klawisz zwolniony

Klawisz zwykły (bez [Alt]) WM_KEYDOWN WM_KEYUP

Klawisz systemowy

(klawisz naciśnięty razem z [Alt] WM_SYSKEYDOWN WM_SYSKEYUP

Do sprawdzenia w jakim stanie były klawisze specjalne w trakcie naciśnięcia dowolnego

klawisza służy funkcja:

SHORT GetKeyState(int nVirtKey)

która zwraca wartość ujemną jeżeli był naciśnięty klawisz [Shift], [Ctrl] lub [Alt], oraz

zwraca wartość 0 jeżeli nie są włączone: [Caps Lock], [Num Lock] oraz [Scroll Lock].

Komunikaty znakowe:

Do stwierdzenia jaki znak odpowiada naciśniętemu klawiszowi często potrzebna jest

znajomość językowej konfiguracji klawiatury. Dlatego należy wykorzystać Windows do

tłumaczenia komunikatów klawiszy na komunikaty znakowe. Robi to funkcja

TranslateMessage wywoływana w pętli komunikatów.

TranslateMessage umieszcza komunikat znakowy w kolejce komunikatów. Mamy cztery komunikaty znakowe odpowiadające czterem komunikatom klawiszy.

W większości przypadków program zajmuje się tylko jednym: WM_CHAR.

Wymiarów znaku dostarcza funkcja GetTextMetrics.

BOOL GetTextMetrics(

hdc //uchwyt kontekstu urządzenia

lptm // wskaźnik na strukturę TEXTMETRIC, którą funkcja wypełnia informacjami o

//czcionce wybranej w kontekście urządzenia.

);

Oto jeszcze kilka innych komunikatów służących do informowania o zdarzeniach klawiatury:

WM_CHAR

WM_DEADCHAR

WM_GETHOTKEY

WM_HOTKEY

WM_KILLFOCUS

WM_SETFOCUS

WM_SETHOTKEY

WM_SYSCHAR

WM_SYSDEADCHAR

Funkcje wysyłające komunikaty(mające te same parametry jak procedura okna):

Funkcja wysyłająca komunikat do kolejki komunikatów, funkcja powraca nie czekając na przetworzenie komunikatu:

BOOL PostMassage (

HWND hWnd - uchwyt okna docelowego

UINT Msg - identyfikator komunikatu

WPARAM wParam - pierwszy parametr komunikatu

LPARAM lParam - drugi parametr komunikatu

)

Funkcja wysyłająca komunikat do okna(z ominięciem kolejki), funkcja czeka na zakończenie powtarzania komunikatu:

LRESULT SendMessage (

HWND hWnd - uchwyt okna docelowego

UINT Msg - identyfikator komunikatu

WPARAM wParam - pierwszy parametr komunikatu

LPARAM lParam - drugi parametr komunikatu

)

Funkcja wysyła komunikat WM_QUIT, najczęściej w odpowiedzi na komunikat WM_DESTROY, argumentem jest kod zamknięcia okna.

VOID PostQuitMessage(

int nExitCode - kod zamknięcia

)

Funkcja umieszcza komunikat w kolejce komunikatów danego wątka i natychmiastowo kończy działanie.

BOOL PostThreadMessage (

DWORD idThread - uchwyt do wątka

UINT Msg - identyfikator komunikatu

WPARAM wParam - pierwszy parametr komunikatu

LPARAM lParam - drugi parametr komunikatu

)

W Windows mamy dużo czasu tzw. „czasu martwego” tzn. czasu kiedy kolejka komunikatów jest pusta.

Możemy w naszym programie przechwycić sterowanie w tym martwym czasie za pomocą funkcji PeekMessage. Funkcja ta zwraca TRUE jeżeli kolejka zawiera jakiś komunikat, FALSE w przeciwnym wypadku.

BOOL PeekMessaga(

LPMSG lpMsg -adres do struktury opisującej komunikat

HWND hWnd - uchwyt okna

UINT wMsgFilterMin - pierwszy komunikat

UINT wMsgFilterMax - ostatni komunikat

UINT wRemoveMsg - ustala czy komunikat będzie usuwany z kolejki podczas pobierania, czy tez nie.

)

Funkcje odbierające komunikaty:

Funkcja GetMessage pobiera z kolejki komunikat i umieszcza go w strukturze:

BOOL GetMessage (

LPMSG lpMsg - jest to adres do struktury Msg(należy ją gdzież zarezerwować)

HWND hWnd - uchwyt okna dla którego chcemy pobrać komunikat

UINT wMsgFilterMin - pierwszy komunikat

UINT wMsgFilterMax - ostatni komunikat

)

function GetMessage(var lpMsg :
TMsg; // wskaźnik do struktury z komunikatem
hWnd:HWND; // uchwyt okna
wMsgFilterMin, // pierwszy komunikat
wMsgFilterMax:UINT // ostatni komunikat
): BOOL; stdcall;
// rezultat informujący o istnieniu komunikatu w kolejce

Zwraca ona niezerową wartość dopóki, dopóty pobranym komunikatem nie będzie WM_QUIT. Może ona też zwrócić "-1" jeżeli uchwyt okna pod jakim skierowany jest komunikat, jest nieprawidłowy, lub okno takie nie istnieje (spowoduje to oczywiście błąd systemowy).

Funkcja GetMessage zwraca wartość dodatnią dopóki okno nie otrzyma komunikatu nakazującego zamknięcie, w wyniku czego do odbioru stosuje się pętlę komunikatów. Pętla ma za zadanie pobieranie komunikatów z kolejki, analizowanie i wysyłanie do procedury okna.

Oto przykładowa pętla komunikatów:

while ( GetMessage ( &msgMain, NULL, 0, 0) )

{

TranslateMessage (&msgMain);

DispatchMessage (&msgMain);

}

// TranslateMessage() dokonuje przez mapowanie komunikatów klawiszy

// DispatchMessage() przesyła komunikat do odpowiedniego okna

Poniżej przykładowy kod generowany przez Microsoft Visual C++:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
      {
     case WM_COMMAND: // obsługa
     break;
     case IDM_ABOUT: // komunikat użytkownika

//obsługa
     break;
     case IDM_EXIT: // komunikat użytkownika
      // obsługa
     break;
     case WM_PAINT: // odrysowanie okna
      // obsługa
     break;
     case WM_DESTROY: // żądanie usunięcia
     PostQuitMessage(0);
     break;
     } return 0;
}

SEMAFORY:

Synchronizowanie procesów :

Proces współpracujący, może wpływać na inne procesy w systemie lub podlegać ich oddziaływaniom. Procesy współpracujące mogą bezpośrednio dzielić logiczną przestrzeń adresową - ( tzn. zarówno kod, jak i dane) albo zezwala się im na dzielenie danych tylko za pośrednictwem plików. Pierwszą możliwość osiąga się za pomocą procesów lekkich, czyli wątków. Współbieżny dostęp do danych dzielonych może powodować ich niespójność. Każdy proces ma segment kodu zwany sekcją krytyczną, w którym może zmieniać swoje zmienne, aktualizować tablice, pisać do pliku itd. Ważną cechą tego systemu jest to, że gdy jeden proces wykonuje sekcję krytyczną, wówczas żaden inny proces nie jest dopuszczony do wykonywania swojej sekcji krytycznej. Zatem wykonanie sekcji krytycznej przez procesy podlega wzajemnemu wykluczaniu w czasie. Problem sekcji krytycznej polega na skonstruowaniu protokołu, który mógłby posłużyć do organizowania współpracy procesów. Każdy proces musi prosić o pozwolenie na wejście do swojej sekcji krytycznej. Fragment kodu realizującego taką prośbę nazywa się sekcją wyjściową. Po sekcji krytycznej może wystąpić sekcja wyjściowa. Pozostały kod nazywa się resztą.

Rozwiązanie problemu sekcji krytycznej musi spełniać następujące trzy warunki:

Zakłada się że proces wykonywany jest z prędkością niezerową. Natomiast nie musimy niczego zakładać o względnej szybkości każdego z n procesów.

W środowisku jednoprocesorowym problem sekcji krytycznej daje się łatwo rozwiązać, jeśli można zakazać występowania przerwań podczas podczas modyfikowania zmiennej dzielonej. W ten sposób uzyskuje się pewność, że dany ciąg rozkazów zostanie wykonany sekwencyjnie, bez wywłaszczenia. Ponieważ wykonanie żądanego innego rozkazu nie jest możliwe, więc nie nastąpi żadna nieoczekiwana zmiana wspólnej zmiennej.

Niestety, rozwiązanie to nie jest przydatne w środowisku wieloprocesorowym. Wyłączanie przerwań w wieloprocesorze może być czasochłonne, gdyż wymaga przekazywania komunikatów do wszystkich procesorów. Owo przekazywanie komunikatów opóźnia wejście do każdej sekcji krytycznej, co powoduje spadek wydajności systemu. Należy tez zwrócić uwagę na skutki wywoływane w zegarze systemowym, jeśli jest on uaktualniany za pomocą przerwań.

Z tych powodów w wielu maszynach są specjalne rozkazy sprzętowe pozwalające w sposób niepodzielny sprawdzić i zmienić zawartość słowa albo zmienić zawartości dwu słów. Takie specjalne rozkazy mogą stosunkowo łatwo posłużyć do rozwiązania problemu sekcji krytycznej.

Rozwiązanie problemu sekcji krytycznej w sposób sprzętowy nie jest łatwe do uogólnienia w bardziej złożonych zagadnieniach. Do omijania tej trudności służy narzędzie synchronizacji zwane semaforem

Semafor S jest zmienną całkowitą, która oprócz nadania wartości początkowej - jest dostępna tylko za pomocą dwóch standardowych, niepodzielnych operacji: czekaj i sygnalizuj. Klasyczne definicje operacji czekaj i sygnalizuj są następujące:

czekaj (S): while S < 0 do nic:

S:=S-1;

sygnalizuj(S):S:=S+ 1;

Zmiany wartości całkowitych semafora muszą być wykonywane za pomocą operacji, czekaj i sygnalizuj w sposób niepodzielny. Oznacza to że, gdy jeden proces modyfikuje wartość semafora, wówczas żaden inny proces nie

może jednocześnie wartości, tej zmieniać. Ponadto w przypadku operacji czekaj (S) nie może wystąpić przerwanie podczas sprawdzania wartości zmiennej całkowitej S (S <= 0) i jej ewentualnego zmieniania (S := S - 1).

Wspólny semafor mutex jest dzielony przez n procesów. Na początku mutex ma wartość 1. Każdy proces jest zorganizowany następująco:

repeat

czekaj(mutex);

sekcja krytyczna

sygnalizuj(mutex);

reszta

until false;

Konstrukcja semafora opisana wcześniej to semafor zliczający, ponie­waż jego wartości całkowite mogą przebiegać dowolny przedział. Semafor binarny to taki, którego wartość całkowita może wynosić tylko 0 lub 1. Zależnie od rodzaju sprzętu realizacja semafora bi­narnego może być prostsza niż semafora zliczającego. Poniżej zilustrowany został sposób za pomocą którego można z semaforów binarnych utworzyć semafor zliczający.

Niech S oznacza semafor zliczający. Aby zaimplementować go przy użyciu semaforów binarnych, będziemy potrzebować następujących struktur danych:

var S l: semafor-binarny;

S2: semafor-binarny;

C: integer;

Początkowo S 1 = l, S2 = 0, a wartość zmiennej całkowitej C jest określana według początkowej wartości semafora zliczającego S.

Operację czekaj semafora zliczającego S można zrealizować następująco:

czekaj(Sl);

C:=C- 1;

if C<0

then begin

sygnalizuj(S1);

czekaj(S2);

end

sygnalizuj(S1);

Operacja sygnalizuj dotycząca semafora zliczającego S może być wyko­nana następująco:

czekaj(Sl);

C:=C+1;

if C<=0

then sygnalizuj(S2);

else svgnalizuj(Sl);

Zastosowanie i tworzenie semaforów :

Semafory można wykorzystać do synchronizacji dostępu do zasobów, dla których istnieje pewien limit równocześnie odwołujących się wątków.

Funkcje związane z semaforami :

CreateSemaphore ( )

ReleaseSemaphore ( )

WaitForSingle0bject ( )

Do tworzenia nowego semafora służy funkcja API :

Function CreateSemaphore ( lpSemaphoreAttributes : PsecurityAttributes;

lInitialCount, lMaximumCount : Longint;

lpName : Pchar) : Thandle; stdcall;

Pierwszy parametr lpSemaphoreAttributes - (wskazanie na atrybuty bezpieczeństwa) - zawiera wskaźnik do rekordu opisującego dostęp do uchwytu semafora. Po za tym funkcja ta posiada dwa charakterystyczne dla siebie parametry : lInitialCount oraz lMaximumCount, które są związane z licznikiem semafora. Licznik semafora decyduje o dopuszczalnej liczbie wątków, synchronizowanych za pomocą semafora. Pierwszy z nich określa początkową zawartość licznika, natomiast drugi jego wartość maksymalną.

Obiekt semafora znajduje się w stanie sygnalnym wówczas, kiedy jego licznik ma wartość większą od 0. Wtedy także wywołanie funkcji oczekującej na stan sygnalny semafora WaitForSingle0bject ( ), zmniejszy jego licznik o 1i zakończy się sukcesem (zwróci WAIT_OBJECT_0). Będzie to możliwe tylko do momentu wyzerowania licznika semafora. Taka sytuacja zajdzie wówczas, kiedy skorzysta z niego równocześnie maksymalna, dopuszczalna liczba wątków.

Zwolnienie semafora przez wątek następuje w wyniku wywołania funkcji API o nazwie ReleaseSemaphore ( ). Daje ona możliwość zwiększenia jego licznika o wartość większą, niż 1, co w pewnych sytuacjach może być przydatne.

Ostatnim parametrem jest lpName - jest nazwa semafora (dowolny ciąg znaków).

Omówimy teraz kilka różnych problemów synchronizacji do rozwiązania których posłużą semafory.

Problem producenta i konsumenta polega na zsynchronizowaniu dwóch procesów: producenta, który cyklicznie produkuje porcje danych, a następnie przekazuje je do konsumpcji, i konsumenta, który cyklicznie te dane pobiera i konsumuje.

Producent musi być wstrzymany, jeśli nie może przekazać wyprodukowanych porcji, a konsument, jeśli nie może ich pobrać.

Porcje powinny być konsumowane w kolejności ich wyprodukowania (to nie zawsze jest wymagane).

Rozwiązania:

Synchronizacja przez N elementowy bufor (N od 1 do nieskończoności).

Efektywność rozwiązania zależy od wielkości bufora i zależności czasowych realizacji produkcji i konsumpcji.

Problem ten jest abstrakcją wielu rzeczywistych problemów pojawiających się w protokołach komunikacyjnych i systemach czasu rzeczywistego.

Polega na zsynchronizowaniu dwóch grup cyklicznych procesów konkurujących o dostęp do wspólnej czytelni.

Proces czytelnik odczytuje informacje zgromadzone w czytelni i może to robić razem z innymi czytelnikami. Proces pisarz cyklicznie zapisuje informacje i może to robić tylko sam przebywając w czytelni.

Czytanie i pisanie trwa skończenie długo.

Problem ten jest abstrakcją problemu synchronizacji procesów korzystających ze wspólnej bazy danych.

Rozwiązanie z możliwością zagłodzenia pisarzy:

Czytelnik może wejść do czytelni, gdy jest pusta lub są w niej inni czytelnicy.

Gdy w czytelni jest pisarz czytelnik jest wstrzymany.

Gdy czytelnia nie jest pusta, to pisarz jest wstrzymany.

Rozwiązanie z możliwością zagłodzenia czytelników:

Jeśli pisarz czeka na wejście do czytelni, to nie może już do niej wejść żaden nowy czytelnik. Rozwiązanie bez zagłodzenia: do czytelni wpuszczani są na przemian czytelnicy i pisarze. Z tym, że wraz z każdym czytelnikiem wchodzą wszyscy inni oczekujący czytelnicy.

Struktura procesu pisarza :

czekaj(S);

...

w tym momencie pisarz wykonuje operacje na obiekcie dzielonym.

...

sygnalizuj(S);

Struktura procesu czytelnika :

czekaj(WW);

liczba-czyt : =liczba-czyt + 1;

if liczba-czyt = 1 then czekaj (S);

sygnalizuj(WW);

...

tu następuje czytanie

...

czekaj(WW);

liczba-czyt : =liczba-czyt - 1;

if liczba-czyt = 0 then sygnalizuj (S);

sygnalizuj(WW);

Gdy pisarz przebywa w sekcji krytycznej to oczekuje na niego n czytelników, z czego 1 do semafora S, a n-1 ustawia się w kolejce do semafora WW.

Polega na zsynchronizowaniu działań pięciu filozofów, którzy siedzą przy okrągłym stole i cyklicznie myślą i jedzą, korzystając z talerza i dwóch widelców.

Przed każdym filozofem stoi talerz, a pomiędzy każdymi dwoma talerzami leży jeden widelec.

Zakłada się, że jeśli filozof podniósł oba widelce, to w skończonym czasie je odłoży.

Rozwiązanie gwarantuje, że każdy filozof, który poczuje się głodny, będzie mógł się w końcu najeść.

Ponadto wszyscy filozofowie powinni być traktowani jednakowo.

Jest to wzorcowy przykład obrazujący zjawiska zagłodzenia i blokady.

Rozwiązanie z możliwością blokady: głodny filozof czeka, aż będzie wolny lewy widelec, podnosi go i czeka na prawy i także go podnosi. Po zjedzeniu odkłada oba widelce.

Rozwiązanie z możliwością zagłodzenia: filozof czeka, aż oba widelce będą wolne i wtedy podnosi je jednocześnie.

Rozwiązanie poprawne: wykorzystany jest zewnętrzny arbiter (lokaj), który zapewnia, że jednocześnie nie więcej niż czterech (4) filozofów chciałoby jeść, to znaczy konkuruje o widelce. Jeśli pięciu (5) filozofów chciałoby jeść naraz, to lokaj powstrzyma jednego z nich.

0x01 graphic

Struktura i - tego filozofa :

repeat

czekaj (pałeczka[i]);

czekaj(pałeczka[i + 1 mod 5]);

...

jedzenie

...

sygnalizuj(pałeczka[i]);

sygnalizuj(pałeczka[i + 1 mod 5]);

...

myślenie

...

until false;

WĄTKI:

Pierwsze systemy komputerowe umożliwiały wykonywanie tylko jednego programu w danej chwili. Program taki miał nadzór nad całym systemem, a oprócz tego korzystał ze wszystkich zasobów systemu. Współczesne systemy komputerowe pozwalają na umieszczenie w pamięci operacyjnej wielu programów i współbieżne ich wykonywanie. Te zmiany pociągnęły za sobą konieczność ostrzejszej kontroli większego odseparowania od siebie poszczególnych programów. Spowodowało to powstanie pojęcia procesu.

Przez proces rozumie się program będący w trakcie wykonywania. Proces jest jednostką pracy w nowoczesnym systemie z podziałem czasu. Z tego wynika fakt, że system składa się więc ze zbioru procesów:

procesy systemu operacyjnego - wykonują kod systemowy.

procesy użytkowe - działają według kodu należącego do użytkowników.

Wszystkie te procesy potencjalnie mogą być wykonywane współbieżnie dzięki podziałowi między nie mocy obliczeniowej procesora (lub procesorów). Przełączając procesor do poszczególnych procesów, system operacyjny może zwiększyć wydajność komputera.

Opis procesu :

Proces jest wykonywanym programem, wykonanie procesu musi przebiegać w sposób sekwencyjny. Wynikiem tego jest to, że w dowolnej chwili na zamówienie danego procesu może być wykonywany co najwyżej jeden rozkaz kodu programu.

Proces jest czymś więcej niż samym kodem programu (nazywa się go sekcją tekstu (ang. text section)). W pojęciu procesu mieści się również bieżąca czynność reprezentowana przez wartość licznika rozkazów (ang. program counter) oraz zawartość rejestrów procesora.

Licznik rozkazów - wskazuje adres następnego rozkazu do wykonania w procesie.

Rejestry procesora - liczba i typy rejestrów zależą od architektury komputera, wyróżniamy : akumulatory, rejestry indeksowe, wskaźniki stosu, rejestry ogólnego przeznaczenia i rejestry warunków. Informacje o stanie tych rejestrów muszą być przechowywane w czasie przerwań po to by proces mógł później być poprawnie kontynuowany.

Do procesu na ogół należy także: stos procesu (ang. process .stack), który przechowuje dane tymczasowe (takie jak parametry procedur, adresy powrotne i zmienne tymczasowe) oraz sekcja danych (ang. data section) zawierająca zmienne globalne.

Program sam w sobie nie jest procesem. Program jest obiektem pasywnym. Natomiast proces jest obiektem aktywnym, z licznikiem rozkazów określającym następny rozkaz do wykonania i ze zbiorem przydzielonych mu zasobów.

Dwa procesy mogą być związane z jednym programem i tak będą one zawsze traktowane jako dwie oddzielne sekwencje wykonania. Przykładem może być : użytkownik może zapoczątkować pracę wielu kopii edytora. W każdym z tych przypadków mamy do czynienia z osobnymi procesami, które - niezależnie od równoważności sekcji tekstu - będą się różniły sekcjami danych, a ponad to każdy wykonywany proces może uruchomić wiele nowych procesów.

Stany procesu :

Każdy wykonywany proces jest w pewnym stanie, który może zmieniać w zależności od bieżącej czynności procesu. Mogą to być następujące stany procesów :

nowy - proces został utworzony.

aktywny - są wykonywane instrukcje.

oczekiwanie - proces czeka na wystąpienie jakiegoś zdarzenia (np. zakończenie operacji wejścia/wyjścia).

gotowy - proces czeka na przydział procesora.

zakończony - proces zakończył działanie.

Wątek, proces lekki, LWP (angielskie thread, lightweight process), podstawowa jednostka wykorzystania procesora, dynamiczna część procesu, w skład której wchodzą: licznik rozkazów, zbiór rejestrów i obszar stosu. Wątek dzieli z innymi równorzędnymi wątkami przestrzeń adresową procesu, a w szczególności jego kod i dane, otwarte pliki i sygnały.

Do wykonania wątku potrzebne są dwie rzeczy: czas systemowy i przestrzeń systemowa.

Każdy wątek potrzebuje do pracy odpowiedniej ilości czasu systemowego i musi mieć wolną przestrzeń, w której przechowa dane. A także wątki muszą być od siebie oddzielone.

W celu rozwiązania tych problemów połączono siły takich elementów Windows 2000 jak:

Menadżer plików, - który tworzy i niszczy wątki

Jądro - przydziela wątkom czas procesora

Menadżer pamięci wirtualnej - przydziela wątkom przestrzeń

Monitor odwołań do zabezpieczeń - nie pozwala wątkom na integrowanie nawzajem w swoje dane poprzez autoryzację każdego dostępu do pamięci.

Wątek współużytkuje wraz z innymi równorzędnymi wątkami sekcję kodu, sekcję danych oraz takie zasoby systemu operacyjnego, jak otwarte pliki i sygnały, co łącznie określa się jako zadanie. Proces tradycyjny, czyli ciężki, jest równoważny zadaniu z jednym wątkiem.

Zadanie nie robi nic, jeśli nie ma w nim ani jednego wątku, z kolei wątek może przebiegać w dokładnie jednym zadaniu.

Rodzaje wątków :

Wątek jądrowy - ma jedynie małą strukturę danych i stos. Przełączanie wątków jądrowych nie wymaga zmiany informacji dotyczących dostępu do pamięci, jest więc stosunkowo szybkie.

Proces lekki (LWP) - zawiera blok kontrolny procesu z danymi rejestrowymi, informacjami rozliczeniowymi i informacjami dotyczącymi pamięci. Przełączanie procesów lekkich wymaga więcej pracy i jest dość wolne.

Wątek poziomu użytkownika - wymaga tylko stosu i licznika rozkazów, nie są mu potrzebne żadne zasoby jądra. Jądro nie jest angażowane w planowanie wątków poziomu użytkownika, więc ich przełączanie jest szybkie. Mogą istnieć tysiące wątków poziomu użytkownika, a jedyne, co będzie widoczne dla jądra, to procesy lekkie w procesie realizującym wątki tego poziomu.

Wątki a wąskie gardła

Jeżeli konieczna jest dokładniejsza analiza aktywności procesora, należy badać poszczególne wątki procesu lub procesów. Chcąc dowiedzieć się, w jakim stopniu aktywność wątku wpływa na powstawanie problemu oraz czy jest on powodowany przez jeden czy kilka procesów, należy monitorować następujące parametry:

Liczbę wątków każdego z procesów, które działają podczas trwania wąskiego gardła.

Ilość czasu procesora wykorzystywanego przez poszczególne wątki.

Poziom priorytetów, z którymi działają poszczególne wątki

Ilość czasu, w którym wątki wykorzystują procesor w trybie uprzywilejowanym.

Po przeanalizowaniu aktywności wątków za pomocą liczników aktywności, można dostosować ich harmonogram w celu udostępnienia większej ilości czasu procesora procesom blokowanym przez wąskie gardło.

Oprócz modyfikowania priorytetu wątku nie można zmieniać jego zachowania nie zmieniając kodu źródłowego związanej z nim aplikacji. Mając dostęp do kodu źródłowego można tworzyć liczniki wydajności umożliwiające niskopoziomowe monitorowanie wątków danej aplikacji.

Wąskie gardła mogą powstawać w wyniku aktywności kilku wątków jednego procesu, pojedynczych wątków kilku procesów lub wielu wątków wielu procesów.

Wielowątkowość i wieloprocesowość

Windows 2000 umożliwia pracę wielu wątków.

Aby wspomóc wielowątkowość procesów, jądro Windowsa 2000 decyduje, który wątek będzie wykonany i przez jaki czas będzie on w posiadaniu CPU. Z drugiej strony wątek może być odsunięty w każdej chwili. Jest to decydująca właściwość systemu wielozadaniowego z wywłaszczeniem. System operacyjny określa, który proces będzie wykonany.

W Windowsie 2000 zdarza się czasami tak, że wątek powinien pracować tak długo, aż osiągnie miejsce, w którym się zatrzyma.

Dlatego wielowątkowość procesów zależy od tego, w jaki sposób je napisano i co muszą robić.

W kodzie mogą znaleźć się miejsca, w których wątki są blokowane na potrzeby krytycznych operacji lub, gdy wątek manipuluje globalnymi danymi, do których muszą mieć dostęp wszystkie inne wątki procesu.

Takie miejsca są nazywane „ obszarami kodu, które nie są bezpieczne dla wątków”. Jednak takich obszarów jest bardzo niewiele w Windowsie.

Razem z wielowątkowością wręcz automatycznie pojawia się wieloprocesowość. Np. Jeśli system posiada więcej niż jeden procesor, jądro Windowsa rozprowadzi wątki po wszystkich dostępnych procesorach Tak, więc ten system będzie kilkakrotnie szybszy od systemu z jednym CPU.

Ogólnie mówiąc nie ma wieloprocesorowych wersji programów, a podziałem na procesory zajmuje się system Windows.

Funkcje związane z wątkami :

HADLE CreateThread( )

DWORD ResumeThread( )

DWORD SuspendedThread( )

VOID Sleep( )

VOID ExitThread( )

DWORD WaitForSingle0bject ( ) - funkcja oczekująca na stan sygnalny

DWORD WaitForMultipleObjects ( ) - funkcja dzięki której wątek oczekuje na przejście wielu obiektów w stan sygnalny.

Function WaitForSignaleObject(hHandle : THandle;

DwMilliseconds : DWOR) : DWOR; stdcall;

Pierwszy parametr hHandle zawiera uchwyt do obiektu, na który czeka, następnie drugi określa długość czasu oczekiwania w milisekundach (wartość tego parametru INFINITE - nieskończoność).

Tworzenie wątku :

W systemie Windows 95/98/NT/2000 i XP w ramach procesu można utworzyć kilka wątków, wykonywanych współbieżnie, mających dostęp do wszelkich zasobów procesu. Każdy proces posiada co najmniej jeden wątek zwany wątkiem główny. Dodatkowo, można dla niego tworzyć wątki poboczne, a służy do tego funkcja API, o nazwie CreateThread( ). W Delphi do dyspozycji jest funkcja BeginThread( ), w której jest wywołanie wcześniej wspomnianej funkcji API.

Wzór odpowiedniej deklaracji :

function BeginThread ( SecurityAttributes : Pointer;

StackSize : LongWord;

ThreadFunc : TThreadFunc;

Parameter : Pointer;

CreationFlags : LongWord;

var ThreadId : LongWord) : Integer;

Wyjaśnienie kolejnych parametrów :

SecurityAttributes - (wskazanie na atrybuty bezpieczeństwa) - zawiera wskaźnik do rekordu opisującego dostęp do uchwytu wątku, może on zawierać wskaźnik pusty NULL(w delphi Nil).

StackSize - (rozmiar stosu w Bajtach) - określa wielkość stosu dla nowego wątku. Wpisanie wartości 0 spowoduje użycie wielkości domyślnej.

ThreadFunc - (wskazanie na funkcje będącą ciałem wątku) - parametr zawiera adres funkcji, w której zawarty jest kod tworzonego wątku.

Parameter - (atrybuty tworzonego wątku) - ten parametr zawiera wartość, która będzie użyta w wywołaniu funkcji wątku.

CreationFlags - (atrybuty tworzonego wątku) - określa sposób utworzenia wątku, może on przyjąć jedną z dwóch wartości :

CREATE_SUSPENDED - oznacza to że po utworzeniu wątku jego wykonanie zostanie zawieszone.

0 - wątek zostanie utworzony w zwykły sposób(tzn. będzie mógł działać).

ThreadId - parametr do którego będzie przypisane wskazanie na zmienną, do której zostanie zapisany identyfikator wątku, nadany mu w chwili utworzenia.

Po poprawnym utworzeniu wątku przez funkcję CreateThread( ), zwraca ona uchwyt wątku, natomiast w razie nie powodzenia zwraca ona wartość 0.

Użycie w argumencie CreationFlags wartości CREATE_SUSPENDED, powoduje że wątek zostaje utworzony ale pozostaje w stanie zawieszenia (tzn. procesor nie przełączy się na to zadanie i nie będzie go wykonywał). Obiekt, który reprezentuje wątek, jest wyposażony w tzw. licznik zawieszeń. Gdy w liczniku zawieszeń znajduję się wartość większa od zera to wątek pozostaje w stanie zawieszenia. By dany wątek został wykonany należy uruchomić funkcję ResumeThread( ), z uchwytem wątku przekazywanym w parametrze. Uruchomienie funkcji ResumeThread( ) powoduje zmniejszenie o jeden wartości licznika zawieszeń. Gdy wartość tego licznika osiągnie zero wątek ulegnie wykonaniu.

Funkcja, która powoduje ponowne zawieszenie wątku to SuspendThread( ). Wywołanie tej funkcji kilka razy powoduje za każdym razem zwiększenie licznika zawieszeń o jeden, co powoduje że jeśli chcemy wykonać wątek musimy funkcje ResumeThread( ) wykonać też kilka razy aż do momentu gdy licznik zawieszeń się wyzeruje.

Kolejną z funkcji dotyczących wątków jest funkcja Sleep( ), która przyjmuje jako parametr wartość w milisekundach. Liczba będąca parametrem tej funkcji wskazuje jak długą chwilę należy zawiesić wykonywanie bieżącego wątku.

Tradycyjnie zakończenie wątku następuj po zakończeniu wykonywania jego funkcji. Zanim to jednak nastąpi można wymusić to, by wątek się zakończył. Funkcją API, która do tego celu służy jest ExitThread( ).

Priorytety wątków :

Ważną sprawą są priorytety wątków, określają one ich ważność, a co za tym idzie ilość przydzielanego im czasu procesora. Priorytet każdego wątku jest ściśle powiązany z priorytetem procesu, do którego należy, tak więc określa się go w sposób względny. Do nadawania wątkowi priorytetu służy funkcja SetThreadPriority( ). Deklaracja tej funkcji wygląda następująco :

function SetThreadPriority (hThread : Thandle;

nPriority : integer) : Bool;

Parametr pierwszy ( hThread ) określa uchwyt wątku, natomiast drugi parametr (nPriority) zawiera wartość nadawanego mu priorytetu.

Lista wartości, jakie może przyjmować drugi parametr :

THREAD_PRIORITY_IDLE - priorytet równy 16 dla procesów o najwyższym priorytecie (należących do klasy priorytetowej czasu rzeczywistego) oraz 1 dla pozostałych procesów.

THREAD_PRIORITY_LOWEST - priorytet wątku mniejszy o 2 od priorytetu procesu.

THREAD_PRIORITY_BELOW_NORMAL - priorytet wątku mniejszy o 1od priorytetu procesu.

THREAD_PRIORITY_NORMAL - priorytet wątku równy priorytetowi jego procesu.

THREAD_PRIORITY_ABOVE_NORMAL - priorytet wątku większy o 1 od priorytetu procesu.

THREAD_PRIORITY_HIGHEST - priorytet wątku większy o 2 od priorytetu procesu.

THREAD_PRIORITY_TIME_CRITICAL - priorytet równy 31 dla procesów o najwyższym priorytecie (należących do klasy priorytetowej czasu rzeczywistego) oraz 15 dla pozostałych procesów.

Nie tylko wysokość priorytetu decyduje o kolejności i sposobie wykonywania wątków, dlatego nie należy nadawać priorytetu wątkom niepotrzebnie, gdyż w większości przypadków, najwłaściwszym rozwiązaniem jest wartość domyślna, przyznawana im w sposób automatyczny. Nie należy ustawiać bardzo wysokiego priorytetu dla wątku, który wykonuje pracochłonne zadanie, bo może to spowodować wyraźne obniżenie wydajności całego systemu. Wątkami kwalifikującymi się do otrzymania wysokiego priorytetu, są taki które wykonują swoje czynności rzadko i krótko, a dodatkowo znaczenie ma szybkość reakcji na określone zdarzenie (przykładem takich wątków mogą być procesy obsługujące dialog z użytkownikiem - odpowiedź na naciśnięcie przycisku myszki, czy klawisza na klawiaturze).

Zmienne wątków :

Gdy w programie działała kilka wątków, to korzystają one z tej samej przestrzeni adresowej, czego wynikiem jest fakt , że mają one dostęp do tych samych zmiennych globalnych. Natomiast zmienne lokalne funkcji lub procedur widoczne są jedynie w tych podprogramach, ponieważ są definiowane na stosie. Zdarza się często, że są potrzebne zmienne globalne, a co za tym idzie widziane przez wiele funkcji równocześnie, jednak odrębne dla każdego wątku. W tym wypadku system operacyjny daje nam możliwość zdefiniowania takich zmiennych w tzw. pamięci lokalnej wątku (TLS - ang. Thread Local Storage). W Delphi do zdefiniowania tego typu zmiennych globalnych służy słowo kluczowe threadvar zamiast var.

Thread Local Storage - czyli pamięć lokalna wątku. Realizowana jest w prywatnym obszarze stosu wątku, w którym na ten cel rezerwowane są 64 wskaźniki. Z każdą zmienną globalną wątku związany jest jeden z tych wskaźników, co powoduje iż każdy wątek posiada w rzeczywistości dostęp do swojej własnej, odrębnej zmiennej. Dostęp do tych zmiennych jest nieco wolniejszy niż odwołanie do „zwykłych zmiennych”, a realizowany jest za pomocą odpowiednich funkcji API.



Wyszukiwarka

Podobne podstrony:
Win 00 Prtofesional
Win 00 server
00
Ergonomia 00
13 ZACHOWANIA ZDROWOTNE gr wtorek 17;00
obiektywne metody oceny postawy ciała (win 1997 2003)
wprowadzenie do systemu win i podst sieci
39 SC DS300 R BMW 5 A 00 XX
00 NPS
III CKN 694 00 id 210233 Nieznany
00 2 Nowa Wiosna
A8 00
egzamin 2007, II rok, II rok CM UMK, Giełdy, 2 rok, II rok, giełdy od Nura, fizjo, egzamin, New fold
tabelka2008, EiE labo, Elektronika i Energoelektronika. Laboratorium, 00.Materiały o wyposażeniu lab
gielda 2010 godz 14.00, Giełdy z farmy
Pilot niezłomny, Żołnierze Wyklęci, Żołnierze Wyklęci , WIN , NSZ
Akumulator do HAKO00 D00 D00 D00 D
Akumulator do BUCHER00$00(0000$00(00
Akumulator do BOLINDERS VOLVO Others S?0 S00

więcej podobnych podstron