Procesy i wątki w WinAPI
Jacek Matulewski
20 lutego 2017 Programowanie Windows
http://www.fizyka.umk.pl/~jacek/dydaktyka/winprog_v2/
Definicje
• Proces to egzemplarz/instancja programu z własną wirtualną przestrzenią adresową, kontekstem bezpieczeństwa, dostępem do obiektów systemowych, unikalnym identyfikatorem (PID), przynajmniej jednym wątkiem (tzw. głównym, primary)
• Wątek to kod wykonywany w obrębie procesu; proces może uruchomić wiele wątków, wykonywanych współbieżnie. Wątki współdzielą przestrzeń adresową procesu, mając dostęp do
wspólnych zmiennych i struktur (tu ostrożnie).
• MSDN: https://msdn.microsoft.com/en-
us/library/windows/desktop/ms684841(v=vs.85).aspx
• Pula wątków (ang. thread pool) – na poziomie aplikacji
• UMS (ang. user-mode scheduler) w obrębie aplikacji, lekki
Uruchamianie procesów
• Tworzenie procesów to de facto uruchamianie programów
• Wcześniej poznaliśmy funkcje WinExec i ShellExecute
• Obie otaczają funkcję CreateProcess
• Proces zwraca strukturę PROCESS_INFORMATION
zawierającą uchwyt procesu, uchwyt głównego wątku, PID, i identyfikator głównego wątku.
• Dodatkowo CreateProcessAsUser ,
CreateProcessWithLogon (np. dodatkowe uprawnienia)
• Czekanie na zakończenie procesu WaitForSingleObject
• Sprzątanie: CloseHandle dla uchwytu procesu i uchwytu wątku (nie zamyka procesu, tylko sprząta)
Uruchamianie procesów
• Przykładowy kod:
bool Wynik=CreateProcess(
NULL, //plik exe aplikacji
lFileName,//polecenie uruchamiające program
NULL, //korzystamy z domyślnego opisu bezpieczeństwa procesu NULL, //korzystamy z domyślnego opisu bezpieczeństwa wątku false, //czy uchwyty dziedziczone w procesie
priority, //priorytet
NULL, //korzystanie ze zmiennych środowiska aplikacji wywołującej lDirectory, //katalog bieżący aplikacji
&startupInfo, //parametry uruchomienia aplikacji
&lProcessInformation //informacje o uruchomionej aplikacji );
• Zmienne środowiskowe zastąpił rejestr systemu Windows
Priorytet procesów
• Odczytanie i zmiana priorytetu procesu
GetPriorityClass i SetPriorityClass
• Wymagane uprawnienia PROCESS_QUERY_INFORMATION
• Zwraca jedną z wartości:
REALTIME_PRIORITY_CLASS HIGH_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS NORMAL_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS IDLE_PRIORITY_CLASS
• Priorytet to wskazówka dla planisty systemu Windows
Obsługa procesów
• Maksymalne uprawnienia PROCESS_ALL_ACCESS
• TerminateProcess - przerywanie procesu i jego wątków (asynchroniczna, podobnie jak CreateProcess)
• ExitProcess – z wnętrza procesu
(return w procedurze wątku też woła tę funkcję)
• GetCurrentProcess – pobieranie niby-uchwytu do bieżącego procesu (stałej interpretowana w funkcjach)
• GetProcessId, GetCurrentProcessId –
„prawdziwe” PID wskazanego procesu i bieżącego procesu
• GetProcessInformation, GetProcessIoCounters, GetProcessTimes, Get/SetProcessAffinityMask
• IsImmersiveProcess (czy proces z Windows Store App)
Tworzenie wątków
• Funkcja CreateThread
(zob. też CreateRemoteThread – w innym procesie)
HANDLE uchwytWątku = CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
//wskaźnik do SECURITY_ATTRIBUTES SIZE_T dwStackSize, //początkowy rozmiar stosu w bajtach
LPTHREAD_START_ROUTINE lpStartAddress, //uruchamiana funkcja typu: DWORD WINAPI ThreadProc(LPVOID lpParameter);
LPVOID lpParameter, //parametr przesyłany do funkcji DWORD dwCreationFlags,
LPDWORD lpThreadId //zwraca ID wątku );
• Dobrze mieć uprawnienia THREAD_ALL_ACCESS
Priorytety wątków
• Analogiczne, jak w przypadku procesów
• Także priorytety wątków mogą być zmieniane po utworzeniu
• Priorytety wątków są względne wobec priorytetów procesów
• Funkcje SetThreadPriority i GetThreadPriority
Priority boost
(dla wątków o priorytecie NORMAL_PRIORITY_CLASS)
• Planista przyspiesza procesy/wątki będące w pierwszym planie
• Planista zwiększa wątek okna, które ma focus
• Wątek jest wznawiany po użyciu funkcji Wait..
• Przyspieszenie takie można to zablokować używając funkcji SetThreadPriorityBoost (podobnie SetProcess..)
Wstrzymywanie wątków
• Usypianie wątku: Sleep na określoną liczbę milisekund
• Wstrzymywanie i wznawianie wątku:
SuspendThread, ResumeThread
• Jeżeli funkcja SuspendThread została wywołana trzy razy, tyle samo musi być wywołana funkcja ResumeThread.
• Wątki można tworzyć funkcją CreateThread od razu wstrzymane – flaga CREATE_SUSPENDED
Przerywanie wątków
• Zła praktyka: TerminateThread – przerywanie bez dania szansy wątkowi na dokończenie procedury
(w tym na zwolnienie zasobów, m.in. pamięci).
Jeżeli wątek jest w sekcji krytycznej – może nigdy nie zostać zwolniona.
Procedura wątku nie jest powiadamiana o zamknięciu.
• Lepiej powiadomić procedurę wątku, że powinna się zakończyć np. przesłać do procedury jako parametr uchwyt zdarzenia
(funkcja CreateEvent) i wywołać go z funkcji wywołującej (funkcja SetEvent).
• TerminateThread vs. ExitThread (uwaga w C++)
Synchronizacja wątków
• Zasadnicze problemy programowania współbieżnego to
1) określenie, które zmienne powinny być współdzielone, a które
dostępne tylko z wątku oraz 2) wydzielenie fragmentów, które powinny być wykonane tylko przez jeden wątek na raz (np. zapis do pliku).
• Funkcje Wait.., np. WaitForSingleObject,
• Zdarzenia (CreateEvent, SetEvent),
• Sekcje krytyczne (ang. critical sections),
• Slim Reader/Writer (SRW) locks,
• MemoryBarrier – makro implementujące barierę I/O,
• Muteksy (ang. mutex od mutual exclusion) – między procesami,
• Semafory (sekcja krytyczna dla więcej niż jednego wątku).
Sekcje krytyczne
• Zakres: tylko w obrębie jednego procesu
• Obiekt CRITICAL_SECTION – identyfikuje sekcję
• Inicjowanie i usuwanie obiektu sekcji krytycznej:
InitializeCriticalSection, DeleteCriticalSection
• Wejście do sekcji krytycznej EnterCriticalSection – inne wątki zatrzymują się na tej funkcji, jeżeli mają ten sam argument identyfikujący sekcję, dopóki bieżąca nie opuści sekcji
• Wyjście z sekcji: LeaveCriticalSection (do sekcji krytycznej wchodzi następny wątek)
• TryEnterCriticalSection – wersja asynchroniczna
Muteksy
• Zakres: w obrębie sesji systemu – działa między procesami
• Można użyć, aby umożliwić tylko jedną instancję aplikacji
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
try {
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, 0, "Unikalny");
if (!hMutex) hMutex = CreateMutex(0, 0, "Unikalny");
else return 0;
//Kod aplikacji (m.in. tworzenie wątku UI) ReleaseMutex(hMutex);
}
catch { ... } return 0;
}
Synchronizacja - problemy
• Problem ucztujących filozofów
• Na dwóch wątkach - przelew
• Scenariusz czytelników i pisarzy
• Dwa wątki z jednym zasobem – problem producent-konsument