C Helpdesk

C Helpdesk

  • Nie jesteś zalogowany.
  • Polecamy: Moda

  • Index
  •  » Tutorial
  •  » [C/C++] Współużytkowanie obiektów jądra w systemie Windows.

#1 04-01-2007 00:28:30

mieczyk

Member

3375619
Zarejestrowany: 03-08-2007
Posty: 24
Punktów :   
WWW

[C/C++] Współużytkowanie obiektów jądra w systemie Windows.

----------------------------------------------------------------  ----------------------
0x01. Wstęp.
0x02. Co to jest obiekt jądra?
0x03. Co to jest proces?
0x04. Tablica procesu z uchwytami obiektów jądra.
0x05. Dziedziczenie uchwytów obiektów.
0x06. Używanie obiektów nazwanych.
0x07. Zakończenie - duplikowanie uchwytów obiektów jądra.
0x08. Bibliografia.

----------------------------------------------------------------  ------------------------

----------------------------------------------------------------  ------------------------
0x01. Wstęp
----------------------------------------------------------------  ------------------------

Teskt pokazuje dwie spośród trzech metod współużytkowania obiektów jądra w systemie Windows. Do zrozumienia tego artykułu wymagana jest podstawowa znajomość WinAPI i języka C/C++. W razie błędów merytorycznych lub technicznych bardzo proszę o sprostowanie.

----------------------------------------------------------------  ------------------------
0x02. Co to jest obiekt jądra?
----------------------------------------------------------------  ------------------------

Z definicji, obiekt jądra jest to blok pamięci zaalokowany i używany bezpośrednio przez jądro systemu. Blok ten jest po prostu strukturą danych przechowującą informacje o obiekcie. System może tworzyć i wykorzystywać różne typy obiektów jądra, takie jak zdarzenia, znaczniki dostępu, pliki, wątki, procesy, muteksy, semafory, czasomierze, zadania, porty zakończenia I/O. Tego typu obiekty można tworzyć przez wywołanie odpowiednich funkcji np.  CreateProcess, CreateMutex, CreateEvent, etc.

Struktury danych obiektów jądra są dostępne tylko dla jądra systemu, dlatego żadna aplikacja nie może bezpośrednio modyfikować tych struktur. Jedną ze składowych takiej struktury jest tzw. licznik użyć. Jeśli licznik użyć danego obiektu jest ustawiony na 0, oznacza to, że żaden proces jego nie używa i jądro systemu może zniszczyć ten obiekt. Licznik użyć wskazuje, ile procesów używa danego obiektu jądra, a jądro nie zniszczy go dopóki licznik użyć nie dojdzie do 0.

Za każdym razem gdy tworzony jest obiekt jądra za pomocą odpowiedniej  funkcji, zwraca ona uchwyt tego obiektu (wartość HANDLE). Wartość uchwytu może być używana zarówno przez wątki procesu, który utworzył obiekt, jak i inne procesy (po odpowiednich zabiegach przedstawionych w dalszej części).

Napisałem wcześniej, że istnieją takie obiekty jądra jak na przykład pliki. Tutaj należy sprostować, że obiekt-plik i plik to dwie różne rzeczy. Obiekt-plik jest tylko strukturą w pamięci, której system używa do zarządzania plikiem. Podobnie jest z procesami i wątkami.

----------------------------------------------------------------  ----------------------------
0x03. Co to jest proces?
----------------------------------------------------------------  ----------------------------

Pojęcie proces jest najczęściej definiowane jako instancja działającego programu. W skład procesu wchodzi obiekt jądra (obiekt-proces), za pomocą którego system może nim zarządzać. Są tutaj przechowywane statystyczne informacje o procesie. Kolejnym składnikiem procesu jest przestrzeń adresowa, która zawiera cały kod i dane modułu wykonywalnego lub DLL. Do przestrzeni należy też pamięć alokowana dynamicznie (np. stosy i sterty wątków). Każdy proces musi mieć przynajmniej jeden wątek, który wykonuje kod z przestrzeni adresowej tego procesu.

----------------------------------------------------------------  -----------------------------
0x04.  Tablica procesu z uchwytami obiektów jądra
----------------------------------------------------------------  -----------------------------

Za każdym razem, kiedy następuje inicjalizacja procesu, system alokuje tablicę uchwytów dla tego procesu. Jest ona przeznaczona tylko i wyłącznie na obiekty jądra. Są tu przechowywane uchwyty obiektów używanych przez proces, wskaźniki do bloków pamięci obiektów jądra, maski dostępu i flagi.

Kiedy wywołujemy funkcję tworzącą obiekt jądra, jądro systemu alokuje blok pamięci na ten obiekt. Inicjalizuje go i umieszcze jego uchwyt wraz z informacjami w tablicy procesu.

Oto bardzo ogólny wygląd takiej tablicy:
----------------------------------------------------------------  --------------------------------------
Indeks         wksaźnik do bloku pamięci obiektu            Maska dostępu             Flagi 
----------------------------------------------------------------  --------------------------------------
1                    0x00001000                                        0x000000001          0x00000001


----------------------------------------------------------------  --------------------------------------

Indeks tablicy ma w rzeczywistości taką samą wartość co uchwyt tworzonego obiektu jądra. W powyższym przykładzie widzmy, że proces do którego odwołuje się ta tablica, korzysta z jednego obiektu, którego uchwyt ma wartość 1.

Spróbujmy teraz napisać krótki programik, któru stworzy dwa obiekty jądra i przekaże ich uchwyty do zmiennych:

uchwyt.cpp

Kod:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hi, HINSTANCE, PSTR cmd, int show)          
{
 HANDLE uchwyt_muteksu = CreateMutex(NULL, TRUE, NULL);
 HANDLE uchwyt_semafora = CreateSemaphore(NULL, 0, 5, NULL);

 CloseHandle(uchwyt_muteksu);
 CloseHandle(uchwyt_semafora);

 return 0;
}

•      WinMain, jak wiadomo stanowi punkt wejścia aplikacji GUI (Graphical User Interface).

•      Funkcja CreateMutex jest funkcją tworzącą obiekt jądra, jakim jest muteks. Muteksy służą do synchronizacji wątków, ale o tym innym razem, ponieważ nam chodzi tylko o wartość uchwytów. Funkcja ta zwraca wartość HANDLE (wartość uchwytu), która jest przekazana do zmiennej uchwyt_muteksu.

•      Następnie tworzymy kolejny obiekt jądra jakim jest semafor (ten również służy do synchronizacji wątków, lecz nam chodzi głównie o uchwyt). Tak samo jak wcześniej, funkcja zwraca wartość typu HANDLE, a ta przypisywana jest do zmiennej uchwyt_semafora.

•      Funkcjia CloseHandle zmniejsza o 1 licznik użyć obiektu. Jeśli żaden inny proces nie korzysta z tych obiektów, to ich licznik powninen być równy 0 i system będzie mógł zniszczyć stworzone przez nas obiekty. Jeśli natomiast inny proces korzystałby z naszych obiektów, to licznik użyć byłby dalej większy od 0 (nawet po wywołaniu funkcji CloseHandle ). Nasz proces zakończyłby działanie pomyślnie, ale obiekty istniałyby nadal.

Czasami zachodzi potrzeba przekazania uchwytów obiektów jądra do innego procesu. WinAPI oferuje trzy sposoby, a dwa z nich dokładnie opiszę.

----------------------------------------------------------------  -----------------------------
0x05. Dziedziczenie uchwytów obiektów
----------------------------------------------------------------  -----------------------------

Jedną z metod wspóldzielenia obiektów jądra jest zastosowanie mechanizmu dziedziczenia uchwytów. Ta metoda działa tylko wtedy, gdy proces nadrzędny wywołuje proces potomny. W takiej sytuacji proces-rodzic może przekazać uchwyty obiektów jądra swojemu potomkowi.

Objaśnię tą metodę na przykładzie:

rodzic.cpp

Kod:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hi, HINSTANCE, PSTR cmd, int show)
{

   SECURITY_ATTRIBUTES sa;
   sa.nLength = sizeof(sa);
   sa.lpSecurityDescriptor = NULL;
   sa.bInheritHandle = TRUE;

   HANDLE uchwyt_zdarzenia = CreateEvent(&sa, TRUE, FALSE, NULL);

   SetEnvironmentVariable(TEXT("uchwyt"), (PCTSTR)&uchwyt_zdarzenia);

   TCHAR CommandLine[] = TEXT("potomek");
   STARTUPINFO si = {sizeof(si)};
   PROCESS_INFORMATION pi;

CreateProcess(NULL, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, π);

   return 0;
}

•      Jest to program (proces), który tworzy obiekt-zdarzenie (obiekt jądra) i przekazuje uchwyt tego obiektu, do procesu potomnego (potomek.exe).

•      Przypatrzmy się na początku strukturze SECURITY_ATTRIBUTES. Praktycznie każda funkcja tworząca dowolny obiekt jądra pobiera wskaźnik do tej struktury (jako argument). Struktura ta wygląda następująco:


    typedef struct _SECURITY_ATTRIBUTES
                {
                    DWORD nLength;
                    LPVOID lpSecurityDescriptor;
                    BOOL bInheritHandle;

                } SECURITY_ATTRIBUTES


Parametr nLength określa rozmiar struktury, parametr lpSecurityDescriptor to adres zainicjalizowanego deskryptora bezpeczeństwa. Nas to nie interesuje i możemy podać tutaj NULL. Najważniejszym dla nas parametrem jest bInheritHandle, ponieważ decyduje on o tym, czy tworzony obiekt jądra jest dziedziczny. W naszym programie ustawiliśmy jego wartość na TRUE, co oznacza, że funkcja pobierająca adres struktury SECURITY_ATTRIBUTES stworzy obiekt o dziedzicznym uchwycie. 

•      Następnie tworzymy obiekt jądra jakim jest zdarzenie. Pomimo, że sam obiekt nie jest potrzebny do zrozumienia mechanizmu dziedziczenia, postaram się napisać o nim kilka zdań. Obiekt-zdarzenie jest jednym z podstawowych obiektów jądra służących do synchronizacji wątków. Załóżmy, że w jakimś procesie istnieją dwa wątki. Załóżmy też, że chcemy aby drugi wątek wykonał się dopiero wtedy, gdy pierwszy mu na to pozwoli. Możemy to osiągnąć dzięki zdarzeniom. Obiekt-zdarzenie może być w stanie sygnalizowanym lub niesygnalizowanym. Przyjmijmy, że pierwszy wątek tworzy zdarzenie, które jest niesygnalizowane. W drugim wątku musi istnieć funkcja czekająca (np. WaitForSingleObject), która pozwoli na wykonanie kodu, tylko wtedy gdy zdarzenie w pierwszym wątku zmieni stan na sygnalizowany. Do zasygnalizowania obiektu-zdarzenia służy funkcja SetEvent. Ale tak jak napisałem wcześniej, obiekt-zdarzenie utworzony w naszym programie służy tylko do wyjaśnienia mechanizmu dziedziczenia. Funkcja tworząca obiekt-zdarzenie wygląda tak:

   HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,
                      BOOL fManualReset,
                      BOOL fInitialState,
                      PCTSTR pszName );



Widzimy, że funkcja zwraca wartość typu HANDLE (uchwyt). W naszym programie przypisujemy tą wartość do zmiennej uchwyt_zdarzenia. Jako pierwszy argument podajemy adres struktury SECURITY_ATTRIBUTES. W naszym przypadku jest to &sa. Pamiętaj, że inicjalizując tą strukturę wcześniej, ustawiliśmy składową bInheritHandle na TRUE. Oznacza to, że uchwyt tworzonego obiektu-zdarzenia jest dziedziczny. Kolejnym argumentem funkcji jest fManualReset. My podaliśmy tutaj TRUE, co oznacza, że zdarzenie jest resetowane ręcznie, ale dla nas jest to nieistotne. W argumencie fInitialState podaliśmy wartość FALSE, co sprawia, że obiekt jest narazie niesygnalizowany. Zrobiliśmy tak, ponieważ proces potomny wywoła odpowiednią funkcję, która zasygnalizuje obiekt. Będzie nam to potrzebne do sprawdzenia, czy dziedziczenie uchwytu powiodło się, ale o tym za chwilę.

•      Dużym utrudnieniem jest fakt, że proces potomny nie ma pojęcia, że rodzic przekazuje mu jakikolwiek uchwyt obiektu. Oznacza to, że my sami musimy powiadomić proces potomny o przekazaniu mu jakiegoś uchwytu przez rodzica. Kolejną istotną rzeczą jest to, że wartość uchwytu obiektu w obu procesach musi być taka sama. Pojawia się pytanie, w jaki sposób można przekazać wartość dziedzicznego uchwytu do do procesu potomnego. Otóż istnieje wiele form komunikacji międzyprocesowej. W naszym przykładzie skorzystamy ze zmiennej środowiskowej. Do stworzenia zmiennej środowiskowej służy funkcja SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);   Jako pierwszy argument podajemy nazwę zmiennej środowiskowej, a jako drugi, wartość tej zmiennej. W naszym przypadku jest to wartość uchwytu obiektu-zadania. Proces potomny będzie mógł odczytać wartość tej zmiennej za pomocą odpowiedniej funkcji i będzie wiedział jaką wartość ma uchwyt, który odziedziczył. Część osób zastanawia się pewnie w tym momencie, czy nie wystarczy tylko użyć zmiennej środowiskowej do przekazania uchwytu obiektu. Otóż nie. Bez mechanizmu dziedziczenia, proces potomny otrzymałby tylko wartość, z którą nie miałby co zrobić.

•      Teraz najważniejsza część naszego procesu, czyli wywołanie procesu potomnego za pomocą funkcji CreateProcess. Oto jak wygląda prototyp tej funkcji:

           BOOL CreateProcess(PCTSTR pszApplicationName,
                             PTSTR  pszCommandLine,
                             PSECURITY_ATTRIBUTES psaProcess,
                             PSECURITY_ATTRIBUTES psaThread,
                             BOOL bInheritHandles,
                             DWORD fdwCreate,
                             PVOID pvEnvironment,
                             PCTSTR pszCurDir,
                             PSTARTUPINFO psiStartInfo,
                            PROCESS_INFORMATION ppiProcInfo);



-Pierwszy parametr przekazuje nazwę pliku wykonywalnego, a drugi linię poleceń. W naszym przykładzie w pierwszym argumencie podajemy podaliśmy NULL, natomiast w drugim podaliśmy nazwę pliku wykonywalnego "potomek", czyli nazwę programu (procesu), który odziedziczy uchwyt obiektu-zdarzenia.
-Kolejne dwa parametry określają atrybuty bezpieczeństwa nowopowstałego procesu i wątku. Nas to nie interesuje i podajemy NULL.
-Ważny jest kolejny argument, czyli bInheritHandles. Podaliśmy tutaj TRUE, ponieważ oznacza to, że proces potomny odziedziczy wszystkie obiekty jądra, które oczywiście są dziedziczne. W naszym przypadku jest to tylko obiekt-zdarzenie (jego uchwyt).
-fdwCreate przekazuje flagi, które wpływają na sposób tworzenia nowego procesu. My podajemy 0.
-Następny argument jest również ważny, ponieważ służy do przekazania bloku pamięci z napisami środowiskowymi , których ma używać proces. Podaliśmy NULL, ponieważ oznacza to, że proces potomny dziedziczy wszystkie zmienne środowiskowe, do których ma dostęp proces nadrzędny. Dzięki temu nasz proces potomny będzie miał dostęp do zmiennej środowiskowej, gdzie przechowywana jest wartość uchwytu obiektu-zdarzenia.
-pszCurDir pozwala określić bieżący napęd i katalog procesu potomnego. Podając NULL sprawiliśmy, że katalog roboczy będzie taki sam jak aplikacji uruchamiającej nowy proces. Ostatnie dwa parametry wskazują na adresy dwóch struktur, którymi nie będziemy się teraz zajmować. Trzeba je tylko wcześniej zainicjalizować.


Teraz należy jeszcze utworzyć aplikację "potomek", która będzie procesem potomnym aplikacji "rodzic". To właśnie do potomka przekażemy uchwyt naszego obiektu jądra. Pamiętaj, że obie aplikacje powinny znajdować się w tym samym katalogu.

potomek.cpp

Kod:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hi, HINSTANCE, PSTR cmd, int show)
{

  LPTSTR uchwyt_a;
  HANDLE uchwyt_zdarzenia;

  uchwyt_a = (LPTSTR)malloc(2*sizeof(TCHAR));

  GetEnvironmentVariable(TEXT("uchwyt"), uchwyt_a, 5);

  uchwyt_zdarzenia = reinterpret_cast<HANDLE>(*uchwyt_a);

  BOOL test = ResetEvent(uchwyt_zdarzenia);

if(test==TRUE)
    MessageBox(NULL, TEXT("Dziedziczenie powiodło się!"), TEXT("Sukces"),
                      MB_OK | MB_ICONINFORMATION);
else
     MessageBox(NULL, TEXT("Dziedziczenie nie powiodło się!"), TEXT("Porażka"),
                       MB_OK | MB_ICONERROR);

   return 0;
}

•      Jest to program,  który odziedziczył uchwyt obiektu-zdarzenia i poinformuje nas czy dziedziczenie powiodło się.

•      Na początku musimy odczytać wartość odziedziczonego uchwytu. Robimy to za pomocą funkcji GetEnvironmentVariable. Jako pierwszy argument podajemy nazwę utworzonej przez proces nadrzędny zmiennej środowiskowej. Drugi argument to wskaźnik bufora na wartość tej zmiennej, czyli w naszym przypadku wartość uchwytu obiektu-zdarzenia. Trzeci argument przekazuje rozmiar tego bufora.

•      Trzeba pamiętać, że to co odczytaliśmy ze zmiennej środowiskowej nie jest właściwie uchwytem, dlatego musimy dokonać konwersji za pomocą reinterpret_cast. Dzięki temu zmienna uchwyt_zdarzenia będzie już przechowywać właściwy, odziedziczony uchwyt.

•      Teraz można już sprawdzić, czy nasz proces potomek rzeczywiście odziedziczył uchwyt. Zrobimy to za pomocą funkcji SetEvent, która zmienia obiekt-zdarzenia na stan sygnalizowany (jak pamiętamy , utworzony obiekt miał stan niesygnalizowany). Jako argument podajemy tylko uchwyt obiektu-zdarzenia. Jeśli uchwyt jest właściwy, to funkcja zwraca wartość TRUE.


To tyle jeśli chodzi o współużytkowanie obiektów jądra metodą dziedziczenia. Na koniec warto wspomnieć o funkcji, która zmienia flagi uchwytu, takie jak np. Dziedziczność uchwytu. Funkcja ta powinna być wywołana przez proces tworzący obiekt.


     BOOL SetHandleInformation( HANDLE hObject,
                                DWORD dMask,
                                DWORD dwFlags );



Jeśli chcemy, aby obiekt o uchwycie hObject był dziedziczny robimy tak:


SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);


a jeśli nie:


SetHandleInformation(hObject, HANDLE_FLAG_INHERIT, 0);


----------------------------------------------------------------  -----------------------------
0x06. Używanie obiektów nazwanych
----------------------------------------------------------------  -----------------------------

Innym, wygodniejszym sposobem współużytkowania obiektó jądra jest używanie obiektów nazwanych. Przy dziedziczeniu używaliśmy obiektów anonimowych (parametr pszName ustawialiśmy na NULL). Teraz możemy zrobić odwrotnie.

Większość funkcji tworzących obiekt jądra, pozwala na nadanie nazwy tym obiektom. Ciekawostką jest również to, że przy współużytkowaniu obiektów nazwanych mogą brać udział dwa różne procesy (które nie są spokewnione).

Jak zwykle postaram się to wyjaśnić na przykładzie. Stworzymy dwie aplikacje (procesy): "procesA" i "procesB".

procesA.cpp

Kod:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hi, HINSTANCE, PSTR cmd, int show)
{
 
 HANDLE uchwyt_zdarzenia_a = CreateEvent(NULL, FALSE, TEXT("zdarzenie"));

 MessageBox(NULL, TEXT("Zanim wciśniesz OK, uruchom procesB.exe !"),
                    TEXT("Informacja"), MB_OK | MB_ICONINFORMATION);

 CloseHandle(uchwyt_obiektu_a);

 return 0;
}

•      Od razu widać, że ten sposób jest prostszy, a do tego procesy nie muszą być spokrewnione.

•      Na początku tworzymy obiekt-zdarzenie, który jest niesygnalizowany. Zwróć uwagę na to, że tym razem pierwszy parametr wskazuje NULL. W ostatnim natomiast, czyli lpszName podaliśmy nazwę naszego obiektu, która będzie dostępna dla innych procesów.

•      Musimy jeszcze tylko zatrzymać nasz proces tworzący obiekt jądra, żeby system nie zniszczył go za szybko. Posłużyliśmy się do tego funkcją MessageBox. Zanim wciśniemy OK, musimy uruchomić "procesB", który również wejdzie w posiadanie stworzonego obiektu jądra dzięki użyciu nazw.

procesB.cpp

Kod:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hi, HINSTANCE, PSTR cmd, int show)
{

HANDLE uchwyt_zdarzenia_b = OpenEvent(EVENT_ALL_ACCESS, FALSE,
                                          TEXT("zdarzenie"));

BOOL test = SetEvent(uchwyt_zdarzenia_b);

if(test==TRUE)
MessageBox(NULL, TEXT("Udało się!"), TEXT("Sukces"),  
                 MB_OK | MB_ICONINFORMATION);
else
MessageBox(NULL, TEXT("Nie udało się!"), TEXT("Porażka"),
                  MB_OK | MB_ICONERROR);

return 0;
}

•      Na początku wywołujemy funkcję OpenEvent, w celu otwarcia istniejącego już obiektu jądra. Najpierw prototyp tej funkcji:

            HANDLE OpenEvent(DWORD dwDesiredAccess,
                             BOOL bInheritHandle,
                             LPCTSTR pszName );



Pierwszy argument określa flagi dostępu. My podaliśmy EVENT_ALL_ACCESS co określa wszystkie możliwe flagi dostępu dla zdarzenia. Drugi parametr określa czy zwrócony uchwyt jest dziedziczny. My podajemy FALSE. I w końcu ostatni parametr, który otwiera istniejący już, nazwany obiekt-zdarzenie. My podajemy TEXT("uchwyt") czyli to samo co przy tworzeniu obiektu.

•      Na koniec sprawdzamy czy uchwyt jest właściwy. Sygnalizujemy zdarzenie za pomocą funkcji SetEvent i sprawdzamy wartość zwróconą przez tą funkcję.

----------------------------------------------------------------  -----------------------------
0x07. Zakończenie - duplikowanie uchwytów obiektów jądra
----------------------------------------------------------------  -----------------------------

Istnieje jeszcze jedna metoda przekazywania uchwytów obiektów jądra, tzw. Duplikowanie uchwytów obiektów jądra. Można tego dokonać za pomocą funkcji DuplicateHandle. Ona równeż pozwala na przekazywanie uchwytów między różnymi procesami poprzez duplikowanie tych uchwytów. Pokażę w jaki sposób to działa, ale już innym razem .

----------------------------------------------------------------  -----------------------------
0x08. Bibliografia
----------------------------------------------------------------  -----------------------------

J. Richter, Programowanie aplikacji dla Windows, Warszawa 2002;

[url]msdn.microsoft.com[\url]

Offline

 
  • Index
  •  » Tutorial
  •  » [C/C++] Współużytkowanie obiektów jądra w systemie Windows.

Stopka forum

RSS
Powered by PunBB 1.2.23
© Copyright 2002–2008 PunBB
Polityka cookies - Wersja Lo-Fi


Darmowe Forum | Ciekawe Fora | Darmowe Fora
GotLink.plnoclegi w Pucku transport międzynarodowy Poznań Stylowy ogród na który Cię stać