API-REFERAT.doc

(651 KB) Pobierz
API czyli z angielskiego „application programming interface”, a po polsku: interfejs programowania aplikacji

 

 

 

 

 

 

 

 

 

 

 

 

 

Funkcje API 2000. Komunikatory, semafory, wątki

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                                                   


Co to są funkcje API?

API czyli z angielskiego „application programming interface”, a po polsku: interfejs programowania aplikacji. Dawniej jedynymi użytkownikami komputerów byli informatycy i programiści. W tamtych czasach program komputerowy pobierał dane, przeliczał je i drukował wynik. Z biegiem czasu zaczęto jednak tworzyć w programach menu, które umożliwiało użytkownikowi dostęp do wszystkich opcji programu, a przy tym użytkownik nie musiał znać zawiłych komend. Gdyby każdy z programistów projektował własny interfejs, to po pierwsze: pisanie programu zajęło by więcej czasu, po drugie każdy program wyglądałby inaczej. Dlatego autorzy systemu operacyjnego Windows stworzyli biblioteki funkcji zwane API Windows, które pozwalają zbudować praktycznie dowolną aplikację działającą w systemach Windows 95, 98, NT, 2000, XP. API 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ą. Aplikacje, które używają funkcji API wymagają oczywiście, aby był zainstalowany jeden z wyżej wymienionych systemów. Nie wszystkie funkcje API działają na wszystkich systemach serii Windows, niektóre, zwłaszcza te najnowsze mogą wymagać Windows’a NT, 2000 lub XP. Oczywiście można się obejść bez API wykorzystując biblioteki obiektowe, takie jak MFC (Microsoft) czy OWL (Borland) lub jeszcze inne.

Proces.

 

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.

 

1.                       Wątek.

 

              Wątek (ang. thread), nazywany także procesem lekkim (ang. lightweight process – LWP). Jest podstawową jednostką wykorzystania procesora, w skład której wchodzą : licznik rozkazów, zbiór rejestrów i obszar stosu. 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 (ang. task).

Proces tradycyjny, czyli ciężki (ang. heavyweight), jest równoważny zadaniu z jednym wątkiem. Zadanie nie robi nic, jeśli nie ma w nim ani jednego wątku. Natomiast wątek może przebiegać w dokładnie jednym zadaniu. Daleko posunięte dzielenie zasobów powoduje, że przełączanie procesora między równorzędnymi wątkami, jak również tworzenie wątków, jest tanie w porównaniu z przełączaniem kontekstu między tradycyjnymi procesami ciężkimi. Choć przełączanie kontekstu między wątkami nadal wymaga przełączania zbioru rejestrów, jednak nie trzeba wykonywać żadnych prac związanych z zarządzaniem pamięcią. Jak w każdym środowisku przetwarzania równoległego, podział procesu na wątki może prowadzić do zagadnień sterowania współbieżnością i konieczności stosowania sekcji krytycznych lub zamków.

W niektórych systemach zrealizowano też wątki poziomu użytkownika (ang. user – level threads), z których korzysta się za pośrednictwem wywołań bibliotecznych zamiast odwołań do systemu. dzięki czemu przełączanie wątków nie wymaga wzywania systemu operacyjnego i przerwań związanych z przechodzeniem do jego jądra. Przełączanie między wątkami poziomu użytkownika może być wykonywane niezależnie od systemu operacyjnego, może więc się odbywać bardzo szybko. Blokowanie jednego wątku i przełączanie do innego wątku jest zatem rozsądnym rozwiązaniem problemu wydajnego obsługiwania przez serwer wielu zamówień. Wątki poziomu użytkownika mają jednak też swoje wady. Na przykład, jeśli jądro jest jednowątkowe, to każdy wątek poziomu użytkownika odwołujący się do systemu będzie powodował oczekiwanie całego zadania na zakończenie wywołania systemowego.

 

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.

 

Funkcje związane z wątkami :

 

·         CreateThread( )

·         ResumeThread( )

·         SuspendedThread( )

·         Sleep( )

·         ExitThread( )

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

·         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 :

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

o       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 klwiaturze).

             

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.

 

Uwagi na temat implementacji wątków w systemie operacyjnym :

 

 

·         Pakiet wątków : zbiór elementarnych działań na wątkach dostępnych w systemie (np. procedur bibliotecznych.

·     &...

Zgłoś jeśli naruszono regulamin